@devaccel-labs/comment-service 0.5.1

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.
@@ -0,0 +1,2 @@
1
+ export { handleSessionRequest, readCommentServiceConfig } from "./types/server/index.js"
2
+ export type { CommentServiceConfig } from "./types/server/index.js"
package/dist/server.js ADDED
@@ -0,0 +1,62 @@
1
+ // ../server/dist/index.js
2
+ function readCommentServiceConfig(env = process.env) {
3
+ const url = env.COMMENT_SERVICE_URL?.replace(/\/$/, "");
4
+ const project = env.COMMENT_SERVICE_PROJECT;
5
+ const environment = env.COMMENT_SERVICE_ENVIRONMENT;
6
+ const secret = env.COMMENT_SERVICE_SECRET;
7
+ if (!url || !project || !environment || !secret)
8
+ return null;
9
+ return { url, project, environment, secret };
10
+ }
11
+ function requestOrigin(request) {
12
+ const url = new URL(request.url);
13
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
14
+ if (!host || host.includes("/"))
15
+ return url.origin;
16
+ const proto = (request.headers.get("x-forwarded-proto") ?? url.protocol.replace(":", "")).split(",")[0].trim();
17
+ return `${proto}://${host}`;
18
+ }
19
+ async function handleSessionRequest(request, config = readCommentServiceConfig()) {
20
+ const headers = { "cache-control": "no-store" };
21
+ if (!config)
22
+ return Response.json({ enabled: false }, { headers });
23
+ const origin = requestOrigin(request);
24
+ let response;
25
+ try {
26
+ response = await fetch(`${config.url}/v1/host/session`, {
27
+ method: "POST",
28
+ headers: {
29
+ authorization: `Bearer ${config.secret}`,
30
+ "content-type": "application/json"
31
+ },
32
+ body: JSON.stringify({
33
+ project: config.project,
34
+ environment: config.environment,
35
+ origin
36
+ })
37
+ });
38
+ } catch {
39
+ return Response.json({ enabled: false, error: "Comment service is unreachable." }, { status: 502, headers });
40
+ }
41
+ if (!response.ok) {
42
+ const payload2 = await response.json().catch(() => null);
43
+ return Response.json({
44
+ enabled: false,
45
+ error: payload2?.error?.message ?? "Comment service rejected this environment.",
46
+ origin,
47
+ status: response.status
48
+ }, { status: 502, headers });
49
+ }
50
+ const payload = await response.json();
51
+ return Response.json({
52
+ enabled: true,
53
+ apiUrl: config.url,
54
+ project: config.project,
55
+ environment: config.environment,
56
+ hostToken: payload.token
57
+ }, { headers });
58
+ }
59
+ export {
60
+ handleSessionRequest,
61
+ readCommentServiceConfig
62
+ };
@@ -0,0 +1,7 @@
1
+ export declare function toProjectPath(filename: string, root: string): string;
2
+ /**
3
+ * Stamp the root DOM node of each component with a stable file + component name.
4
+ * Line numbers are intentionally not included: moving the component in the file
5
+ * or on the page must not change the stamp.
6
+ */
7
+ export declare function annotateSource(code: string, filename: string, root: string): string;
@@ -0,0 +1 @@
1
+ export { annotateSource, toProjectPath } from "./annotate.js";
@@ -0,0 +1,2 @@
1
+ export { describeAnchor, dropAncestors, resolveAnchor, validateAnchor } from "./resolve.js";
2
+ export type { Anchor, Candidate, Resolution, ResolutionStatus } from "./types.js";
@@ -0,0 +1,19 @@
1
+ import type { Anchor, Candidate, Resolution } from "./types.js";
2
+ /** Drop elements that contain another match, so a wrapping div doesn't steal the button. */
3
+ export declare function dropAncestors(candidates: Candidate[]): Candidate[];
4
+ /**
5
+ * Resolve a stored anchor against the elements currently on the route.
6
+ *
7
+ * Order is deliberate:
8
+ * 1. An explicit id (`data-cs-anchor`, `data-testid`, stable `id`) wins.
9
+ * 2. Otherwise the match is component file + component name + role + label.
10
+ * 3. Sibling index and pixel position are ignored, so inserting, reordering,
11
+ * or moving the element (including out of a sheet) does not retarget the comment.
12
+ * 4. A tie is ambiguous. We do not fall back to "the first one" or the old index.
13
+ * 5. A unique component whose label changed is "drifted": still shown, marked uncertain.
14
+ * 6. A match that exists but is inside a closed dialog is "hidden", not deleted.
15
+ * 7. No match is "unavailable". The thread stays in the inbox.
16
+ */
17
+ export declare function resolveAnchor(anchor: Anchor, candidates: Candidate[]): Resolution;
18
+ export declare function describeAnchor(anchor: Anchor): string;
19
+ export declare function validateAnchor(input: unknown): Anchor | null;
@@ -0,0 +1,65 @@
1
+ /** Stable identity for a commented element. Pixel and DOM-index position are not part of the match. */
2
+ export type Anchor = {
3
+ v: 1;
4
+ /** Pathname only, no query or hash. */
5
+ route: string;
6
+ /**
7
+ * Dialog or sheet the element lived in when the comment was written.
8
+ * Used to explain a hidden thread. Not required to match after the element moves out of the overlay.
9
+ */
10
+ overlay: {
11
+ role: string;
12
+ name: string;
13
+ } | null;
14
+ /** Opt-in stable ids: data-cs-anchor, data-testid, or a human id. Highest priority. */
15
+ explicit: Array<{
16
+ attr: string;
17
+ value: string;
18
+ }>;
19
+ /** React component that owns the element. File path is relative and has no line number. */
20
+ component: {
21
+ name: string;
22
+ file: string | null;
23
+ } | null;
24
+ /** Component names from the tree, outer to inner. Diagnostic. */
25
+ stack: string[];
26
+ role: string | null;
27
+ accName: string | null;
28
+ text: string | null;
29
+ tag: string;
30
+ /** Sibling path inside the component. Stored for agents, never used to choose a match. */
31
+ intra: string | null;
32
+ };
33
+ export type Candidate = {
34
+ key: string;
35
+ route: string;
36
+ visible: boolean;
37
+ /** Index path from the document body. Used only to tell ancestors from descendants. */
38
+ path: string;
39
+ componentRoot: boolean;
40
+ overlay: {
41
+ role: string;
42
+ name: string;
43
+ } | null;
44
+ explicit: Array<{
45
+ attr: string;
46
+ value: string;
47
+ }>;
48
+ component: {
49
+ name: string;
50
+ file: string | null;
51
+ } | null;
52
+ stack: string[];
53
+ role: string | null;
54
+ accName: string | null;
55
+ text: string | null;
56
+ tag: string;
57
+ intra: string | null;
58
+ };
59
+ export type ResolutionStatus = "pinned" | "drifted" | "hidden" | "ambiguous" | "unavailable";
60
+ export type Resolution = {
61
+ status: ResolutionStatus;
62
+ key: string | null;
63
+ score: number;
64
+ reason: string;
65
+ };
@@ -0,0 +1,44 @@
1
+ import { type ClipboardEvent } from "react";
2
+ import { type Attachment, type Session } from "./client.js";
3
+ export type Pending = {
4
+ key: string;
5
+ name: string;
6
+ size: number;
7
+ contentType: string;
8
+ preview: string | null;
9
+ status: "uploading" | "ready" | "failed";
10
+ id?: string;
11
+ error?: string;
12
+ };
13
+ export declare function formatSize(bytes: number): string;
14
+ /** Files attached to one draft. Each upload starts immediately so posting doesn't wait on the network. */
15
+ export declare function useAttachments(session: Session | null, memberToken: string | null, onError: (message: string) => void): {
16
+ items: Pending[];
17
+ add: (files: Array<{
18
+ blob: Blob;
19
+ name: string;
20
+ }>) => void;
21
+ remove: (key: string) => void;
22
+ reset: () => void;
23
+ ids: string[];
24
+ uploading: boolean;
25
+ };
26
+ export declare function filesFromClipboard(event: ClipboardEvent): {
27
+ blob: Blob;
28
+ name: string;
29
+ }[];
30
+ export declare function AttachmentTray({ items, onRemove }: {
31
+ items: Pending[];
32
+ onRemove: (key: string) => void;
33
+ }): import("react").JSX.Element | null;
34
+ export declare function AttachButtons({ onFiles, onSnapshot, capturing, }: {
35
+ onFiles: (files: Array<{
36
+ blob: Blob;
37
+ name: string;
38
+ }>) => void;
39
+ onSnapshot: () => void;
40
+ capturing: boolean;
41
+ }): import("react").JSX.Element;
42
+ export declare function MessageAttachments({ attachments }: {
43
+ attachments?: Attachment[];
44
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,13 @@
1
+ import type { Anchor, Candidate } from "../core/index.js";
2
+ export declare function candidateFromElement(element: Element, route: string, key: string): Candidate;
3
+ export declare function captureAnchor(element: Element, route?: string): Anchor;
4
+ export declare function collectCandidates(route?: string): {
5
+ element: Element;
6
+ candidate: Candidate;
7
+ }[];
8
+ export declare function resolveInDocument(anchor: Anchor, route?: string): {
9
+ resolution: import("../core/index.js").Resolution;
10
+ element: Element | null;
11
+ };
12
+ export declare function elementAt(x: number, y: number): Element | null;
13
+ export declare function describeElement(element: Element): string;
@@ -0,0 +1,114 @@
1
+ import type { Anchor } from "../core/index.js";
2
+ export type SessionPayload = {
3
+ enabled: boolean;
4
+ apiUrl?: string;
5
+ project?: string;
6
+ environment?: string;
7
+ hostToken?: string;
8
+ error?: string;
9
+ };
10
+ export type Mentioned = {
11
+ id: string;
12
+ name: string;
13
+ };
14
+ export type Attachment = {
15
+ id: string;
16
+ name: string;
17
+ contentType: string;
18
+ size: number;
19
+ url: string;
20
+ };
21
+ export type CommentMessage = {
22
+ id: string;
23
+ body: string;
24
+ author: string;
25
+ createdAt: string;
26
+ mentions?: Mentioned[];
27
+ attachments?: Attachment[];
28
+ };
29
+ export type Member = {
30
+ id: string;
31
+ name: string;
32
+ email: string;
33
+ };
34
+ export type Delivery = {
35
+ emailed: Mentioned[];
36
+ skipped: Array<Mentioned & {
37
+ reason: "rate_limited" | "muted" | "disabled" | string;
38
+ }>;
39
+ };
40
+ export type Profile = {
41
+ name: string;
42
+ email: string | null;
43
+ mentionEmails: boolean;
44
+ };
45
+ export type PostExtras = {
46
+ mentions?: string[];
47
+ screenshot?: string | null;
48
+ attachments?: string[];
49
+ };
50
+ export type Thread = {
51
+ id: string;
52
+ route: string;
53
+ anchor: Anchor;
54
+ status: "open" | "resolved";
55
+ createdBy: string;
56
+ createdAt: string;
57
+ updatedAt: string;
58
+ messages: CommentMessage[];
59
+ };
60
+ export type Session = {
61
+ apiUrl: string;
62
+ project: string;
63
+ environment: string;
64
+ hostToken: string;
65
+ };
66
+ export declare class ApiError extends Error {
67
+ readonly status: number;
68
+ readonly code: string;
69
+ constructor(status: number, code: string, message: string);
70
+ }
71
+ export declare function storageKey(session: Pick<Session, "apiUrl" | "project" | "environment">): string;
72
+ export declare function readJwt(token: string): {
73
+ exp?: number;
74
+ typ?: string;
75
+ name?: string;
76
+ email?: string;
77
+ } | null;
78
+ export declare function memberIsCurrent(token: string): boolean;
79
+ /** Sign in with the team passphrase, or with an invite link token instead. */
80
+ export declare function unlock(session: Session, displayName: string, email: string, secret: {
81
+ passphrase: string;
82
+ } | {
83
+ invite: string;
84
+ }): Promise<string>;
85
+ export declare function createInvite(session: Session, memberToken: string): Promise<{
86
+ invite: string;
87
+ invitedBy: string;
88
+ expiresAt: string;
89
+ }>;
90
+ export declare function getProfile(session: Session, memberToken: string): Promise<Profile>;
91
+ export declare function updateProfile(session: Session, memberToken: string, patch: {
92
+ displayName?: string;
93
+ email?: string;
94
+ mentionEmails?: boolean;
95
+ }): Promise<Profile & {
96
+ token: string;
97
+ }>;
98
+ export declare function uploadAttachment(session: Session, memberToken: string, file: Blob, name: string): Promise<Attachment>;
99
+ export declare function listMembers(session: Session, memberToken: string): Promise<Member[]>;
100
+ export declare function listThreads(session: Session, memberToken: string): Promise<Thread[]>;
101
+ export declare function createThread(session: Session, memberToken: string, anchor: Anchor, body: string, extras?: PostExtras): Promise<{
102
+ thread: Thread;
103
+ delivery?: Delivery;
104
+ }>;
105
+ export declare function replyToThread(session: Session, memberToken: string, threadId: string, body: string, extras?: PostExtras): Promise<{
106
+ message: CommentMessage;
107
+ delivery?: Delivery;
108
+ }>;
109
+ export declare function updateThread(session: Session, memberToken: string, threadId: string, patch: {
110
+ status?: "open" | "resolved";
111
+ anchor?: Anchor;
112
+ }): Promise<{
113
+ id: string;
114
+ }>;
@@ -0,0 +1,14 @@
1
+ import type { Anchor } from "../core/index.js";
2
+ import type { Delivery } from "./client.js";
3
+ export declare function initials(name: string): string;
4
+ export declare function colorFor(name: string): string;
5
+ export declare function ago(iso: string): string;
6
+ export declare function targetName(anchor: Anchor): string;
7
+ export type PlacedStatus = "pinned" | "drifted" | "hidden" | "ambiguous" | "unavailable" | "resolved";
8
+ /** Plain-language reason a thread isn't pinned, or null when there's nothing to explain. */
9
+ export declare function statusNote(status: PlacedStatus, anchor: Anchor, onThisRoute: boolean): {
10
+ text: string;
11
+ tone: string;
12
+ } | null;
13
+ /** One quiet line about who was emailed for a message, or null when nobody was mentioned. */
14
+ export declare function deliveryText(delivery: Delivery | undefined): string | null;
@@ -0,0 +1,18 @@
1
+ export declare const AddComment: () => import("react").JSX.Element;
2
+ export declare const Inbox: () => import("react").JSX.Element;
3
+ export declare const Eye: () => import("react").JSX.Element;
4
+ export declare const EyeOff: () => import("react").JSX.Element;
5
+ export declare const Lock: ({ size }: {
6
+ size?: number;
7
+ }) => import("react").JSX.Element;
8
+ export declare const Close: () => import("react").JSX.Element;
9
+ export declare const Send: () => import("react").JSX.Element;
10
+ export declare const Check: () => import("react").JSX.Element;
11
+ export declare const Target: () => import("react").JSX.Element;
12
+ export declare const Relink: () => import("react").JSX.Element;
13
+ export declare const Undo: () => import("react").JSX.Element;
14
+ export declare const Pencil: () => import("react").JSX.Element;
15
+ export declare const Cursor: () => import("react").JSX.Element;
16
+ export declare const Paperclip: () => import("react").JSX.Element;
17
+ export declare const Camera: () => import("react").JSX.Element;
18
+ export declare const FileIcon: () => import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export { captureAnchor, resolveInDocument } from "./capture.js";
2
+ export { CommentToolbar, type ToolbarPosition, type ToolbarTheme } from "./toolbar.js";
@@ -0,0 +1,17 @@
1
+ import { type TextareaHTMLAttributes } from "react";
2
+ import type { Member, Mentioned } from "./client.js";
3
+ type Props = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "value" | "onChange"> & {
4
+ value: string;
5
+ onValueChange: (next: string) => void;
6
+ members: Member[];
7
+ selfEmail?: string;
8
+ onMention: (member: Member) => void;
9
+ };
10
+ /** Members picked while writing whose `@Name` is still in the text. */
11
+ export declare function mentionsIn(text: string, picked: Map<string, Member>): Member[];
12
+ export declare function MessageText({ body, mentions }: {
13
+ body: string;
14
+ mentions?: Mentioned[];
15
+ }): import("react").JSX.Element;
16
+ export declare function MentionTextarea({ value, onValueChange, members, selfEmail, onMention, onKeyDown, ...rest }: Props): import("react").JSX.Element;
17
+ export {};
@@ -0,0 +1,7 @@
1
+ /** The whole visible page as a JPEG, toolbar left out. If `highlight` is on screen it's outlined. */
2
+ export declare function capturePage(highlight?: Element | null): Promise<Blob | null>;
3
+ /**
4
+ * Capture what the person sees around `element`, with the toolbar left out, the rest dimmed,
5
+ * and the element outlined. Returns a JPEG data URL, or null if the element isn't on screen or capture fails.
6
+ */
7
+ export declare function captureAround(element: Element): Promise<string | null>;
@@ -0,0 +1 @@
1
+ export declare const toolbarCss = "\n#cs-root {\n --cs-text: #1d1d1f;\n --cs-text-2: #6e6e73;\n --cs-text-3: #a1a1a6;\n --cs-glass: rgba(251, 251, 253, 0.8);\n --cs-card: rgba(255, 255, 255, 0.94);\n --cs-fill: rgba(0, 0, 0, 0.05);\n --cs-fill-2: rgba(0, 0, 0, 0.08);\n --cs-line: rgba(0, 0, 0, 0.08);\n --cs-edge: rgba(0, 0, 0, 0.1);\n --cs-accent: #007aff;\n --cs-accent-soft: rgba(0, 122, 255, 0.12);\n --cs-danger: #ff3b30;\n --cs-warn: #ff9500;\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.06);\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n pointer-events: none;\n margin: 0;\n padding: 0;\n border: 0;\n background: transparent;\n color: var(--cs-text);\n font: 13px/1.4 -apple-system, BlinkMacSystemFont, \"SF Pro Text\", \"Helvetica Neue\", Inter, system-ui, sans-serif;\n letter-spacing: -0.005em;\n -webkit-font-smoothing: antialiased;\n text-align: left;\n}\n#cs-root.cs-dark {\n --cs-text: #f5f5f7;\n --cs-text-2: #a1a1a6;\n --cs-text-3: #6e6e73;\n --cs-glass: rgba(40, 40, 44, 0.78);\n --cs-card: rgba(36, 36, 40, 0.96);\n --cs-fill: rgba(255, 255, 255, 0.07);\n --cs-fill-2: rgba(255, 255, 255, 0.12);\n --cs-line: rgba(255, 255, 255, 0.08);\n --cs-edge: rgba(255, 255, 255, 0.12);\n --cs-accent: #0a84ff;\n --cs-accent-soft: rgba(10, 132, 255, 0.2);\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 10px 30px rgba(0, 0, 0, 0.45);\n}\n#cs-root.cs-inside { position: absolute; }\n#cs-root, #cs-root * { box-sizing: border-box; }\n#cs-root h2, #cs-root p { margin: 0; }\n#cs-root button, #cs-root input, #cs-root textarea {\n font: inherit; color: inherit; letter-spacing: inherit; margin: 0;\n}\n#cs-root button { background: none; border: 0; padding: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }\n#cs-root button:disabled { cursor: default; opacity: 0.45; }\n#cs-root button:focus-visible, #cs-root [role=button]:focus-visible { outline: 2px solid var(--cs-accent); outline-offset: 2px; }\n#cs-root .cs-hit { pointer-events: auto; }\n#cs-root .cs-icon { display: block; flex: none; }\n\n/* Bar */\n#cs-root .cs-bar {\n position: absolute;\n bottom: 16px;\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 4px;\n border-radius: 999px;\n background: var(--cs-glass);\n -webkit-backdrop-filter: saturate(180%) blur(20px);\n backdrop-filter: saturate(180%) blur(20px);\n box-shadow: var(--cs-shadow);\n}\n#cs-root.cs-at-center .cs-bar { left: 50%; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-bar { left: 16px; }\n#cs-root.cs-at-right .cs-bar { right: 16px; }\n#cs-root .cs-tool {\n position: relative;\n width: 34px;\n height: 34px;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--cs-text);\n transition: background-color 0.15s ease, color 0.15s ease;\n}\n#cs-root .cs-tool:hover { background: var(--cs-fill); }\n#cs-root .cs-tool.is-on { background: var(--cs-fill-2); }\n#cs-root .cs-tool.is-accent { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-tool[data-tip]::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 10px);\n left: 50%;\n transform: translate(-50%, 2px);\n white-space: pre;\n padding: 4px 8px;\n border-radius: 7px;\n background: rgba(29, 29, 31, 0.92);\n color: #fff;\n font-size: 11.5px;\n font-weight: 500;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.12s ease, transform 0.12s ease;\n}\n#cs-root .cs-tool[data-tip]:hover::after { opacity: 1; transform: translate(-50%, 0); transition-delay: 0.35s; }\n#cs-root .cs-me { width: 34px; }\n#cs-root .cs-divider { width: 1px; height: 18px; background: var(--cs-line); margin: 0 3px; }\n#cs-root .cs-badge {\n position: absolute;\n top: 3px;\n right: 2px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--cs-accent);\n color: #fff;\n font-size: 10px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n box-shadow: 0 0 0 2px var(--cs-glass);\n}\n#cs-root .cs-bar-lock {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n height: 34px;\n padding: 0 14px 0 12px;\n border-radius: 999px;\n font-weight: 500;\n}\n#cs-root .cs-bar-lock:hover { background: var(--cs-fill); }\n#cs-root .cs-avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n border-radius: 999px;\n color: #fff;\n font-weight: 600;\n letter-spacing: 0;\n line-height: 1;\n user-select: none;\n}\n\n/* Cards */\n#cs-root .cs-card {\n position: absolute;\n background: var(--cs-card);\n -webkit-backdrop-filter: saturate(180%) blur(24px);\n backdrop-filter: saturate(180%) blur(24px);\n border-radius: 14px;\n box-shadow: var(--cs-shadow);\n}\n#cs-root .cs-unlock, #cs-root .cs-menu { bottom: 64px; }\n#cs-root.cs-at-center .cs-unlock, #cs-root.cs-at-center .cs-menu { left: 50%; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-unlock, #cs-root.cs-at-left .cs-menu { left: 16px; }\n#cs-root.cs-at-right .cs-unlock, #cs-root.cs-at-right .cs-menu { right: 16px; }\n#cs-root .cs-unlock { width: 280px; padding: 20px; text-align: center; }\n#cs-root .cs-unlock-icon {\n width: 40px; height: 40px; margin: 0 auto 10px; border-radius: 12px;\n display: flex; align-items: center; justify-content: center; background: var(--cs-fill); color: var(--cs-text-2);\n}\n#cs-root .cs-unlock h2 { font-size: 15px; font-weight: 600; }\n#cs-root .cs-sub { color: var(--cs-text-2); margin-top: 4px; }\n#cs-root .cs-unlock form { margin-top: 14px; display: grid; gap: 8px; text-align: left; }\n#cs-root .cs-field { display: grid; gap: 4px; }\n#cs-root .cs-field span { font-size: 12px; color: var(--cs-text-2); padding-left: 2px; }\n#cs-root input, #cs-root textarea {\n width: 100%;\n border: 0;\n border-radius: 9px;\n background: var(--cs-fill);\n padding: 8px 10px;\n outline: none;\n transition: box-shadow 0.15s ease, background-color 0.15s ease;\n}\n#cs-root input:focus, #cs-root textarea:focus { background: transparent; box-shadow: 0 0 0 1px var(--cs-accent), 0 0 0 4px var(--cs-accent-soft); }\n#cs-root textarea { resize: none; }\n#cs-root .cs-error { color: var(--cs-danger); font-size: 12px; }\n#cs-root .cs-pad { padding: 6px 14px 0; }\n#cs-root .cs-hint { color: var(--cs-text-3); font-size: 11.5px; }\n\n/* Buttons */\n#cs-root .cs-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 5px;\n height: 28px;\n padding: 0 11px;\n border-radius: 8px;\n background: var(--cs-fill);\n font-size: 12.5px;\n font-weight: 500;\n transition: background-color 0.15s ease;\n}\n#cs-root .cs-button:hover { background: var(--cs-fill-2); }\n#cs-root .cs-button.is-primary { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-button.is-primary:hover { filter: brightness(1.06); }\n#cs-root .cs-button.is-danger { background: var(--cs-danger); color: #fff; }\n#cs-root .cs-button.is-wide { width: 100%; height: 32px; margin-top: 4px; }\n#cs-root .cs-icon-button {\n width: 26px; height: 26px; border-radius: 999px; flex: none;\n display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-2);\n}\n#cs-root .cs-icon-button:hover { background: var(--cs-fill); color: var(--cs-text); }\n#cs-root .cs-send {\n width: 26px; height: 26px; border-radius: 999px; flex: none;\n display: inline-flex; align-items: center; justify-content: center; background: var(--cs-accent); color: #fff;\n}\n#cs-root .cs-send:disabled { background: var(--cs-fill-2); color: var(--cs-text-3); opacity: 1; }\n\n/* Menu */\n#cs-root .cs-menu { width: 264px; padding: 6px; }\n#cs-root .cs-me-row { display: flex; align-items: center; gap: 10px; padding: 8px 8px 10px; }\n#cs-root .cs-me-name { display: flex; align-items: center; gap: 4px; min-width: 0; }\n#cs-root .cs-me-name strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-rename { display: flex; align-items: center; gap: 4px; flex: 1; }\n#cs-root .cs-rename input { padding: 5px 8px; }\n#cs-root .cs-sep { height: 1px; background: var(--cs-line); margin: 4px 8px; }\n#cs-root .cs-item {\n display: flex; align-items: center; justify-content: space-between; gap: 10px;\n width: 100%; min-height: 32px; padding: 0 8px; border-radius: 8px; text-align: left;\n}\n#cs-root button.cs-item:hover { background: var(--cs-fill); }\n#cs-root .cs-item.is-danger { color: var(--cs-danger); }\n#cs-root .cs-switch { position: relative; width: 32px; height: 20px; border-radius: 999px; background: var(--cs-fill-2); transition: background-color 0.2s ease; flex: none; }\n#cs-root .cs-switch::after {\n content: \"\"; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 999px;\n background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); transition: transform 0.2s ease;\n}\n#cs-root .cs-switch.is-on { background: #34c759; }\n#cs-root .cs-switch.is-on::after { transform: translateX(12px); }\n#cs-root .cs-segment { display: inline-flex; padding: 2px; border-radius: 8px; background: var(--cs-fill); }\n#cs-root .cs-segment button { padding: 2px 8px; border-radius: 6px; font-size: 12px; color: var(--cs-text-2); }\n#cs-root .cs-segment button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); font-weight: 500; }\n#cs-root .cs-menu .cs-hint { padding: 6px 8px 4px; }\n#cs-root .cs-invite { padding: 2px 8px 6px; }\n#cs-root .cs-invite input { font: 11.5px ui-monospace, SFMono-Regular, Menlo, monospace; padding: 6px 8px; }\n#cs-root .cs-invite .cs-hint { padding: 4px 0 0; }\n#cs-root .cs-confirm { padding: 10px; }\n#cs-root .cs-confirm h2 { font-size: 14px; font-weight: 600; }\n#cs-root .cs-row-end { display: flex; justify-content: flex-end; gap: 6px; margin-top: 12px; }\n\n/* Sheet */\n#cs-root .cs-sheet {\n top: 12px; right: 12px; max-height: calc(100% - 88px); width: min(360px, calc(100vw - 24px));\n display: flex; flex-direction: column; overflow: hidden;\n}\n#cs-root .cs-sheet-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 10px 4px 16px; }\n#cs-root .cs-sheet-head h2 { font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }\n#cs-root .cs-tabs { display: flex; gap: 2px; margin: 4px 12px 6px; padding: 2px; border-radius: 9px; background: var(--cs-fill); }\n#cs-root .cs-tabs button {\n flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 5px;\n height: 26px; border-radius: 7px; font-size: 12px; font-weight: 500; color: var(--cs-text-2);\n}\n#cs-root .cs-tabs button.is-on { background: var(--cs-card); color: var(--cs-text); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12); }\n#cs-root .cs-count { font-size: 11px; color: var(--cs-text-3); font-variant-numeric: tabular-nums; }\n#cs-root .cs-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 2px 6px 8px; overscroll-behavior: contain; }\n#cs-root .cs-group { padding: 10px 10px 4px; font-size: 11px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.04em; }\n#cs-root .cs-empty { padding: 36px 16px; text-align: center; color: var(--cs-text-2); display: grid; gap: 12px; justify-items: center; }\n#cs-root .cs-thread { border-radius: 10px; }\n#cs-root .cs-thread + .cs-thread { margin-top: 1px; }\n#cs-root .cs-thread.is-open { background: var(--cs-fill); }\n#cs-root .cs-thread-main { display: flex; gap: 10px; padding: 10px; border-radius: 10px; cursor: pointer; }\n#cs-root .cs-thread:not(.is-open) .cs-thread-main:hover { background: var(--cs-fill); }\n#cs-root .cs-thread-body { min-width: 0; flex: 1; }\n#cs-root .cs-meta { display: flex; align-items: center; gap: 6px; font-size: 12.5px; }\n#cs-root .cs-meta strong { font-weight: 600; }\n#cs-root .cs-meta time { color: var(--cs-text-3); font-size: 12px; }\n#cs-root .cs-text { margin-top: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }\n#cs-root .cs-target { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); }\n#cs-root .cs-note { display: inline-flex; align-items: center; gap: 5px; font-weight: 500; }\n#cs-root .cs-note::before { content: \"\"; width: 6px; height: 6px; border-radius: 999px; background: currentColor; }\n#cs-root .cs-note.is-quiet { color: var(--cs-text-2); }\n#cs-root .cs-note.is-warn { color: var(--cs-warn); }\n#cs-root .cs-note.is-bad { color: var(--cs-danger); }\n#cs-root .cs-replies { margin-top: 4px; font-size: 12px; color: var(--cs-accent); font-weight: 500; }\n#cs-root .cs-thread-more { padding: 0 10px 10px 44px; display: grid; gap: 10px; }\n#cs-root .cs-message { display: flex; gap: 8px; }\n#cs-root .cs-reply { display: flex; align-items: flex-end; gap: 6px; padding: 4px 4px 4px 0; border-radius: 10px; }\n#cs-root .cs-reply textarea { min-height: 32px; max-height: 120px; field-sizing: content; }\n#cs-root .cs-thread.is-open .cs-reply textarea { background: var(--cs-card); }\n#cs-root .cs-actions { display: flex; flex-wrap: wrap; gap: 6px; }\n#cs-root .cs-thread.is-open .cs-button { background: var(--cs-card); box-shadow: 0 0 0 0.5px var(--cs-edge); }\n#cs-root .cs-thread.is-open .cs-button:hover { background: var(--cs-fill-2); }\n\n/* Mentions */\n#cs-root .cs-mention { color: var(--cs-accent); background: var(--cs-accent-soft); border-radius: 4px; padding: 0 3px; font-weight: 500; }\n#cs-root .cs-delivery { margin-top: 4px; font-size: 11.5px; color: var(--cs-text-3); display: flex; align-items: center; gap: 5px; }\n#cs-root .cs-delivery::before { content: \"\"; width: 6px; height: 6px; border-radius: 999px; background: #34c759; }\n#cs-root .cs-people {\n position: fixed; z-index: 1; width: 264px; padding: 5px; border-radius: 12px;\n background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: auto;\n}\n#cs-root .cs-person { display: flex; align-items: center; gap: 9px; padding: 6px 8px; border-radius: 8px; cursor: pointer; }\n#cs-root .cs-person.is-on { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-person-text { display: grid; line-height: 1.25; min-width: 0; }\n#cs-root .cs-person-text strong { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-person-text small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-person.is-on small { color: rgba(255, 255, 255, 0.8); }\n#cs-root .cs-field small { font-size: 11.5px; color: var(--cs-text-3); padding-left: 2px; }\n#cs-root .cs-me-lines { display: grid; line-height: 1.25; min-width: 0; }\n#cs-root .cs-me-lines small { font-size: 11.5px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* Composer */\n#cs-root .cs-composer { width: min(320px, calc(100% - 16px)); }\n#cs-root .cs-composer-target {\n display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 12px;\n border-bottom: 1px solid var(--cs-line); font-size: 12px; color: var(--cs-text-2);\n}\n#cs-root .cs-composer-name { font-weight: 500; color: var(--cs-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n#cs-root .cs-composer-target code {\n margin-left: auto; font: 11px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--cs-text-3);\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis; direction: rtl; min-width: 0; max-width: 45%;\n}\n#cs-root .cs-composer-body { display: flex; gap: 10px; padding: 12px 12px 4px; }\n#cs-root .cs-composer-body textarea { background: transparent; padding: 3px 0; min-height: 56px; box-shadow: none; border-radius: 0; }\n#cs-root .cs-composer-foot { display: flex; align-items: center; gap: 6px; padding: 6px 8px 8px 40px; }\n#cs-root .cs-composer-foot .cs-hint { margin-left: auto; }\n\n/* Attachments */\n#cs-root .cs-attach { display: inline-flex; gap: 0; flex: none; }\n#cs-root .cs-tray { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 12px 2px 46px; }\n#cs-root .cs-thread-more .cs-tray { padding: 0; }\n#cs-root .cs-pending {\n position: relative; display: inline-flex; align-items: center; gap: 6px; max-width: 170px; height: 34px;\n padding: 0 24px 0 4px; border-radius: 8px; background: var(--cs-fill); font-size: 11.5px; color: var(--cs-text-2);\n}\n#cs-root .cs-pending img { width: 26px; height: 26px; border-radius: 5px; object-fit: cover; flex: none; }\n#cs-root .cs-pending .cs-icon { margin-left: 4px; }\n#cs-root .cs-pending-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-pending.is-uploading { opacity: 0.6; }\n#cs-root .cs-pending.is-failed { box-shadow: inset 0 0 0 1px var(--cs-danger); }\n#cs-root .cs-pending-remove {\n position: absolute; right: 3px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; border-radius: 999px;\n display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);\n}\n#cs-root .cs-pending-remove:hover { background: var(--cs-fill-2); color: var(--cs-text); }\n#cs-root .cs-pending-remove .cs-icon { width: 11px; height: 11px; margin: 0; }\n#cs-root .cs-files { display: grid; gap: 6px; margin-top: 6px; }\n#cs-root .cs-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }\n#cs-root .cs-thumb { display: block; width: 112px; height: 72px; border-radius: 8px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--cs-edge); background: var(--cs-fill); }\n#cs-root .cs-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }\n#cs-root .cs-file {\n display: inline-flex; align-items: center; gap: 6px; max-width: 100%; padding: 6px 10px; border-radius: 8px;\n background: var(--cs-fill); color: var(--cs-text); text-decoration: none; font-size: 12px; justify-self: start;\n}\n#cs-root .cs-file:hover { background: var(--cs-fill-2); }\n#cs-root .cs-file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-file-size { color: var(--cs-text-3); flex: none; }\n\n/* Page overlays */\n#cs-root .cs-pin {\n position: absolute;\n transform: translate(-50%, -50%);\n padding: 2px;\n border-radius: 999px;\n background: var(--cs-card);\n box-shadow: 0 0 0 0.5px var(--cs-edge), 0 3px 10px rgba(0, 0, 0, 0.18);\n pointer-events: auto;\n transition: transform 0.15s ease;\n}\n#cs-root .cs-pin:hover { transform: translate(-50%, -50%) scale(1.08); }\n#cs-root .cs-pin.is-active { box-shadow: 0 0 0 2px var(--cs-accent), 0 3px 10px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin.is-drifted { box-shadow: 0 0 0 1.5px var(--cs-warn), 0 3px 10px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin-count {\n position: absolute; top: -5px; right: -6px; min-width: 15px; height: 15px; padding: 0 4px;\n border-radius: 999px; background: var(--cs-text); color: var(--cs-card);\n font-size: 9.5px; font-weight: 700; line-height: 15px; text-align: center;\n}\n#cs-root .cs-peek {\n position: absolute; width: 240px; padding: 10px 12px; border-radius: 12px;\n background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: none;\n}\n#cs-root .cs-peek .cs-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }\n#cs-root .cs-outline {\n position: absolute; border-radius: 8px; pointer-events: none;\n box-shadow: 0 0 0 2px var(--cs-accent); background: var(--cs-accent-soft);\n}\n#cs-root .cs-outline.is-flash { animation: cs-flash 1.4s ease forwards; }\n@keyframes cs-flash { 0%, 60% { opacity: 1; } 100% { opacity: 0; } }\n#cs-root .cs-outline-label {\n position: absolute; max-width: 360px; padding: 3px 8px; border-radius: 6px; pointer-events: none;\n background: var(--cs-accent); color: #fff; font-size: 11.5px; font-weight: 500;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n#cs-root .cs-banner {\n position: absolute; top: 14px; left: 50%; transform: translateX(-50%);\n display: flex; align-items: center; gap: 8px; padding: 5px 5px 5px 12px; border-radius: 999px;\n background: var(--cs-glass); -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);\n box-shadow: var(--cs-shadow); font-weight: 500; white-space: nowrap;\n}\n#cs-root .cs-banner button { padding: 3px 10px; border-radius: 999px; background: var(--cs-fill); font-size: 12px; }\n#cs-root .cs-banner button:hover { background: var(--cs-fill-2); }\n#cs-root .cs-toast {\n position: absolute; top: 14px; left: 50%; transform: translateX(-50%); padding: 6px 12px; border-radius: 999px;\n background: rgba(29, 29, 31, 0.9); color: #fff; font-size: 12px; font-weight: 500;\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); pointer-events: none; white-space: nowrap;\n}\n\n/* Docked inside an open <dialog> */\n#cs-root.cs-inside .cs-bar { bottom: 8px; }\n#cs-root.cs-inside.cs-at-left .cs-bar { left: 8px; }\n#cs-root.cs-inside.cs-at-right .cs-bar { right: 8px; }\n#cs-root.cs-inside .cs-sheet { top: 8px; right: 8px; bottom: auto; max-height: 46%; width: min(300px, calc(100% - 16px)); }\n#cs-root.cs-inside .cs-unlock, #cs-root.cs-inside .cs-menu { bottom: 52px; }\n#cs-root.cs-inside .cs-toast { top: 8px; }\n";
@@ -0,0 +1,10 @@
1
+ export type ToolbarPosition = "bottom-left" | "bottom-center" | "bottom-right";
2
+ export type ToolbarTheme = "light" | "dark" | "system";
3
+ export declare function CommentToolbar({ sessionPath, pollMs, position, theme, }: {
4
+ sessionPath?: string;
5
+ pollMs?: number;
6
+ /** Default bar position. Each person can override it from their menu. */
7
+ position?: ToolbarPosition;
8
+ /** "system" follows the OS appearance. Light by default so it matches most host pages. */
9
+ theme?: ToolbarTheme;
10
+ }): import("react").ReactPortal | null;
@@ -0,0 +1,8 @@
1
+ export type CommentServiceConfig = {
2
+ url: string;
3
+ project: string;
4
+ environment: string;
5
+ secret: string;
6
+ };
7
+ export declare function readCommentServiceConfig(env?: Record<string, string | undefined>): CommentServiceConfig | null;
8
+ export declare function handleSessionRequest(request: Request, config?: CommentServiceConfig | null): Promise<Response>;
@@ -0,0 +1,3 @@
1
+ export { commentService } from "./plugin.js";
2
+ export { handleSessionRequest, readCommentServiceConfig } from "../server/index.js";
3
+ export type { CommentServiceConfig } from "../server/index.js";
@@ -0,0 +1,11 @@
1
+ export declare function commentService(options?: {
2
+ enabled?: boolean;
3
+ root?: string;
4
+ }): {
5
+ name: string;
6
+ enforce: "pre";
7
+ transform(code: string, id: string): {
8
+ code: string;
9
+ map: null;
10
+ } | null;
11
+ };
package/dist/vite.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { commentService } from "./types/start/index.js"
package/dist/vite.js ADDED
@@ -0,0 +1,128 @@
1
+ // ../compiler/dist/annotate.js
2
+ import generateModule from "@babel/generator";
3
+ import { parse } from "@babel/parser";
4
+ import traverseModule from "@babel/traverse";
5
+ import * as t from "@babel/types";
6
+ function generate() {
7
+ const mod = generateModule;
8
+ return mod.default ?? mod;
9
+ }
10
+ function traverse() {
11
+ const mod = traverseModule;
12
+ return mod.default ?? mod;
13
+ }
14
+ function toProjectPath(filename, root) {
15
+ const clean = filename.split("?")[0].replaceAll("\\", "/");
16
+ const rootClean = root.replaceAll("\\", "/").replace(/\/$/, "");
17
+ let relative = clean.startsWith(`${rootClean}/`) ? clean.slice(rootClean.length + 1) : clean;
18
+ if (relative.startsWith("/"))
19
+ relative = relative.slice(1);
20
+ return relative;
21
+ }
22
+ function componentName(path, fallback) {
23
+ let current = path;
24
+ while (current) {
25
+ if (current.isFunctionDeclaration() && current.node.id)
26
+ return current.node.id.name;
27
+ if (current.isClassDeclaration() && current.node.id)
28
+ return current.node.id.name;
29
+ if (current.isFunctionExpression() || current.isArrowFunctionExpression()) {
30
+ const parent = current.parentPath;
31
+ if (parent?.isVariableDeclarator() && parent.node.id.type === "Identifier")
32
+ return parent.node.id.name;
33
+ if ((parent?.isObjectProperty() || parent?.isClassProperty()) && parent.node.key.type === "Identifier") {
34
+ return parent.node.key.name;
35
+ }
36
+ }
37
+ current = current.parentPath;
38
+ }
39
+ return fallback;
40
+ }
41
+ function isRootJsx(path) {
42
+ const fn = path.getFunctionParent();
43
+ if (!fn)
44
+ return false;
45
+ let current = path.parentPath;
46
+ while (current && current !== fn) {
47
+ if (current.isJSXElement())
48
+ return false;
49
+ current = current.parentPath;
50
+ }
51
+ return true;
52
+ }
53
+ function hasAttr(path, name) {
54
+ return path.node.openingElement.attributes.some((attr) => attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier" && attr.name.name === name);
55
+ }
56
+ function setAttr(path, name, value) {
57
+ if (hasAttr(path, name))
58
+ return;
59
+ path.node.openingElement.attributes.push(t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value)));
60
+ }
61
+ function annotateSource(code, filename, root) {
62
+ if (!filename.match(/\.[jt]sx$/))
63
+ return code;
64
+ if (filename.includes("node_modules"))
65
+ return code;
66
+ let ast;
67
+ try {
68
+ ast = parse(code, {
69
+ sourceType: "module",
70
+ plugins: ["jsx", "typescript"]
71
+ });
72
+ } catch {
73
+ return code;
74
+ }
75
+ const file = toProjectPath(filename, root);
76
+ const fallback = file.split("/").pop()?.replace(/\.[jt]sx$/, "") || "component";
77
+ let changed = false;
78
+ traverse()(ast, {
79
+ JSXElement(path) {
80
+ const opening = path.node.openingElement.name;
81
+ if (!t.isJSXIdentifier(opening))
82
+ return;
83
+ if (opening.name[0] !== opening.name[0].toLowerCase())
84
+ return;
85
+ if (!isRootJsx(path))
86
+ return;
87
+ const name = componentName(path, fallback);
88
+ const before = path.node.openingElement.attributes.length;
89
+ setAttr(path, "data-cs-cmp", name);
90
+ setAttr(path, "data-cs-file", file);
91
+ if (path.node.openingElement.attributes.length !== before)
92
+ changed = true;
93
+ }
94
+ });
95
+ if (!changed)
96
+ return code;
97
+ const printed = generate()(ast, { retainLines: true, comments: true }, code);
98
+ return printed.code;
99
+ }
100
+
101
+ // ../start/dist/plugin.js
102
+ function commentService(options) {
103
+ const enabled = options?.enabled ?? process.env.COMMENT_SERVICE_ENABLED !== "0";
104
+ const root = (options?.root ?? process.cwd()).replace(/\/$/, "");
105
+ return {
106
+ name: "devaccel-comment-service",
107
+ enforce: "pre",
108
+ transform(code, id) {
109
+ if (!enabled)
110
+ return null;
111
+ const file = id.split("?")[0];
112
+ if (!/\.[jt]sx$/.test(file) || file.includes("/node_modules/"))
113
+ return null;
114
+ if (!file.startsWith(`${root}/`))
115
+ return null;
116
+ try {
117
+ const next = annotateSource(code, file, root);
118
+ return next === code ? null : { code: next, map: null };
119
+ } catch (error) {
120
+ console.warn("[comment-service] annotation skipped", file, error);
121
+ return null;
122
+ }
123
+ }
124
+ };
125
+ }
126
+ export {
127
+ commentService
128
+ };