@devaccel-labs/app-comments-toolbar 0.0.0-stage → 1.0.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 +19 -2
- package/dist/chunks/chunk-CSKXLF33.js +153 -0
- package/dist/chunks/chunk-HUM5FQII.js +160 -0
- package/dist/chunks/render-page-76MLIT6M.js +1421 -0
- package/dist/chunks/runtime-JX75TKER.js +9267 -0
- package/dist/chunks/screenshot-66ARTBGR.js +12 -0
- package/dist/react.d.ts +1 -0
- package/dist/react.js +91 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +87 -0
- package/dist/types/compiler/annotate.d.ts +10 -0
- package/dist/types/compiler/enabled.d.ts +14 -0
- package/dist/types/compiler/index.d.ts +2 -0
- package/dist/types/core/index.d.ts +2 -0
- package/dist/types/core/resolve.d.ts +29 -0
- package/dist/types/core/types.d.ts +65 -0
- package/dist/types/react.d.ts +2 -0
- package/dist/types/runtime/attachments.d.ts +73 -0
- package/dist/types/runtime/capture-ui.d.ts +35 -0
- package/dist/types/runtime/capture.d.ts +30 -0
- package/dist/types/runtime/client.d.ts +220 -0
- package/dist/types/runtime/dashboard.d.ts +28 -0
- package/dist/types/runtime/details.d.ts +43 -0
- package/dist/types/runtime/dock.d.ts +38 -0
- package/dist/types/runtime/edits.d.ts +20 -0
- package/dist/types/runtime/format.d.ts +15 -0
- package/dist/types/runtime/freshness.d.ts +18 -0
- package/dist/types/runtime/handoff.d.ts +15 -0
- package/dist/types/runtime/icons.d.ts +40 -0
- package/dist/types/runtime/idle.d.ts +6 -0
- package/dist/types/runtime/index.d.ts +2 -0
- package/dist/types/runtime/loader.d.ts +14 -0
- package/dist/types/runtime/members.d.ts +11 -0
- package/dist/types/runtime/mentions.d.ts +17 -0
- package/dist/types/runtime/placement.d.ts +43 -0
- package/dist/types/runtime/record.d.ts +33 -0
- package/dist/types/runtime/redact.d.ts +10 -0
- package/dist/types/runtime/related.d.ts +11 -0
- package/dist/types/runtime/render-page.d.ts +1 -0
- package/dist/types/runtime/replay.d.ts +42 -0
- package/dist/types/runtime/runtime-version.d.ts +7 -0
- package/dist/types/runtime/runtime.d.ts +8 -0
- package/dist/types/runtime/screenshot.d.ts +12 -0
- package/dist/types/runtime/session.d.ts +25 -0
- package/dist/types/runtime/styles.d.ts +5 -0
- package/dist/types/runtime/sync.d.ts +25 -0
- package/dist/types/runtime/toolbar.d.ts +22 -0
- package/dist/types/server/index.d.ts +14 -0
- package/dist/types/server.d.ts +2 -0
- package/dist/types/vite/plugin.d.ts +20 -0
- package/dist/types/vite.d.ts +1 -0
- package/dist/vite.d.ts +1 -0
- package/dist/vite.js +173 -0
- package/package.json +52 -4
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @devaccel-labs/app-comments-toolbar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The in-app comment toolbar that host apps (Officers Assist, JebelAI) load at runtime from the App comments
|
|
4
|
+
worker as `/v1/toolbar/1.js`. Ported from the comment-service repo (`packages/core` and `packages/react`) on
|
|
5
|
+
2026-10-12; this is now the source of record for the runtime.
|
|
6
|
+
|
|
7
|
+
- `src/core`: anchor model and resolution (`resolveAnchor`), DOM-free.
|
|
8
|
+
- `src/runtime`: the React runtime (`runtime.tsx` is the bundle entry), element capture and placement, sync,
|
|
9
|
+
attachments, screenshots, styles. `loader.tsx` is the host-side `<CommentToolbar>`.
|
|
10
|
+
- `src/compiler`, `src/vite`, `src/server`: the Vite plugin that stamps components with `data-cs-cmp` /
|
|
11
|
+
`data-cs-file`, and the same-origin session route. Entry points `src/react.ts`, `src/vite.ts`, `src/server.ts`.
|
|
12
|
+
- Worker bundle: `bun scripts/app-comments/build-toolbar.mjs` writes `workers/app-comments/toolbar-dist/v1/toolbar/`
|
|
13
|
+
(checked in; see `docs/policies/generated-files-policy.md`). `bun scripts/dev/dev-app-comments.mjs` builds it too.
|
|
14
|
+
- npm package: `bun run build` here writes `dist/` (vite, react with the runtime bundled as fallback, server, and
|
|
15
|
+
declarations); publish with `npm publish` from this directory. Host apps install it and use
|
|
16
|
+
`@devaccel-labs/app-comments-toolbar/vite`, `/react` and `/server`; the `COMMENT_SERVICE_*` env names are unchanged.
|
|
17
|
+
- Tests: `tests/unit/app-comments-toolbar-*.spec.ts`.
|
|
18
|
+
|
|
19
|
+
The bundle has no version negotiation with host apps: `/v1` responses and the runtime's storage keys must stay
|
|
20
|
+
compatible with whatever package version the hosts have installed.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// src/runtime/runtime-version.ts
|
|
2
|
+
var runtimeVersion = 1;
|
|
3
|
+
|
|
4
|
+
// src/runtime/handoff.ts
|
|
5
|
+
var key = "devaccel.comment.handoff";
|
|
6
|
+
var ttl = 30 * 60 * 1e3;
|
|
7
|
+
function readHandoff() {
|
|
8
|
+
try {
|
|
9
|
+
const value = JSON.parse(sessionStorage.getItem(key) ?? "null");
|
|
10
|
+
return value && Date.now() - value.at < ttl ? value : null;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function write(value) {
|
|
16
|
+
try {
|
|
17
|
+
if (value.invite || value.thread) sessionStorage.setItem(key, JSON.stringify(value));
|
|
18
|
+
else sessionStorage.removeItem(key);
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function linkParams() {
|
|
23
|
+
const url = new URL(window.location.href);
|
|
24
|
+
const hash = new URLSearchParams(url.hash.slice(1));
|
|
25
|
+
return {
|
|
26
|
+
url,
|
|
27
|
+
hash,
|
|
28
|
+
invite: url.searchParams.get("cs-invite") ?? hash.get("cs-invite"),
|
|
29
|
+
thread: url.searchParams.get("cs-thread") ?? hash.get("cs-thread")
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function captureHandoff() {
|
|
33
|
+
if (typeof window === "undefined") return;
|
|
34
|
+
const { invite, thread } = linkParams();
|
|
35
|
+
if (!invite && !thread) return;
|
|
36
|
+
write({
|
|
37
|
+
...readHandoff(),
|
|
38
|
+
...invite ? { invite } : {},
|
|
39
|
+
...thread ? { thread } : {},
|
|
40
|
+
at: Date.now()
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function dropHandoff(part) {
|
|
44
|
+
const current = readHandoff();
|
|
45
|
+
if (!current) return;
|
|
46
|
+
const { [part]: _dropped, ...rest } = current;
|
|
47
|
+
write(rest);
|
|
48
|
+
}
|
|
49
|
+
function stripHandoffFromUrl() {
|
|
50
|
+
const { url, hash } = linkParams();
|
|
51
|
+
const inQuery = url.searchParams.has("cs-invite") || url.searchParams.has("cs-thread");
|
|
52
|
+
const inHash = hash.has("cs-invite") || hash.has("cs-thread");
|
|
53
|
+
if (!inQuery && !inHash) return;
|
|
54
|
+
url.searchParams.delete("cs-invite");
|
|
55
|
+
url.searchParams.delete("cs-thread");
|
|
56
|
+
if (inHash) url.hash = "";
|
|
57
|
+
history.replaceState(history.state, "", url);
|
|
58
|
+
}
|
|
59
|
+
function linkWith(href, name, value) {
|
|
60
|
+
const url = new URL(href, window.location.origin);
|
|
61
|
+
url.searchParams.delete("cs-invite");
|
|
62
|
+
url.searchParams.delete("cs-thread");
|
|
63
|
+
url.hash = `${name}=${encodeURIComponent(value)}`;
|
|
64
|
+
return url.toString();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/runtime/idle.ts
|
|
68
|
+
var FALLBACK_SLICE_MS = 8;
|
|
69
|
+
function whenIdle(task, timeout) {
|
|
70
|
+
if (typeof requestIdleCallback === "function") {
|
|
71
|
+
const id2 = requestIdleCallback(task, { timeout });
|
|
72
|
+
return () => cancelIdleCallback(id2);
|
|
73
|
+
}
|
|
74
|
+
const id = setTimeout(
|
|
75
|
+
() => {
|
|
76
|
+
const end = performance.now() + FALLBACK_SLICE_MS;
|
|
77
|
+
task({ didTimeout: false, timeRemaining: () => Math.max(0, end - performance.now()) });
|
|
78
|
+
},
|
|
79
|
+
Math.min(timeout, 200)
|
|
80
|
+
);
|
|
81
|
+
return () => clearTimeout(id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/runtime/session.ts
|
|
85
|
+
var EXPIRY_MARGIN_MS = 6e4;
|
|
86
|
+
var UNKNOWN_EXPIRY_MS = 5 * 6e4;
|
|
87
|
+
function readJwt(token) {
|
|
88
|
+
try {
|
|
89
|
+
const part = token.split(".")[1];
|
|
90
|
+
if (!part) return null;
|
|
91
|
+
const pad = part.length % 4 === 0 ? "" : "=".repeat(4 - part.length % 4);
|
|
92
|
+
return JSON.parse(atob(part.replaceAll("-", "+").replaceAll("_", "/") + pad));
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function sessionFrom(payload) {
|
|
98
|
+
const value = payload;
|
|
99
|
+
if (!value?.enabled || !value.apiUrl || !value.project || !value.environment || !value.hostToken)
|
|
100
|
+
return null;
|
|
101
|
+
return {
|
|
102
|
+
apiUrl: value.apiUrl,
|
|
103
|
+
project: value.project,
|
|
104
|
+
environment: value.environment,
|
|
105
|
+
hostToken: value.hostToken
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function fetchSession(path) {
|
|
109
|
+
const response = await fetch(path, {
|
|
110
|
+
headers: { accept: "application/json", "x-comment-service-origin": window.location.origin },
|
|
111
|
+
cache: "no-store"
|
|
112
|
+
});
|
|
113
|
+
return response.json();
|
|
114
|
+
}
|
|
115
|
+
function sessionSource(path, load = fetchSession, now = Date.now) {
|
|
116
|
+
let kept = null;
|
|
117
|
+
let pending = null;
|
|
118
|
+
return ({ refresh = false } = {}) => {
|
|
119
|
+
if (!refresh && kept && now() < kept.until) return Promise.resolve(kept.session);
|
|
120
|
+
if (pending) return pending;
|
|
121
|
+
pending = load(path).then((payload) => {
|
|
122
|
+
const session = sessionFrom(payload);
|
|
123
|
+
const expires = session ? readJwt(session.hostToken)?.exp : void 0;
|
|
124
|
+
const until = !session ? Infinity : expires ? expires * 1e3 - EXPIRY_MARGIN_MS : now() + UNKNOWN_EXPIRY_MS;
|
|
125
|
+
kept = { session, until };
|
|
126
|
+
return session;
|
|
127
|
+
}).finally(() => {
|
|
128
|
+
pending = null;
|
|
129
|
+
});
|
|
130
|
+
return pending;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
var sources = /* @__PURE__ */ new Map();
|
|
134
|
+
function sessionFor(path) {
|
|
135
|
+
let source = sources.get(path);
|
|
136
|
+
if (!source) {
|
|
137
|
+
source = sessionSource(path);
|
|
138
|
+
sources.set(path, source);
|
|
139
|
+
}
|
|
140
|
+
return source;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export {
|
|
144
|
+
readHandoff,
|
|
145
|
+
captureHandoff,
|
|
146
|
+
dropHandoff,
|
|
147
|
+
stripHandoffFromUrl,
|
|
148
|
+
linkWith,
|
|
149
|
+
whenIdle,
|
|
150
|
+
runtimeVersion,
|
|
151
|
+
readJwt,
|
|
152
|
+
sessionFor
|
|
153
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/runtime/screenshot.ts
|
|
2
|
+
var MAX_WIDTH = 1200;
|
|
3
|
+
var BROWSERS = [
|
|
4
|
+
["Edge", /Edg\/(\d+)/],
|
|
5
|
+
["Firefox", /Firefox\/(\d+)/],
|
|
6
|
+
["Chrome", /Chrome\/(\d+)/],
|
|
7
|
+
["Safari", /Version\/(\d+)[\d.]* .*Safari/]
|
|
8
|
+
];
|
|
9
|
+
var SYSTEMS = [
|
|
10
|
+
["iOS", /iPhone|iPad/],
|
|
11
|
+
["Android", /Android/],
|
|
12
|
+
["macOS", /Mac OS X/],
|
|
13
|
+
["Windows", /Windows/],
|
|
14
|
+
["Linux", /Linux/]
|
|
15
|
+
];
|
|
16
|
+
function browserLabel(agent) {
|
|
17
|
+
let browser;
|
|
18
|
+
for (const [name, pattern] of BROWSERS) {
|
|
19
|
+
const version = agent.match(pattern)?.[1];
|
|
20
|
+
if (version) {
|
|
21
|
+
browser = `${name} ${version}`;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const system = SYSTEMS.find(([, pattern]) => pattern.test(agent))?.[0];
|
|
26
|
+
return [browser, system].filter(Boolean).join(" \xB7 ") || void 0;
|
|
27
|
+
}
|
|
28
|
+
function describeCapture(url) {
|
|
29
|
+
return {
|
|
30
|
+
url,
|
|
31
|
+
viewport: { width: document.documentElement.clientWidth, height: window.innerHeight },
|
|
32
|
+
browser: browserLabel(navigator.userAgent)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function roundedRect(ctx, x, y, w, h, r) {
|
|
36
|
+
ctx.moveTo(x + r, y);
|
|
37
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
38
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
39
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
40
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
41
|
+
ctx.closePath();
|
|
42
|
+
}
|
|
43
|
+
async function renderViewport(scale, timeout = 6e3) {
|
|
44
|
+
const { domToCanvas } = await import("./render-page-76MLIT6M.js");
|
|
45
|
+
return domToCanvas(document.documentElement, {
|
|
46
|
+
width: document.documentElement.clientWidth,
|
|
47
|
+
height: window.innerHeight,
|
|
48
|
+
scale,
|
|
49
|
+
timeout,
|
|
50
|
+
backgroundColor: getComputedStyle(document.body).backgroundColor || "#ffffff",
|
|
51
|
+
filter: (node) => !(node instanceof Element && node.id === "cs-root"),
|
|
52
|
+
style: {
|
|
53
|
+
transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)`,
|
|
54
|
+
transformOrigin: "0 0"
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function outline(ctx, width, height, ring, scale) {
|
|
59
|
+
const radius = Math.min(12 * scale, ring.w / 2, ring.h / 2);
|
|
60
|
+
ctx.beginPath();
|
|
61
|
+
ctx.rect(0, 0, width, height);
|
|
62
|
+
roundedRect(ctx, ring.x, ring.y, ring.w, ring.h, radius);
|
|
63
|
+
ctx.fillStyle = "rgba(15, 15, 20, 0.22)";
|
|
64
|
+
ctx.fill("evenodd");
|
|
65
|
+
ctx.beginPath();
|
|
66
|
+
roundedRect(ctx, ring.x, ring.y, ring.w, ring.h, radius);
|
|
67
|
+
ctx.lineWidth = 3 * scale;
|
|
68
|
+
ctx.strokeStyle = "#007aff";
|
|
69
|
+
ctx.stroke();
|
|
70
|
+
}
|
|
71
|
+
async function capturePage(highlight) {
|
|
72
|
+
const scale = Math.min(window.devicePixelRatio || 1, 2);
|
|
73
|
+
let page;
|
|
74
|
+
try {
|
|
75
|
+
page = await renderViewport(scale);
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const width = document.documentElement.clientWidth;
|
|
80
|
+
const outScale = Math.min(scale, 1600 / width);
|
|
81
|
+
const out = document.createElement("canvas");
|
|
82
|
+
out.width = Math.round(width * outScale);
|
|
83
|
+
out.height = Math.round(window.innerHeight * outScale);
|
|
84
|
+
const ctx = out.getContext("2d");
|
|
85
|
+
if (!ctx) return null;
|
|
86
|
+
ctx.drawImage(page, 0, 0, out.width, out.height);
|
|
87
|
+
const rect = highlight?.getBoundingClientRect();
|
|
88
|
+
if (rect && rect.width > 0 && rect.bottom > 0 && rect.top < window.innerHeight) {
|
|
89
|
+
const pad = 6;
|
|
90
|
+
outline(
|
|
91
|
+
ctx,
|
|
92
|
+
out.width,
|
|
93
|
+
out.height,
|
|
94
|
+
{
|
|
95
|
+
x: (rect.left - pad) * outScale,
|
|
96
|
+
y: (rect.top - pad) * outScale,
|
|
97
|
+
w: (rect.width + pad * 2) * outScale,
|
|
98
|
+
h: (rect.height + pad * 2) * outScale
|
|
99
|
+
},
|
|
100
|
+
outScale
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return new Promise((resolve) => out.toBlob((blob) => resolve(blob), "image/jpeg", 0.85));
|
|
104
|
+
}
|
|
105
|
+
async function captureAround(element) {
|
|
106
|
+
const rect = element.getBoundingClientRect();
|
|
107
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
108
|
+
const viewportHeight = window.innerHeight;
|
|
109
|
+
if (rect.width < 1 || rect.bottom <= 0 || rect.top >= viewportHeight || rect.right <= 0 || rect.left >= viewportWidth)
|
|
110
|
+
return null;
|
|
111
|
+
const scale = Math.min(window.devicePixelRatio || 1, 2);
|
|
112
|
+
let page;
|
|
113
|
+
try {
|
|
114
|
+
page = await renderViewport(scale);
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const width = Math.min(viewportWidth, Math.max(rect.width + 200, 760));
|
|
119
|
+
const height = Math.min(viewportHeight, Math.max(rect.height + 200, 420));
|
|
120
|
+
const x = Math.max(0, Math.min(rect.left + rect.width / 2 - width / 2, viewportWidth - width));
|
|
121
|
+
const y = Math.max(0, Math.min(rect.top + rect.height / 2 - height / 2, viewportHeight - height));
|
|
122
|
+
const out = document.createElement("canvas");
|
|
123
|
+
const outScale = Math.min(scale, MAX_WIDTH / width);
|
|
124
|
+
out.width = Math.round(width * outScale);
|
|
125
|
+
out.height = Math.round(height * outScale);
|
|
126
|
+
const ctx = out.getContext("2d");
|
|
127
|
+
if (!ctx) return null;
|
|
128
|
+
ctx.drawImage(
|
|
129
|
+
page,
|
|
130
|
+
x * scale,
|
|
131
|
+
y * scale,
|
|
132
|
+
width * scale,
|
|
133
|
+
height * scale,
|
|
134
|
+
0,
|
|
135
|
+
0,
|
|
136
|
+
out.width,
|
|
137
|
+
out.height
|
|
138
|
+
);
|
|
139
|
+
const pad = 6;
|
|
140
|
+
outline(
|
|
141
|
+
ctx,
|
|
142
|
+
out.width,
|
|
143
|
+
out.height,
|
|
144
|
+
{
|
|
145
|
+
x: (rect.left - x - pad) * outScale,
|
|
146
|
+
y: (rect.top - y - pad) * outScale,
|
|
147
|
+
w: (rect.width + pad * 2) * outScale,
|
|
148
|
+
h: (rect.height + pad * 2) * outScale
|
|
149
|
+
},
|
|
150
|
+
outScale
|
|
151
|
+
);
|
|
152
|
+
return out.toDataURL("image/jpeg", 0.82);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
describeCapture,
|
|
157
|
+
renderViewport,
|
|
158
|
+
capturePage,
|
|
159
|
+
captureAround
|
|
160
|
+
};
|