@hachej/boring-feedback 0.0.0-stage → 0.2.5
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/INVARIANTS.md +35 -0
- package/LICENSE +21 -0
- package/README.md +20 -2
- package/dist/agent/capability.d.ts +52 -0
- package/dist/agent/capability.js +230 -0
- package/dist/agent/index.d.ts +4 -0
- package/dist/agent/index.js +5 -0
- package/dist/agent/preview.d.ts +10 -0
- package/dist/agent/preview.js +27 -0
- package/dist/format/index.d.ts +7 -0
- package/dist/format/index.js +6 -0
- package/dist/format/json.d.ts +30 -0
- package/dist/format/json.js +193 -0
- package/dist/format/preview.d.ts +28 -0
- package/dist/format/preview.js +45 -0
- package/dist/format/readable.d.ts +10 -0
- package/dist/format/readable.js +19 -0
- package/dist/format/report.d.ts +187 -0
- package/dist/format/report.js +669 -0
- package/dist/format/requests.d.ts +53 -0
- package/dist/format/requests.js +69 -0
- package/dist/page/app-element.d.ts +79 -0
- package/dist/page/app-element.js +451 -0
- package/dist/page/canaries.d.ts +46 -0
- package/dist/page/canaries.js +142 -0
- package/dist/page/index.d.ts +6 -0
- package/dist/page/index.js +8 -0
- package/dist/page/picker/index.d.ts +4 -0
- package/dist/page/picker/index.js +4 -0
- package/dist/page/picker/label.d.ts +16 -0
- package/dist/page/picker/label.js +58 -0
- package/dist/page/picker/overlay.d.ts +42 -0
- package/dist/page/picker/overlay.js +126 -0
- package/dist/page/picker/picker.d.ts +72 -0
- package/dist/page/picker/picker.js +386 -0
- package/dist/page/privacy/digest.d.ts +6 -0
- package/dist/page/privacy/digest.js +24 -0
- package/dist/page/privacy/index.d.ts +6 -0
- package/dist/page/privacy/index.js +5 -0
- package/dist/page/privacy/name.d.ts +8 -0
- package/dist/page/privacy/name.js +106 -0
- package/dist/page/privacy/policy.d.ts +65 -0
- package/dist/page/privacy/policy.js +125 -0
- package/dist/page/privacy/route.d.ts +6 -0
- package/dist/page/privacy/route.js +20 -0
- package/dist/page/privacy/serialize.d.ts +42 -0
- package/dist/page/privacy/serialize.js +90 -0
- package/dist/page/voice/align.d.ts +36 -0
- package/dist/page/voice/align.js +122 -0
- package/dist/page/voice/capture.d.ts +43 -0
- package/dist/page/voice/capture.js +186 -0
- package/dist/page/voice/index.d.ts +3 -0
- package/dist/page/voice/index.js +3 -0
- package/dist/preview/agent.d.ts +34 -0
- package/dist/preview/agent.js +91 -0
- package/dist/preview/index.d.ts +5 -0
- package/dist/preview/index.js +6 -0
- package/dist/preview/page.d.ts +50 -0
- package/dist/preview/page.js +293 -0
- package/dist/preview/session.d.ts +63 -0
- package/dist/preview/session.js +110 -0
- package/dist/source/index.d.ts +24 -0
- package/dist/source/index.js +59 -0
- package/dist/source/jsx-dev-runtime.d.ts +5 -0
- package/dist/source/jsx-dev-runtime.js +29 -0
- package/dist/store/index.d.ts +5 -0
- package/dist/store/index.js +4 -0
- package/dist/store/mentions.d.ts +17 -0
- package/dist/store/mentions.js +27 -0
- package/dist/store/store.d.ts +121 -0
- package/dist/store/store.js +353 -0
- package/dist/tickets/index.d.ts +5 -0
- package/dist/tickets/index.js +5 -0
- package/dist/tickets/sinks.d.ts +27 -0
- package/dist/tickets/sinks.js +84 -0
- package/dist/tickets/ticket.d.ts +93 -0
- package/dist/tickets/ticket.js +148 -0
- package/dist/ui/annotate.d.ts +103 -0
- package/dist/ui/annotate.js +154 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/report.d.ts +51 -0
- package/dist/ui/report.js +58 -0
- package/dist/ui/save.d.ts +45 -0
- package/dist/ui/save.js +65 -0
- package/dist/ui/session.d.ts +188 -0
- package/dist/ui/session.js +787 -0
- package/dist/ui/show.d.ts +43 -0
- package/dist/ui/show.js +72 -0
- package/dist/ui/state.d.ts +11 -0
- package/dist/ui/state.js +21 -0
- package/package.json +98 -4
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type AppElementRange, type PickerOverlay, type PrivacyPolicy } from '../page/index.js';
|
|
2
|
+
export interface ShowCandidate {
|
|
3
|
+
/** 1-based, in the order resolution ranked them. */
|
|
4
|
+
readonly number: number;
|
|
5
|
+
/** The candidate's label from the privacy policy. */
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly range: AppElementRange;
|
|
8
|
+
}
|
|
9
|
+
export type ShowOutcome = {
|
|
10
|
+
readonly kind: 'revealed';
|
|
11
|
+
readonly placement: 'exact' | 'moved' | 'chosen';
|
|
12
|
+
readonly element: Element;
|
|
13
|
+
readonly evaluated: string;
|
|
14
|
+
}
|
|
15
|
+
/** A person must choose: the candidates are numbered on the overlay and here. `choose` reveals one against the same snapshot. */
|
|
16
|
+
| {
|
|
17
|
+
readonly kind: 'choose';
|
|
18
|
+
readonly candidates: readonly ShowCandidate[];
|
|
19
|
+
readonly evaluated: string;
|
|
20
|
+
readonly choose: (number: number) => ShowOutcome;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'stale';
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'missing';
|
|
26
|
+
readonly reason: string;
|
|
27
|
+
readonly evaluated: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'unsupported';
|
|
30
|
+
readonly reason: string;
|
|
31
|
+
};
|
|
32
|
+
export interface ShowOptions {
|
|
33
|
+
readonly anchor: unknown;
|
|
34
|
+
readonly root: Element;
|
|
35
|
+
readonly policy: PrivacyPolicy;
|
|
36
|
+
/** Draws the revealed element or the numbered candidates. Without it Show only scrolls. */
|
|
37
|
+
readonly overlay?: PickerOverlay;
|
|
38
|
+
/** The note drawn with a revealed element. */
|
|
39
|
+
readonly note?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Resolves an anchor against the live page now. Exact and moved placements are revealed; ambiguous ones wait for the person. */
|
|
42
|
+
export declare function showAnchor(options: ShowOptions): Promise<ShowOutcome>;
|
|
43
|
+
//# sourceMappingURL=show.d.ts.map
|
package/dist/ui/show.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Show (FEEDBACK.md, "Pointing at elements" and "Activating it on an agent"): resolve a stored anchor against a FRESH snapshot of the
|
|
2
|
+
// live page, reveal only `exact` or `moved`, number `ambiguous` candidates and let the person choose, and say honestly when the element
|
|
3
|
+
// is missing, stale or of a kind this page cannot place (FEEDBACK-4, FEEDBACK-7). Nothing uncertain is revealed without that choice.
|
|
4
|
+
import { APP_ELEMENT_KIND, appElementResolution, elementAt, pageDigest, pickerLabel, revealElement, serializePage, } from '../page/index.js';
|
|
5
|
+
const STALE_RETRIES = 1;
|
|
6
|
+
/** Resolves an anchor against the live page now. Exact and moved placements are revealed; ambiguous ones wait for the person. */
|
|
7
|
+
export async function showAnchor(options) {
|
|
8
|
+
const { root, policy, overlay, note } = options;
|
|
9
|
+
const kind = typeof options.anchor === 'object' && options.anchor !== null ? options.anchor.kind : undefined;
|
|
10
|
+
if (kind !== APP_ELEMENT_KIND)
|
|
11
|
+
return { kind: 'unsupported', reason: `This page places ${APP_ELEMENT_KIND} anchors only${typeof kind === 'string' ? `, not ${kind}` : ''}.` };
|
|
12
|
+
let anchor;
|
|
13
|
+
try {
|
|
14
|
+
anchor = appElementResolution.schema.parse(options.anchor);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
return { kind: 'unsupported', reason: error instanceof Error ? error.message : 'The anchor is not valid.' };
|
|
18
|
+
}
|
|
19
|
+
for (let attempt = 0;; attempt++) {
|
|
20
|
+
const snapshot = serializePage(root, policy);
|
|
21
|
+
const evaluated = await pageDigest(snapshot);
|
|
22
|
+
const placement = appElementResolution.resolve(anchor, snapshot, evaluated);
|
|
23
|
+
switch (placement.kind) {
|
|
24
|
+
case 'exact':
|
|
25
|
+
case 'moved': {
|
|
26
|
+
const revealed = revealElement(placement.range, root, snapshot, policy);
|
|
27
|
+
if (revealed.kind === 'revealed') {
|
|
28
|
+
overlay?.highlight(revealed.element, note ?? anchor.fallback);
|
|
29
|
+
return { kind: 'revealed', placement: placement.kind, element: revealed.element, evaluated };
|
|
30
|
+
}
|
|
31
|
+
// The page changed between resolve and reveal: resolve again against a fresh snapshot, once.
|
|
32
|
+
if (attempt < STALE_RETRIES)
|
|
33
|
+
continue;
|
|
34
|
+
overlay?.clear();
|
|
35
|
+
return { kind: 'stale', reason: revealed.reason };
|
|
36
|
+
}
|
|
37
|
+
case 'ambiguous':
|
|
38
|
+
return choice(anchor, placement.candidates, root, snapshot, evaluated, policy, overlay, note);
|
|
39
|
+
case 'missing':
|
|
40
|
+
overlay?.clear();
|
|
41
|
+
return { kind: 'missing', reason: `Nothing on this page matches ${anchor.fallback}.`, evaluated };
|
|
42
|
+
default:
|
|
43
|
+
overlay?.clear();
|
|
44
|
+
return { kind: 'unsupported', reason: `This page cannot place the anchor (${placement.kind}).` };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function choice(anchor, ranges, root, snapshot, evaluated, policy, overlay, note) {
|
|
49
|
+
const candidates = [];
|
|
50
|
+
const marks = [];
|
|
51
|
+
ranges.forEach((range, index) => {
|
|
52
|
+
const element = elementAt(range, root, snapshot);
|
|
53
|
+
candidates.push(Object.freeze({ number: index + 1, label: element ? pickerLabel(element, policy) : 'not on screen', range }));
|
|
54
|
+
// Numbers only: no candidate is scrolled to or singled out before the person chooses.
|
|
55
|
+
if (element)
|
|
56
|
+
marks.push({ element, label: String(index + 1), tone: 'candidate' });
|
|
57
|
+
});
|
|
58
|
+
overlay?.draw(marks);
|
|
59
|
+
const choose = (number) => {
|
|
60
|
+
const picked = candidates[number - 1];
|
|
61
|
+
if (!picked)
|
|
62
|
+
return { kind: 'stale', reason: `There is no candidate ${number}.` };
|
|
63
|
+
const revealed = revealElement(picked.range, root, snapshot, policy);
|
|
64
|
+
if (revealed.kind !== 'revealed') {
|
|
65
|
+
overlay?.clear();
|
|
66
|
+
return { kind: 'stale', reason: `${revealed.reason}; Show again.` };
|
|
67
|
+
}
|
|
68
|
+
overlay?.highlight(revealed.element, note ?? anchor.fallback);
|
|
69
|
+
return { kind: 'revealed', placement: 'chosen', element: revealed.element, evaluated };
|
|
70
|
+
};
|
|
71
|
+
return { kind: 'choose', candidates: Object.freeze(candidates), evaluated, choose };
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ExternalState<State> {
|
|
2
|
+
readonly getSnapshot: () => State;
|
|
3
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
4
|
+
}
|
|
5
|
+
export interface WritableState<State> extends ExternalState<State> {
|
|
6
|
+
readonly set: (change: Partial<State>) => State;
|
|
7
|
+
/** Replaces the whole snapshot (drops optional fields `set` would keep). */
|
|
8
|
+
readonly replace: (next: State) => State;
|
|
9
|
+
}
|
|
10
|
+
export declare function createState<State extends object>(initial: State): WritableState<State>;
|
|
11
|
+
//# sourceMappingURL=state.d.ts.map
|
package/dist/ui/state.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// A minimal external store: `subscribe` and `getSnapshot` fit React's `useSyncExternalStore` and any other view layer.
|
|
2
|
+
export function createState(initial) {
|
|
3
|
+
let snapshot = Object.freeze({ ...initial });
|
|
4
|
+
const listeners = new Set();
|
|
5
|
+
return {
|
|
6
|
+
getSnapshot: () => snapshot,
|
|
7
|
+
subscribe: listener => { listeners.add(listener); return () => { listeners.delete(listener); }; },
|
|
8
|
+
set: change => {
|
|
9
|
+
snapshot = Object.freeze({ ...snapshot, ...change });
|
|
10
|
+
for (const listener of [...listeners])
|
|
11
|
+
listener();
|
|
12
|
+
return snapshot;
|
|
13
|
+
},
|
|
14
|
+
replace: next => {
|
|
15
|
+
snapshot = Object.freeze({ ...next });
|
|
16
|
+
for (const listener of [...listeners])
|
|
17
|
+
listener();
|
|
18
|
+
return snapshot;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,100 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-feedback",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"description": "Feedback people leave on what they see, usable without an agent and by agents through an opt-in capability.",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/**/*.js",
|
|
11
|
+
"dist/**/*.d.ts",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"INVARIANTS.md"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
"./format": {
|
|
18
|
+
"types": "./dist/format/index.d.ts",
|
|
19
|
+
"import": "./dist/format/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./store": {
|
|
22
|
+
"types": "./dist/store/index.d.ts",
|
|
23
|
+
"import": "./dist/store/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./agent": {
|
|
26
|
+
"types": "./dist/agent/index.d.ts",
|
|
27
|
+
"import": "./dist/agent/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./page": {
|
|
30
|
+
"types": "./dist/page/index.d.ts",
|
|
31
|
+
"import": "./dist/page/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./ui": {
|
|
34
|
+
"types": "./dist/ui/index.d.ts",
|
|
35
|
+
"import": "./dist/ui/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./preview": {
|
|
38
|
+
"types": "./dist/preview/index.d.ts",
|
|
39
|
+
"import": "./dist/preview/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./tickets": {
|
|
42
|
+
"types": "./dist/tickets/index.d.ts",
|
|
43
|
+
"import": "./dist/tickets/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./source": {
|
|
46
|
+
"types": "./dist/source/index.d.ts",
|
|
47
|
+
"import": "./dist/source/index.js"
|
|
48
|
+
},
|
|
49
|
+
"./source/jsx-dev-runtime": {
|
|
50
|
+
"types": "./dist/source/jsx-dev-runtime.d.ts",
|
|
51
|
+
"import": "./dist/source/jsx-dev-runtime.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@hachej/boring-ui-kit": "^0.2.5",
|
|
56
|
+
"@hachej/boring-files": "^0.2.5",
|
|
57
|
+
"@hachej/boring-agent": "^0.2.5",
|
|
58
|
+
"@earendil-works/pi-durable": "~1.1.0",
|
|
59
|
+
"@earendil-works/pi-ai": "~1.1.0",
|
|
60
|
+
"@earendil-works/chord": "~1.1.0",
|
|
61
|
+
"react": "^19.0.0",
|
|
62
|
+
"esbuild": "^0.28.2"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"@hachej/boring-agent": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@earendil-works/pi-durable": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@earendil-works/pi-ai": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@earendil-works/chord": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"react": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"esbuild": {
|
|
81
|
+
"optional": true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"repository": {
|
|
85
|
+
"type": "git",
|
|
86
|
+
"url": "git+https://github.com/hachej/boring-ui.git",
|
|
87
|
+
"directory": "packages/feedback"
|
|
88
|
+
},
|
|
89
|
+
"homepage": "https://github.com/hachej/boring-ui#readme",
|
|
90
|
+
"bugs": {
|
|
91
|
+
"url": "https://github.com/hachej/boring-ui/issues"
|
|
92
|
+
},
|
|
93
|
+
"publishConfig": {
|
|
94
|
+
"access": "public",
|
|
95
|
+
"registry": "https://registry.npmjs.org/"
|
|
96
|
+
},
|
|
97
|
+
"engines": {
|
|
98
|
+
"node": ">=22.19.0"
|
|
99
|
+
}
|
|
100
|
+
}
|