@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.
Files changed (54) hide show
  1. package/README.md +19 -2
  2. package/dist/chunks/chunk-CSKXLF33.js +153 -0
  3. package/dist/chunks/chunk-HUM5FQII.js +160 -0
  4. package/dist/chunks/render-page-76MLIT6M.js +1421 -0
  5. package/dist/chunks/runtime-JX75TKER.js +9267 -0
  6. package/dist/chunks/screenshot-66ARTBGR.js +12 -0
  7. package/dist/react.d.ts +1 -0
  8. package/dist/react.js +91 -0
  9. package/dist/server.d.ts +1 -0
  10. package/dist/server.js +87 -0
  11. package/dist/types/compiler/annotate.d.ts +10 -0
  12. package/dist/types/compiler/enabled.d.ts +14 -0
  13. package/dist/types/compiler/index.d.ts +2 -0
  14. package/dist/types/core/index.d.ts +2 -0
  15. package/dist/types/core/resolve.d.ts +29 -0
  16. package/dist/types/core/types.d.ts +65 -0
  17. package/dist/types/react.d.ts +2 -0
  18. package/dist/types/runtime/attachments.d.ts +73 -0
  19. package/dist/types/runtime/capture-ui.d.ts +35 -0
  20. package/dist/types/runtime/capture.d.ts +30 -0
  21. package/dist/types/runtime/client.d.ts +220 -0
  22. package/dist/types/runtime/dashboard.d.ts +28 -0
  23. package/dist/types/runtime/details.d.ts +43 -0
  24. package/dist/types/runtime/dock.d.ts +38 -0
  25. package/dist/types/runtime/edits.d.ts +20 -0
  26. package/dist/types/runtime/format.d.ts +15 -0
  27. package/dist/types/runtime/freshness.d.ts +18 -0
  28. package/dist/types/runtime/handoff.d.ts +15 -0
  29. package/dist/types/runtime/icons.d.ts +40 -0
  30. package/dist/types/runtime/idle.d.ts +6 -0
  31. package/dist/types/runtime/index.d.ts +2 -0
  32. package/dist/types/runtime/loader.d.ts +14 -0
  33. package/dist/types/runtime/members.d.ts +11 -0
  34. package/dist/types/runtime/mentions.d.ts +17 -0
  35. package/dist/types/runtime/placement.d.ts +43 -0
  36. package/dist/types/runtime/record.d.ts +33 -0
  37. package/dist/types/runtime/redact.d.ts +10 -0
  38. package/dist/types/runtime/related.d.ts +11 -0
  39. package/dist/types/runtime/render-page.d.ts +1 -0
  40. package/dist/types/runtime/replay.d.ts +42 -0
  41. package/dist/types/runtime/runtime-version.d.ts +7 -0
  42. package/dist/types/runtime/runtime.d.ts +8 -0
  43. package/dist/types/runtime/screenshot.d.ts +12 -0
  44. package/dist/types/runtime/session.d.ts +25 -0
  45. package/dist/types/runtime/styles.d.ts +5 -0
  46. package/dist/types/runtime/sync.d.ts +25 -0
  47. package/dist/types/runtime/toolbar.d.ts +22 -0
  48. package/dist/types/server/index.d.ts +14 -0
  49. package/dist/types/server.d.ts +2 -0
  50. package/dist/types/vite/plugin.d.ts +20 -0
  51. package/dist/types/vite.d.ts +1 -0
  52. package/dist/vite.d.ts +1 -0
  53. package/dist/vite.js +173 -0
  54. package/package.json +52 -4
@@ -0,0 +1,8 @@
1
+ import { type RuntimeProps } from "./toolbar.js";
2
+ export { runtimeVersion } from "./runtime-version.js";
3
+ export type { RuntimeProps } from "./toolbar.js";
4
+ export type ToolbarHandle = {
5
+ update(props: RuntimeProps): void;
6
+ unmount(): void;
7
+ };
8
+ export declare function mount(container: HTMLElement, props: RuntimeProps): ToolbarHandle;
@@ -0,0 +1,12 @@
1
+ import type { Capture } from "./client.js";
2
+ /** The page, viewport and browser a screenshot is being taken in. */
3
+ export declare function describeCapture(url: string): Capture;
4
+ /** The visible page as a canvas, toolbar left out. `timeout` bounds a slow frame. */
5
+ export declare function renderViewport(scale: number, timeout?: number): Promise<HTMLCanvasElement>;
6
+ /** The whole visible page as a JPEG, toolbar left out. If `highlight` is on screen it's outlined. */
7
+ export declare function capturePage(highlight?: Element | null): Promise<Blob | null>;
8
+ /**
9
+ * Capture what the person sees around `element`, with the toolbar left out, the rest dimmed,
10
+ * and the element outlined. Returns a JPEG data URL, or null if the element isn't on screen or capture fails.
11
+ */
12
+ export declare function captureAround(element: Element): Promise<string | null>;
@@ -0,0 +1,25 @@
1
+ export type Session = {
2
+ apiUrl: string;
3
+ project: string;
4
+ environment: string;
5
+ hostToken: string;
6
+ };
7
+ /** Returns this page's session, or null when comments are off. `refresh` skips the cached one. */
8
+ export type GetSession = (options?: {
9
+ refresh?: boolean;
10
+ }) => Promise<Session | null>;
11
+ export declare function readJwt(token: string): {
12
+ exp?: number;
13
+ typ?: string;
14
+ name?: string;
15
+ email?: string;
16
+ memberId?: string;
17
+ } | null;
18
+ export declare function sessionFrom(payload: unknown): Session | null;
19
+ /**
20
+ * Reads the session route at most once at a time and reuses the answer until its host token is about to expire.
21
+ * "Comments are off" is kept for the page's lifetime. A failed request is not kept, so the next call retries.
22
+ */
23
+ export declare function sessionSource(path: string, load?: (path: string) => Promise<unknown>, now?: () => number): GetSession;
24
+ /** The page-wide session source for a session route path. */
25
+ export declare function sessionFor(path: string): GetSession;
@@ -0,0 +1,5 @@
1
+ declare const sheets: readonly [readonly ["bar", "\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-bar: rgba(244, 244, 246, 0.92);\n --cs-well: rgba(0, 0, 0, 0.05);\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-linked: #af52de;\n --cs-warn-soft: rgba(255, 149, 0, 0.16);\n --cs-warn-ink: #b25f00;\n --cs-raised: #fff;\n --cs-raised-shadow: 0 1px 2px rgba(0, 0, 0, 0.14), 0 0 0 0.5px rgba(0, 0, 0, 0.06);\n --cs-d1: 120ms;\n --cs-d2: 200ms;\n --cs-d3: 320ms;\n --cs-ease: cubic-bezier(0.2, 0.8, 0.2, 1);\n --cs-ease-in: cubic-bezier(0.4, 0, 1, 1);\n --cs-spring: cubic-bezier(0.34, 1.56, 0.64, 1);\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 8px 23px rgba(0, 0, 0, 0.12), 0 2px 5px 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: 11px/1.45 -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(28, 28, 30, 0.86);\n --cs-bar: rgba(43, 43, 43, 0.94);\n --cs-well: rgba(255, 255, 255, 0.06);\n --cs-card: #0f0f0f;\n --cs-fill: rgba(255, 255, 255, 0.06);\n --cs-fill-2: rgba(255, 255, 255, 0.1);\n --cs-line: rgba(255, 255, 255, 0.08);\n --cs-edge: rgba(255, 255, 255, 0.1);\n --cs-accent: #0a84ff;\n --cs-accent-soft: rgba(10, 132, 255, 0.2);\n --cs-warn-soft: rgba(255, 159, 10, 0.2);\n --cs-warn-ink: #ffb340;\n --cs-raised: rgba(255, 255, 255, 0.2);\n --cs-raised-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n --cs-shadow: 0 0 0 0.5px var(--cs-edge), 0 8px 23px 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 z-index: 8;\n bottom: 21px;\n display: flex;\n align-items: center;\n gap: 5px;\n padding: 5px;\n border-radius: 999px;\n background: var(--cs-bar);\n -webkit-backdrop-filter: saturate(180%) blur(18px);\n backdrop-filter: saturate(180%) blur(18px);\n box-shadow: 0 0 0 0.5px var(--cs-edge), 0 8px 21px rgba(0, 0, 0, 0.28);\n cursor: grab;\n touch-action: none;\n user-select: none;\n}\n#cs-root.cs-at-center .cs-bar { left: 50%; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-bar { left: 12px; }\n#cs-root.cs-at-right .cs-bar { right: 12px; }\n#cs-root .cs-bar.is-docked { transition: left 300ms var(--cs-ease), top 300ms var(--cs-ease); }\n#cs-root .cs-bar.is-dragging,\n#cs-root .cs-bar.is-dragging button { cursor: grabbing; transition: none; }\n#cs-root .cs-tool {\n position: relative;\n width: 27px;\n height: 27px;\n border-radius: 999px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--cs-text-2);\n background: var(--cs-well);\n transition:\n background-color var(--cs-d1) var(--cs-ease), color var(--cs-d1) var(--cs-ease), filter var(--cs-d1) var(--cs-ease),\n box-shadow var(--cs-d2) var(--cs-ease), transform var(--cs-d2) var(--cs-spring);\n}\n#cs-root .cs-tool:hover { background: var(--cs-fill-2); color: var(--cs-text); }\n#cs-root .cs-tool:active:not(:disabled) { transform: scale(0.9); transition-duration: 80ms; transition-timing-function: var(--cs-ease); }\n/* Color means a mode: blue comments, amber says pins are hidden. An open panel only gets a raised chip. */\n#cs-root .cs-bar .cs-tool.is-on { background: var(--cs-raised); color: var(--cs-text); box-shadow: var(--cs-raised-shadow); }\n#cs-root .cs-bar .cs-tool.is-on[data-cs-ui=comment-mode] { background: var(--cs-accent); color: #fff; box-shadow: 0 0 0 2px var(--cs-accent-soft); }\n#cs-root .cs-bar .cs-tool.is-on[data-cs-ui=comment-mode]:hover { filter: brightness(1.08); }\n#cs-root .cs-bar .cs-tool.is-on[data-cs-ui=pins-toggle] { background: var(--cs-warn-soft); color: var(--cs-warn-ink); box-shadow: none; }\n#cs-root .cs-bar .cs-tool.is-on[data-cs-ui=pins-toggle]:hover { background: rgba(255, 149, 0, 0.24); }\n#cs-root .cs-tool.is-accent { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-slash { stroke-dasharray: 26; stroke-dashoffset: 26; transition: stroke-dashoffset var(--cs-d2) var(--cs-ease); }\n#cs-root .cs-slash.is-on { stroke-dashoffset: 0; }\n#cs-root .cs-ripple {\n position: absolute; inset: 0; border-radius: inherit; pointer-events: none; opacity: 0;\n box-shadow: 0 0 0 2px var(--cs-accent);\n}\n/* The tools rise in one after another when the bar appears or unlocks. */\n#cs-root .cs-bar > * { animation: cs-enter 300ms var(--cs-ease) 60ms backwards; }\n#cs-root .cs-bar > :nth-child(2) { animation-delay: 88ms; }\n#cs-root .cs-bar > :nth-child(3) { animation-delay: 116ms; }\n#cs-root .cs-bar > :nth-child(4) { animation-delay: 144ms; }\n#cs-root .cs-bar > :nth-child(5) { animation-delay: 172ms; }\n@keyframes cs-enter { from { opacity: 0; transform: translateY(5px) scale(0.9); } }\n#cs-root .cs-tool[data-tip]::after,\n#cs-root .cs-attach [data-tip]::after {\n content: attr(data-tip);\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n z-index: 2;\n transform: translate(-50%, 2px) scale(0.96);\n transform-origin: 50% 100%;\n white-space: pre;\n padding: 3px 6px;\n border-radius: 5px;\n background: rgba(29, 29, 31, 0.92);\n color: #fff;\n font-size: 10px;\n font-weight: 500;\n line-height: 1.2;\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--cs-d1) var(--cs-ease), transform var(--cs-d1) var(--cs-ease);\n}\n#cs-root .cs-tool[data-tip]:hover::after,\n#cs-root .cs-attach [data-tip]:hover::after,\n#cs-root .cs-attach [data-tip]:focus-visible::after { opacity: 1; transform: translate(-50%, 0); transition-delay: 0.35s; }\n#cs-root .cs-bar[data-warm] .cs-tool[data-tip]:hover::after,\n#cs-root .cs-attach[data-warm] [data-tip]:hover::after,\n#cs-root .cs-attach [data-tip]:focus-visible::after { transition-delay: 0s; }\n#cs-root .cs-bar.is-dragging .cs-tool[data-tip]::after { opacity: 0 !important; transition-delay: 0s !important; }\n#cs-root .cs-tool[data-tipless]::after,\n#cs-root .cs-attach [data-tipless]::after { opacity: 0 !important; transition-delay: 0s !important; }\n#cs-root .cs-divider { width: 1px; height: 14px; background: var(--cs-line); margin: 0 2px; }\n#cs-root .cs-badge {\n position: absolute;\n top: 2px;\n right: 2px;\n min-width: 12px;\n height: 12px;\n padding: 0 3px;\n border-radius: 999px;\n background: var(--cs-accent);\n color: #fff;\n font-size: 9px;\n font-weight: 600;\n line-height: 12px;\n text-align: center;\n box-shadow: 0 0 0 2px var(--cs-glass);\n overflow: hidden;\n animation: cs-badge-in var(--cs-d2) var(--cs-spring) backwards;\n}\n#cs-root .cs-badge > span { display: block; font-variant-numeric: tabular-nums; }\n@keyframes cs-badge-in { from { transform: scale(0); opacity: 0; } }\n#cs-root .cs-bar-lock {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n height: 26px;\n padding: 0 11px 0 9px;\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@media (prefers-reduced-motion: reduce) {\n #cs-root *, #cs-root *::before, #cs-root *::after {\n animation: none !important;\n transition-property: opacity, color, background-color, box-shadow, filter, stroke-dashoffset !important;\n }\n}\n"], readonly ["panel", "\n/* Cards */\n#cs-root .cs-card {\n position: absolute;\n background: var(--cs-card);\n -webkit-backdrop-filter: saturate(180%) blur(18px);\n backdrop-filter: saturate(180%) blur(18px);\n border-radius: 11px;\n box-shadow: var(--cs-shadow);\n}\n#cs-root .cs-unlock, #cs-root .cs-menu { bottom: 66px; z-index: 8; }\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: 12px; }\n#cs-root.cs-at-right .cs-unlock, #cs-root.cs-at-right .cs-menu { right: 12px; }\n#cs-root .cs-unlock { width: 210px; padding: 15px; text-align: center; }\n#cs-root .cs-unlock-icon {\n width: 30px; height: 30px; margin: 0 auto 8px; border-radius: 9px;\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: 11px; font-weight: 600; }\n#cs-root .cs-sub { color: var(--cs-text-2); margin-top: 3px; }\n#cs-root .cs-unlock form { margin-top: 11px; display: grid; gap: 6px; text-align: left; }\n#cs-root .cs-unlock-sso { margin-top: 11px; display: grid; gap: 6px; }\n#cs-root .cs-unlock-sso + form { margin-top: 5px; }\n#cs-root .cs-field { display: grid; gap: 3px; }\n#cs-root .cs-field span { font-size: 10px; color: var(--cs-text-2); padding-left: 2px; }\n#cs-root input, #cs-root textarea {\n width: 100%;\n border: 0;\n border-radius: 7px;\n background: var(--cs-fill);\n padding: 6px 8px;\n min-height: 32px;\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 3px var(--cs-accent-soft); }\n#cs-root textarea { resize: none; }\n#cs-root .cs-error { color: var(--cs-danger); font-size: 10px; }\n#cs-root .cs-pad { padding: 5px 11px 0; }\n#cs-root .cs-hint { color: var(--cs-text-3); font-size: 10px; }\n\n/* Buttons */\n#cs-root .cs-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n height: 24px;\n padding: 0 8px;\n border-radius: 6px;\n background: var(--cs-fill);\n font-size: 11px;\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: 3px; }\n#cs-root .cs-icon-button {\n width: 20px; height: 20px; 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: 20px; height: 20px; 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: 198px; padding: 5px; }\n#cs-root .cs-me-row { display: flex; align-items: center; gap: 8px; padding: 6px 6px 8px; }\n#cs-root .cs-me-name { display: flex; align-items: center; gap: 3px; 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: 3px; flex: 1; }\n#cs-root .cs-rename input { padding: 4px 6px; }\n#cs-root .cs-sep { height: 1px; background: var(--cs-line); margin: 3px 6px; }\n#cs-root .cs-item {\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n width: 100%; min-height: 24px; padding: 0 6px; border-radius: 6px; 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: 24px; height: 15px; 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: 12px; height: 12px; border-radius: 999px;\n background: #fff; box-shadow: 0 1px 2px 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(8px); }\n#cs-root .cs-segment { display: inline-flex; padding: 2px; border-radius: 6px; background: var(--cs-fill); }\n#cs-root .cs-segment button { padding: 2px 6px; border-radius: 5px; font-size: 10px; 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: 5px 6px 3px; }\n#cs-root .cs-share { display: grid; gap: 3px; padding: 5px 6px; }\n#cs-root .cs-share-label { font-size: 10px; color: var(--cs-text-2); }\n#cs-root .cs-share-code {\n display: flex; align-items: center; gap: 5px; padding: 3px 3px 3px 8px; border-radius: 6px; background: var(--cs-fill);\n}\n#cs-root .cs-share-code code {\n flex: 1; min-width: 0; font: 600 11px ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.02em;\n overflow-wrap: anywhere; user-select: all;\n}\n#cs-root .cs-share .cs-hint, #cs-root .cs-menu .cs-share .cs-hint { padding: 2px 0 0; }\n#cs-root .cs-confirm { padding: 8px; }\n#cs-root .cs-confirm h2 { font-size: 11px; font-weight: 600; }\n#cs-root .cs-row-end { display: flex; justify-content: flex-end; gap: 5px; margin-top: 9px; }\n\n/* Sheet — a card sitting on the toolbar, which overlaps its bottom edge. */\n#cs-root .cs-sheet {\n top: auto; bottom: 8px; max-height: calc(100% - 18px); width: min(255px, calc(100vw - 18px));\n display: flex; flex-direction: column; overflow: hidden; border-radius: 17px; z-index: 5;\n}\n#cs-root .cs-sheet.is-menu { overflow: visible; }\n#cs-root .cs-sheet.is-free { bottom: auto; right: auto; max-height: none; transform: none; }\n#cs-root .cs-grab {\n position: absolute; z-index: 4; top: 0; left: 0; right: 0; height: 15px;\n display: flex; align-items: center; justify-content: center;\n background: transparent; cursor: grab; touch-action: none;\n}\n#cs-root .cs-grab::before {\n content: \"\"; width: 27px; height: 4px; border-radius: 999px; background: var(--cs-text-3);\n}\n#cs-root .cs-grab:hover::before { background: var(--cs-text-2); }\n#cs-root .cs-grab:active { cursor: grabbing; }\n#cs-root .cs-sheet-head { cursor: grab; }\n#cs-root .cs-sheet-head button { cursor: pointer; }\n#cs-root .cs-resize {\n position: absolute; z-index: 6; right: 0; bottom: 0; width: 27px; height: 27px;\n cursor: nwse-resize; touch-action: none;\n}\n#cs-root .cs-resize::after {\n content: \"\"; position: absolute; right: 6px; bottom: 6px; width: 8px; height: 8px;\n border-right: 2px solid var(--cs-text-3); border-bottom: 2px solid var(--cs-text-3); border-bottom-right-radius: 2px;\n}\n#cs-root .cs-resize:hover::after { border-color: var(--cs-text); }\n#cs-root.cs-at-center .cs-sheet { left: 50%; right: auto; transform: translateX(-50%); }\n#cs-root.cs-at-left .cs-sheet { left: 12px; right: auto; }\n#cs-root.cs-at-right .cs-sheet { right: 12px; left: auto; }\n#cs-root .cs-sheet-head { display: flex; align-items: center; gap: 2px; padding: 17px 8px 6px 14px; }\n#cs-root .cs-sheet-head h2 { font-size: 13px; font-weight: 600; letter-spacing: -0.022em; }\n#cs-root .cs-sheet-head .cs-icon-button { width: 23px; height: 23px; color: var(--cs-text-2); }\n#cs-root .cs-sheet-head .cs-icon-button.is-on,\n#cs-root .cs-sheet-head .cs-icon-button:hover { background: var(--cs-fill); color: var(--cs-text); }\n#cs-root .cs-scope-wrap { position: relative; margin-right: auto; }\n#cs-root .cs-scope {\n display: inline-flex; align-items: center; gap: 2px; height: 24px; padding: 0 5px 0 0;\n border-radius: 6px; font-size: 13px; font-weight: 600; letter-spacing: -0.022em;\n}\n#cs-root .cs-scope .cs-icon { color: var(--cs-text-3); }\n#cs-root .cs-scope:hover { background: var(--cs-fill); }\n#cs-root .cs-scope-menu {\n position: absolute; z-index: 4; top: calc(100% + 5px); left: 0; min-width: 150px; padding: 4px;\n border-radius: 11px; background: var(--cs-glass); box-shadow: var(--cs-shadow);\n -webkit-backdrop-filter: saturate(180%) blur(18px); backdrop-filter: saturate(180%) blur(18px);\n}\n#cs-root.cs-dark .cs-scope-menu { background: #2c2c2e; }\n#cs-root .cs-scope-menu button {\n display: flex; align-items: center; justify-content: space-between; gap: 9px;\n width: 100%; min-height: 24px; padding: 0 8px; border-radius: 6px; text-align: left; font-size: 11px;\n}\n#cs-root .cs-scope-menu button:hover,\n#cs-root .cs-scope-menu button.is-on { background: var(--cs-fill-2); }\n#cs-root .cs-scope-meta { display: inline-flex; align-items: center; gap: 5px; color: var(--cs-text-3); }\n#cs-root .cs-scope-meta .cs-icon { width: 10px; height: 10px; }\n#cs-root .cs-count { font-size: 10px; color: var(--cs-text-3); font-variant-numeric: tabular-nums; }\n#cs-root .cs-search { display: grid; gap: 8px; padding: 2px 11px 9px; }\n#cs-root .cs-list-search { width: 100%; margin: 0; }\n#cs-root .cs-dates {\n display: flex; padding: 2px; border-radius: 8px; background: var(--cs-fill);\n}\n#cs-root .cs-dates button {\n flex: 1; min-width: 0; height: 21px; border-radius: 6px;\n color: var(--cs-text-2); font-size: 10px; font-weight: 500; white-space: nowrap;\n}\n#cs-root .cs-dates button.is-on {\n background: var(--cs-card); color: var(--cs-text); font-weight: 600;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 0 0 0.5px rgba(0, 0, 0, 0.04);\n}\n#cs-root.cs-dark .cs-dates button.is-on { background: rgba(255, 255, 255, 0.16); box-shadow: none; }\n#cs-root .cs-dates button:hover:not(.is-on) { color: var(--cs-text); }\n#cs-root .cs-who {\n display: flex; gap: 3px; margin: 0 -3px; padding: 0 3px;\n overflow-x: auto; scrollbar-width: none; scroll-snap-type: x proximity;\n}\n#cs-root .cs-who::-webkit-scrollbar { display: none; }\n#cs-root .cs-who button {\n flex: none; width: 51px; scroll-snap-align: start;\n display: grid; justify-items: center; gap: 4px;\n color: var(--cs-text-2); font-size: 10px; font-weight: 500;\n}\n#cs-root .cs-who button span:last-child {\n max-width: 51px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n#cs-root .cs-who button.is-on { color: var(--cs-text); font-weight: 600; }\n#cs-root .cs-who-all {\n width: 27px; height: 27px; border-radius: 999px; display: grid; place-items: center;\n background: var(--cs-fill); color: var(--cs-text-2); font-size: 10px; font-weight: 600;\n}\n#cs-root .cs-who button.is-on .cs-avatar,\n#cs-root .cs-who button.is-on .cs-who-all {\n box-shadow: 0 0 0 2px var(--cs-card), 0 0 0 3px var(--cs-text);\n}\n#cs-root .cs-list { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 2px 0 63px; overscroll-behavior: contain; }\n#cs-root .cs-group { padding: 11px 14px 3px; font-size: 10px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.04em; }\n#cs-root .cs-empty { padding: 27px 14px 36px; text-align: center; color: var(--cs-text-2); display: grid; gap: 9px; justify-items: center; }\n#cs-root .cs-thread { border-radius: 0; }\n#cs-root .cs-thread + .cs-thread { border-top: 1px solid var(--cs-line); }\n#cs-root .cs-thread.is-open { background: var(--cs-fill); }\n#cs-root .cs-thread-main { display: block; padding: 12px 14px 11px; 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; }\n#cs-root .cs-byline { position: relative; display: flex; align-items: center; gap: 6px; margin-top: 8px; min-width: 0; color: var(--cs-text-2); font-size: 11px; }\n#cs-root .cs-quick-reply {\n position: absolute; z-index: 1; right: 0; top: 50%; transform: translateY(-50%);\n width: 20px; height: 20px; border-radius: 999px; display: grid; place-items: center;\n background: var(--cs-card); color: var(--cs-text-2); box-shadow: 0 0 0 0.5px var(--cs-edge);\n opacity: 0; pointer-events: none;\n}\n#cs-root .cs-thread:hover .cs-quick-reply,\n#cs-root .cs-thread:focus-within .cs-quick-reply { opacity: 1; pointer-events: auto; }\n#cs-root .cs-thread:hover .cs-byline time { opacity: 0; }\n#cs-root .cs-quick-reply:hover { color: var(--cs-text); background: var(--cs-fill); }\n#cs-root .cs-byline-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-byline time { margin-left: auto; flex: none; color: var(--cs-text-2); font-variant-numeric: tabular-nums; font-size: 11px; }\n#cs-root.cs-dark .cs-tool { color: #d2d2d7; }\n#cs-root .cs-byline .cs-replies { flex: none; color: var(--cs-text-3); font-size: 10px; font-weight: 500; }\n#cs-root .cs-meta { display: flex; align-items: center; gap: 5px; font-size: 10px; }\n#cs-root .cs-meta strong { font-weight: 600; }\n#cs-root .cs-meta time { color: var(--cs-text-3); font-size: 10px; }\n#cs-root .cs-sheet .cs-text { margin: 0; font-size: 11px; font-weight: 500; line-height: 1.45; letter-spacing: -0.02em; white-space: pre-wrap; overflow-wrap: anywhere; }\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: 3px 6px; margin-top: 3px; font-size: 10px; color: var(--cs-text-3); }\n#cs-root .cs-note { display: inline-flex; align-items: center; gap: 4px; font-weight: 500; }\n#cs-root .cs-note::before { content: \"\"; width: 5px; height: 5px; 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-replies { margin-top: 3px; font-size: 10px; color: var(--cs-accent); font-weight: 500; }\n#cs-root .cs-byline .cs-replies { margin-top: 0; }\n#cs-root .cs-thread-more { padding: 0 14px 11px; display: grid; gap: 8px; }\n#cs-root .cs-message { display: flex; gap: 6px; }\n#cs-root .cs-message-body { min-width: 0; flex: 1; }\n#cs-root .cs-edited { color: var(--cs-text-3); font-size: 10px; }\n#cs-root .cs-message-tools { display: inline-flex; gap: 6px; margin-left: auto; }\n#cs-root .cs-first-tools { display: flex; margin-top: -5px; }\n#cs-root .cs-first-tools:empty { display: none; }\n#cs-root .cs-link { padding: 0; border: 0; background: none; color: var(--cs-text-3); font-size: 10px; font-weight: 500; cursor: pointer; }\n#cs-root .cs-link:hover, #cs-root .cs-link[aria-expanded=\"true\"] { color: var(--cs-accent); }\n#cs-root .cs-edit { display: grid; gap: 5px; }\n#cs-root .cs-edit textarea { min-height: 48px; max-height: 150px; field-sizing: content; background: var(--cs-card); }\n#cs-root .cs-edit-foot { display: flex; align-items: center; gap: 5px; }\n#cs-root .cs-edit-foot .cs-hint { margin-right: auto; }\n#cs-root .cs-history { margin: 3px 0 0; padding: 0; list-style: none; display: grid; gap: 5px; }\n#cs-root .cs-history li { padding: 5px 6px; border-radius: 6px; background: var(--cs-card); box-shadow: 0 0 0 0.5px var(--cs-edge); }\n#cs-root .cs-history li:not(.is-current) .cs-text { color: var(--cs-text-2); }\n#cs-root .cs-history-meta { display: flex; align-items: center; gap: 5px; font-size: 10px; color: var(--cs-text-3); }\n#cs-root .cs-history-meta strong { color: var(--cs-text); font-weight: 600; }\n#cs-root .cs-history-meta span { margin-left: auto; color: var(--cs-accent); font-weight: 500; }\n#cs-root .cs-reply { display: flex; align-items: flex-end; gap: 5px; padding: 3px 3px 3px 0; border-radius: 8px; }\n#cs-root .cs-reply textarea { min-height: 32px; max-height: 90px; 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: 5px; }\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: 3px; padding: 0 2px; font-weight: 500; }\n#cs-root .cs-delivery { margin-top: 3px; font-size: 10px; color: var(--cs-text-3); display: flex; align-items: center; gap: 4px; }\n#cs-root .cs-delivery::before { content: \"\"; width: 5px; height: 5px; border-radius: 999px; background: #34c759; }\n/* Above the composer, the open thread, the inbox, and the dashboard. A lower layer sits behind those cards. */\n#cs-root .cs-people {\n position: absolute; z-index: 20; width: 198px; padding: 4px; border-radius: 9px;\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: 7px; padding: 5px 6px; border-radius: 6px; 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: 10px; 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: 10px; 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: 10px; color: var(--cs-text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* Composer */\n#cs-root .cs-composer { width: min(240px, calc(100% - 12px)); border-radius: 14px; }\n#cs-root .cs-composer-close { position: absolute; top: 5px; right: 5px; z-index: 1; }\n#cs-root .cs-composer-body { display: flex; gap: 8px; padding: 8px 27px 3px 9px; }\n#cs-root .cs-composer-body textarea { flex: 1; width: auto; min-width: 0; background: transparent; padding: 2px 0; min-height: 48px; box-shadow: none; border-radius: 0; }\n#cs-root .cs-composer-foot { display: flex; align-items: center; gap: 5px; padding: 5px 6px 6px 30px; }\n#cs-root .cs-composer-foot .cs-hint,\n#cs-root .cs-composer-foot .cs-send { margin-left: auto; }\n#cs-root .cs-composer-foot .cs-hint + .cs-send { margin-left: 0; }\n\n/* Roles and fields */\n#cs-root .cs-details-input { display: grid; gap: 6px; padding: 2px 9px 5px 35px; }\n#cs-root .cs-details-edit .cs-details-input { padding: 0; }\n#cs-root .cs-detail-field { display: grid; gap: 2px; }\n#cs-root .cs-detail-field span, #cs-root .cs-roles-label, #cs-root .cs-detail-list dt {\n font-size: 10px; font-weight: 600; color: var(--cs-text-3); text-transform: uppercase; letter-spacing: 0.03em;\n}\n#cs-root .cs-detail-field textarea { padding: 5px 6px; min-height: 32px; max-height: 90px; field-sizing: content; }\n#cs-root .cs-thread.is-open .cs-detail-field textarea { background: var(--cs-card); }\n#cs-root .cs-roles { display: flex; flex-wrap: wrap; align-items: center; gap: 3px; }\n#cs-root .cs-roles-label { margin-right: 3px; }\n#cs-root .cs-chip {\n display: inline-flex; align-items: center; gap: 2px; height: 18px; padding: 0 7px; border-radius: 999px;\n background: var(--cs-fill); font-size: 10px; font-weight: 500; color: var(--cs-text-2);\n}\n#cs-root button.cs-chip:hover { background: var(--cs-fill-2); }\n#cs-root .cs-chip.is-on, #cs-root button.cs-chip.is-on:hover { background: var(--cs-accent); color: #fff; }\n#cs-root .cs-chip.is-static { padding-right: 2px; color: var(--cs-text); }\n#cs-root .cs-chip.is-static button {\n width: 14px; height: 14px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; color: var(--cs-text-3);\n}\n#cs-root .cs-chip.is-static button:hover { background: var(--cs-fill-2); color: var(--cs-text); }\n#cs-root .cs-chip.is-static .cs-icon { width: 8px; height: 8px; }\n#cs-root .cs-chips { display: flex; flex-wrap: wrap; gap: 3px; }\n#cs-root .cs-role {\n display: inline-flex; align-items: center; height: 14px; padding: 0 5px; border-radius: 999px;\n background: var(--cs-accent-soft); color: var(--cs-accent); font-size: 10px; font-weight: 600;\n}\n#cs-root .cs-ticket {\n display: inline-flex; align-items: center; height: 14px; padding: 0 5px; border-radius: 999px;\n background: rgba(255, 149, 0, 0.14); color: #b25000; font-size: 10px; font-weight: 600; white-space: nowrap;\n}\n#cs-root .cs-ticket.is-closed { background: rgba(52, 199, 89, 0.16); color: #1f7a3a; }\n#cs-root .cs-env {\n display: inline-flex; align-items: center; height: 14px; padding: 0 5px; border-radius: 999px;\n background: rgba(175, 82, 222, 0.14); color: var(--cs-linked); font-size: 10px; font-weight: 600;\n}\n#cs-root .cs-sheet .cs-thread.is-linked { box-shadow: none; }\n#cs-root .cs-linked-note { align-self: center; margin-left: auto; }\n#cs-root .cs-env-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 3px; margin: 0 9px 5px; }\n#cs-root .cs-env-filter button {\n height: 17px; padding: 0 7px; border-radius: 999px; background: var(--cs-fill); color: var(--cs-text-2); font-size: 10px; font-weight: 500;\n}\n#cs-root .cs-env-filter button.is-on { background: var(--cs-text); color: var(--cs-card); }\n#cs-root .cs-live { display: inline-flex; align-items: center; gap: 4px; margin-left: auto; font-size: 10px; color: var(--cs-text-3); }\n#cs-root .cs-live::before { content: \"\"; width: 5px; height: 5px; border-radius: 999px; background: #34c759; }\n#cs-root .cs-detail-list { display: grid; gap: 5px; margin: 5px 0 0; }\n#cs-root .cs-detail-list > div { padding: 5px 6px; border-radius: 6px; background: var(--cs-fill); }\n#cs-root .cs-thread.is-open .cs-detail-list > div { background: var(--cs-card); }\n#cs-root .cs-detail-list dd { margin: 1px 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }\n#cs-root .cs-detail-list.is-clamped dd { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }\n#cs-root .cs-details-edit { display: grid; gap: 6px; }\n#cs-root .cs-details-edit .cs-row-end { margin-top: 0; }\n#cs-root .cs-settings { width: 210px; }\n#cs-root .cs-settings-head { display: flex; align-items: center; gap: 3px; padding: 3px 3px 5px; }\n#cs-root .cs-settings-head h2 { font-size: 11px; font-weight: 600; }\n#cs-root .cs-settings-body { display: grid; gap: 5px; padding: 2px 6px 6px; }\n#cs-root .cs-field-row { display: flex; align-items: center; justify-content: space-between; gap: 5px; min-height: 21px; padding-left: 2px; }\n#cs-root .cs-add-row { display: flex; align-items: center; gap: 3px; }\n#cs-root .cs-add-row input { padding: 4px 6px; }\n#cs-root .cs-settings .cs-hint { padding: 5px 6px 3px; }\n\n/* Attachments */\n#cs-root .cs-attach { display: inline-flex; gap: 0; flex: none; }\n#cs-root .cs-attach .cs-icon-button { position: relative; }\n#cs-root .cs-attach [data-tip]::after { bottom: calc(100% + 6px); }\n#cs-root .cs-tray { display: flex; flex-wrap: wrap; gap: 5px; padding: 3px 9px 2px 35px; }\n#cs-root .cs-thread-more .cs-tray { padding: 0; }\n#cs-root .cs-thread-quick { padding: 0 14px 11px; display: grid; gap: 5px; }\n#cs-root .cs-thread-quick .cs-tray { padding: 0; }\n#cs-root .cs-pending {\n position: relative; display: inline-flex; align-items: center; gap: 5px; max-width: 128px; height: 26px;\n padding: 0 18px 0 3px; border-radius: 6px; background: var(--cs-fill); font-size: 10px; color: var(--cs-text-2);\n}\n#cs-root .cs-pending-open {\n display: inline-flex; align-items: center; gap: 5px; min-width: 0; flex: 1; padding: 0; color: inherit; cursor: zoom-in;\n}\n#cs-root .cs-pending.is-open { box-shadow: 0 0 0 2px var(--cs-accent); }\n#cs-root .cs-pending img, #cs-root .cs-pending video { width: 20px; height: 20px; border-radius: 4px; object-fit: cover; flex: none; background: #111; pointer-events: none; }\n#cs-root .cs-pending .cs-icon { margin-left: 3px; }\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: 2px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; 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: 8px; height: 8px; margin: 0; }\n#cs-root .cs-files { display: grid; gap: 5px; margin-top: 5px; }\n#cs-root .cs-thumbs { display: flex; flex-wrap: wrap; gap: 5px; }\n#cs-root .cs-thumb { display: block; width: 84px; height: 54px; padding: 0; border-radius: 6px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--cs-edge); background: var(--cs-fill); cursor: pointer; }\n#cs-root .cs-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }\n#cs-root .cs-shot { margin: 0; border-radius: 6px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--cs-edge); background: var(--cs-fill); }\n#cs-root .cs-shot-image { display: block; width: 100%; padding: 0; line-height: 0; cursor: pointer; }\n#cs-root .cs-shot-image img { width: 100%; max-height: 135px; object-fit: cover; object-position: top; display: block; }\n#cs-root .cs-thumb.is-open, #cs-root .cs-shot.is-open, #cs-root .cs-clip.is-open, #cs-root .cs-replay.is-open { box-shadow: 0 0 0 2px var(--cs-accent); }\n#cs-root .cs-shot-meta { display: grid; gap: 1px; padding: 4px 6px 5px; font-size: 10px; line-height: 1.35; color: var(--cs-text-3); }\n#cs-root .cs-shot-meta a { color: var(--cs-text-2); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-shot-meta a:hover { color: var(--cs-accent); text-decoration: underline; }\n#cs-root .cs-file {\n display: inline-flex; align-items: center; gap: 5px; max-width: 100%; padding: 5px 8px; border-radius: 6px;\n background: var(--cs-fill); color: var(--cs-text); text-decoration: none; font-size: 10px; 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#cs-root .cs-record-button { color: var(--cs-danger); }\n#cs-root .cs-clip { margin: 0; border-radius: 6px; overflow: hidden; box-shadow: 0 0 0 0.5px var(--cs-edge); background: #111; }\n#cs-root .cs-clip-open { position: relative; display: block; width: 100%; min-height: 54px; padding: 0; cursor: pointer; }\n#cs-root .cs-clip video { display: block; width: 100%; max-height: 165px; background: #111; pointer-events: none; }\n#cs-root .cs-clip-play {\n position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);\n width: 27px; height: 27px; border-radius: 999px; display: flex; align-items: center; justify-content: center;\n background: rgba(0, 0, 0, 0.55); color: #fff; pointer-events: none;\n}\n#cs-root .cs-media {\n display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden;\n background: #161618; color: #f5f5f7; border-radius: 14px; box-shadow: var(--cs-shadow);\n}\n#cs-root .cs-media-bar { display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 11px; flex: none; }\n#cs-root .cs-media-name { flex: 1; min-width: 0; font-size: 10px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-media-count { flex: none; font-size: 10px; color: #a1a1a6; font-variant-numeric: tabular-nums; }\n#cs-root .cs-media-bar .cs-icon-button { color: #a1a1a6; }\n#cs-root .cs-media-bar .cs-icon-button:hover { color: #fff; background: rgba(255, 255, 255, 0.12); }\n#cs-root .cs-media-bar .cs-icon-button:disabled:hover,\n#cs-root .cs-media-zoom-level:disabled:hover { color: #a1a1a6; background: transparent; }\n#cs-root .cs-media-zoom { display: flex; align-items: center; gap: 1px; flex: none; }\n#cs-root .cs-media-zoom-level {\n min-width: 33px; height: 20px; padding: 0 3px; border-radius: 999px;\n font-size: 10px; font-variant-numeric: tabular-nums; color: #a1a1a6;\n}\n#cs-root .cs-media-zoom-level:hover { color: #fff; background: rgba(255, 255, 255, 0.12); }\n#cs-root .cs-media-stage { position: relative; flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; padding: 0 33px 9px; overflow: hidden; }\n#cs-root .cs-media-stage.is-replay { align-items: flex-start; justify-content: flex-start; overflow: auto; }\n#cs-root .cs-media-stage.is-zoomed { touch-action: none; }\n#cs-root .cs-media-stage img, #cs-root .cs-media-stage video {\n width: 100%; height: 100%; object-fit: contain; border-radius: 5px;\n transform-origin: center center; transition: transform 0.12s ease; user-select: none;\n}\n#cs-root .cs-media-stage.is-zoomed img { cursor: grab; }\n#cs-root .cs-media-stage.is-panning img, #cs-root .cs-media-stage.is-panning video { cursor: grabbing; transition: none; }\n#cs-root .cs-media-nav {\n position: absolute; top: 50%; transform: translateY(-50%); z-index: 1;\n width: 24px; height: 24px; border-radius: 999px; display: flex; align-items: center; justify-content: center;\n background: rgba(255, 255, 255, 0.14); color: #fff;\n}\n#cs-root .cs-media-nav:hover { background: rgba(255, 255, 255, 0.24); }\n#cs-root .cs-media-nav.is-prev { left: 6px; }\n#cs-root .cs-media-nav.is-next { right: 6px; }\n#cs-root .cs-media { position: relative; }\n#cs-root .cs-media-resize { position: absolute; z-index: 2; width: 21px; height: 21px; touch-action: none; }\n#cs-root .cs-media-resize::after { content: \"\"; position: absolute; width: 8px; height: 8px; border: 0 solid #6e6e73; }\n#cs-root .cs-media-resize:hover::after { border-color: #fff; }\n#cs-root .cs-media-resize.is-bottom-right { right: 0; bottom: 0; cursor: nwse-resize; }\n#cs-root .cs-media-resize.is-bottom-right::after { right: 6px; bottom: 6px; border-right-width: 2px; border-bottom-width: 2px; border-bottom-right-radius: 2px; }\n#cs-root .cs-media-resize.is-bottom-left { left: 0; bottom: 0; cursor: nesw-resize; }\n#cs-root .cs-media-resize.is-bottom-left::after { left: 6px; bottom: 6px; border-left-width: 2px; border-bottom-width: 2px; border-bottom-left-radius: 2px; }\n#cs-root .cs-media-resize.is-top-right { right: 0; top: 0; cursor: nesw-resize; }\n#cs-root .cs-media-resize.is-top-right::after { right: 6px; top: 6px; border-right-width: 2px; border-top-width: 2px; border-top-right-radius: 2px; }\n#cs-root .cs-media-resize.is-top-left { left: 0; top: 0; cursor: nwse-resize; }\n#cs-root .cs-media-resize.is-top-left::after { left: 6px; top: 6px; border-left-width: 2px; border-top-width: 2px; border-top-left-radius: 2px; }\n#cs-root .cs-replay { display: grid; gap: 3px; width: 100%; padding: 6px 8px; border-radius: 6px; background: var(--cs-fill); text-align: left; color: inherit; font: inherit; }\n#cs-root button.cs-replay { cursor: pointer; }\n#cs-root .cs-replay-title { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; text-transform: uppercase; color: var(--cs-text-3); }\n#cs-root .cs-replay-status { margin: 0; font-size: 10px; color: var(--cs-text-2); }\n#cs-root .cs-replay ol { list-style: none; display: grid; gap: 2px; margin: 0; padding: 0; max-height: 120px; overflow: auto; }\n#cs-root .cs-replay li { display: grid; grid-template-columns: 27px 1fr; gap: 6px; font-size: 10px; }\n#cs-root .cs-replay time { font-variant-numeric: tabular-nums; color: var(--cs-text-3); }\n#cs-root .cs-media .cs-replay { background: transparent; color: #f5f5f7; }\n#cs-root .cs-media .cs-replay ol { max-height: none; overflow: visible; }\n#cs-root .cs-media .cs-replay-title, #cs-root .cs-media .cs-replay time, #cs-root .cs-media .cs-replay-status { color: #a1a1a6; }\n#cs-root .cs-media .cs-replay-status a { color: #f5f5f7; }\n\n/* Dashboard — a near-fullscreen inbox: filters, a list, and the open thread. */\n#cs-root .cs-dash-scrim {\n position: absolute; inset: 0; z-index: 6; background: rgba(0, 0, 0, 0.28); pointer-events: auto;\n}\n#cs-root.cs-dark .cs-dash-scrim { background: rgba(0, 0, 0, 0.55); }\n#cs-root .cs-dashboard {\n z-index: 7; top: 12px; left: 12px; right: 12px; bottom: 63px; width: auto; max-height: none;\n display: flex; flex-direction: column; overflow: hidden; border-radius: 14px;\n background: #f3f4f6; color: #1d1d1f;\n}\n#cs-root.cs-dark .cs-dashboard { background: #1c1c1e; color: #f5f5f7; }\n#cs-root .cs-dashboard { flex-direction: row; }\n#cs-root .cs-dash-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }\n#cs-root .cs-dash-sidebar {\n flex: none; width: 147px; display: flex; flex-direction: column; gap: 2px; padding: 11px 8px;\n border-right: 1px solid rgba(0, 0, 0, 0.06); background: rgba(255, 255, 255, 0.55);\n}\n#cs-root.cs-dark .cs-dash-sidebar { border-right-color: rgba(255, 255, 255, 0.06); background: rgba(255, 255, 255, 0.03); }\n#cs-root .cs-dash-sidebar-env {\n margin: 0 0 6px; padding: 5px 8px; font-size: 10px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;\n color: #8e8e93; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n#cs-root .cs-dash-nav {\n display: flex; align-items: center; gap: 8px; width: 100%; height: 26px; padding: 0 8px; border-radius: 7px;\n text-align: left; color: #3a3a3c; font-size: 11px; font-weight: 500;\n}\n#cs-root.cs-dark .cs-dash-nav { color: #d1d1d6; }\n#cs-root .cs-dash-nav:hover { background: rgba(0, 0, 0, 0.05); }\n#cs-root.cs-dark .cs-dash-nav:hover { background: rgba(255, 255, 255, 0.06); }\n#cs-root .cs-dash-nav.is-on { background: #fff; color: #1d1d1f; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06); }\n#cs-root.cs-dark .cs-dash-nav.is-on { background: #2c2c2e; color: #f5f5f7; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }\n#cs-root .cs-dash-nav span { flex: 1; }\n#cs-root .cs-dash-nav em {\n font-style: normal; font-size: 10px; font-variant-numeric: tabular-nums; min-width: 15px; padding: 1px 5px;\n border-radius: 999px; background: #007aff; color: #fff; text-align: center;\n}\n#cs-root .cs-dash-pane {\n width: 100%; max-width: 390px; padding: 6px; border-radius: 12px; background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);\n}\n#cs-root.cs-dark .cs-dash-pane { background: #2c2c2e; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06); }\n#cs-root .cs-dash-pane .cs-hint { padding: 5px 6px 3px; }\n#cs-root .cs-dash-pane .cs-settings.is-inline .cs-settings-head { padding: 5px 6px 3px; }\n@media (max-width: 720px) {\n #cs-root .cs-dash-sidebar { width: 42px; padding: 11px 6px; }\n #cs-root .cs-dash-sidebar-env, #cs-root .cs-dash-nav span, #cs-root .cs-dash-nav em { display: none; }\n #cs-root .cs-dash-nav { justify-content: center; padding: 0; }\n}\n#cs-root .cs-insight-title { flex: none; margin: 0; font-size: 12px; font-weight: 650; letter-spacing: -0.02em; }\n#cs-root .cs-insights {\n flex: 1 1 auto; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 9px; padding: 2px 11px 11px;\n}\n#cs-root .cs-insight-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 8px; }\n#cs-root .cs-insight-stat {\n display: grid; align-content: center; gap: 2px; min-height: 63px; padding: 11px 12px; border-radius: 12px; text-align: left; color: inherit;\n background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);\n}\n#cs-root button.cs-insight-stat:hover { box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.14); }\n#cs-root .cs-insight-stat b { font-size: 21px; line-height: 1.1; font-weight: 650; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }\n#cs-root .cs-insight-stat span { color: #6e6e73; font-size: 11px; }\n#cs-root .cs-insight-card {\n min-width: 0; padding: 11px 12px 5px; border-radius: 12px; background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);\n}\n#cs-root .cs-insight-card > header { display: flex; align-items: baseline; justify-content: space-between; gap: 9px; margin-bottom: 3px; }\n#cs-root .cs-insight-card h3 { margin: 0; font-size: 11px; font-weight: 650; }\n#cs-root .cs-insight-legend { display: flex; align-items: center; gap: 5px; margin: 0; color: #6e6e73; font-size: 10px; }\n#cs-root .cs-insight-legend i { width: 6px; height: 6px; border-radius: 99px; }\n#cs-root .cs-insight-legend i.is-done { margin-left: 5px; }\n#cs-root .cs-insight-row {\n display: grid; grid-template-columns: 21px minmax(0, 1fr) auto; gap: 8px; align-items: center; width: 100%;\n padding: 6px 0; text-align: left; color: inherit; border-top: 1px solid rgba(0, 0, 0, 0.05);\n}\n#cs-root .cs-insight-row:hover strong { color: #007aff; }\n#cs-root .cs-insight-who { min-width: 0; }\n#cs-root .cs-insight-who strong, #cs-root .cs-insight-who small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n#cs-root .cs-insight-who strong { font-size: 11px; font-weight: 600; }\n#cs-root .cs-insight-who small { margin-top: 1px; color: #8e8e93; font-size: 10px; }\n#cs-root .cs-insight-row em { font-style: normal; font-variant-numeric: tabular-nums; color: #6e6e73; font-size: 10px; white-space: nowrap; }\n#cs-root .cs-insight-bar { display: flex; height: 5px; margin-top: 5px; border-radius: 99px; background: rgba(0, 0, 0, 0.06); overflow: hidden; }\n#cs-root .cs-insight-bar i { display: block; height: 100%; flex: none; }\n#cs-root .cs-insight-legend i.is-open, #cs-root .cs-insight-bar i.is-open { background: #34c759; }\n#cs-root .cs-insight-legend i.is-done, #cs-root .cs-insight-bar i.is-done { background: #c7c7cc; }\n#cs-root .cs-insight-recent { display: grid; grid-template-columns: 1fr 1fr; }\n#cs-root .cs-insight-recent .cs-insight-row { padding-right: 12px; }\n#cs-root .cs-insight-stat.is-mention {\n background: var(--cs-accent-soft); box-shadow: 0 0 0 1px rgba(0, 122, 255, 0.35);\n}\n#cs-root .cs-insight-stat.is-mention b, #cs-root .cs-insight-stat.is-mention span { color: var(--cs-accent); }\n#cs-root button.cs-insight-stat.is-mention:hover { box-shadow: 0 0 0 1px var(--cs-accent); }\n#cs-root.cs-dark .cs-insight-stat, #cs-root.cs-dark .cs-insight-card { background: #2c2c2e; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06); }\n#cs-root.cs-dark .cs-insight-stat.is-mention { background: var(--cs-accent-soft); box-shadow: 0 0 0 1px rgba(10, 132, 255, 0.45); }\n#cs-root.cs-dark .cs-insight-stat span, #cs-root.cs-dark .cs-insight-legend, #cs-root.cs-dark .cs-insight-row em { color: #a1a1a6; }\n#cs-root.cs-dark .cs-insight-stat.is-mention b, #cs-root.cs-dark .cs-insight-stat.is-mention span { color: var(--cs-accent); }\n#cs-root.cs-dark .cs-insight-row { border-top-color: rgba(255, 255, 255, 0.06); }\n#cs-root.cs-dark .cs-insight-bar { background: rgba(255, 255, 255, 0.08); }\n@media (max-width: 860px) {\n #cs-root .cs-insight-stats, #cs-root .cs-insight-recent { grid-template-columns: 1fr 1fr; }\n}\n#cs-root .cs-dash-top {\n display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 11px 11px 8px; flex: none;\n}\n#cs-root .cs-dash-pills {\n display: flex; align-items: center; gap: 5px; min-width: 0; overflow-x: auto; scrollbar-width: none;\n}\n#cs-root .cs-dash-pills::-webkit-scrollbar { display: none; }\n#cs-root .cs-dash-pills [role=\"radiogroup\"] { display: contents; }\n#cs-root .cs-dash-sep { width: 1px; height: 14px; margin: 0 2px; background: rgba(0, 0, 0, 0.12); flex: none; }\n#cs-root.cs-dark .cs-dash-sep { background: rgba(255, 255, 255, 0.16); }\n#cs-root .cs-dash-tools { display: flex; align-items: center; gap: 5px; margin-left: auto; }\n#cs-root .cs-dash-pill {\n display: inline-flex; align-items: center; gap: 3px; height: 24px; padding: 0 9px; border-radius: 999px;\n background: #fff; color: #1d1d1f; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06); font-size: 11px; font-weight: 500;\n white-space: nowrap; flex: none;\n}\n#cs-root.cs-dark .cs-dash-pill { background: #2c2c2e; color: #f5f5f7; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }\n#cs-root .cs-dash-pill.is-on { background: #1d1d1f; color: #fff; box-shadow: none; }\n#cs-root.cs-dark .cs-dash-pill.is-on { background: #f5f5f7; color: #1d1d1f; }\n#cs-root .cs-dash-pill .cs-icon { width: 11px; height: 11px; }\n#cs-root .cs-dash-icon {\n width: 24px; height: 24px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center;\n background: #fff; color: #6e6e73; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);\n}\n#cs-root.cs-dark .cs-dash-icon { background: #2c2c2e; color: #a1a1a6; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08); }\n#cs-root .cs-dash-icon:hover, #cs-root .cs-dash-icon.is-on { color: #1d1d1f; }\n#cs-root.cs-dark .cs-dash-icon:hover, #cs-root.cs-dark .cs-dash-icon.is-on { color: #f5f5f7; }\n#cs-root .cs-dash-menu { position: relative; }\n#cs-root .cs-dash-menu-pop {\n position: absolute; z-index: 3; top: calc(100% + 5px); right: 0; min-width: 135px; padding: 4px;\n border-radius: 9px; background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08), 0 9px 24px rgba(0, 0, 0, 0.12);\n}\n#cs-root.cs-dark .cs-dash-menu-pop { background: #2c2c2e; }\n#cs-root .cs-dash-menu-pop button {\n display: flex; width: 100%; align-items: center; height: 24px; padding: 0 8px; border-radius: 6px;\n text-align: left; font-size: 11px; color: inherit;\n}\n#cs-root .cs-dash-menu-pop button:hover, #cs-root .cs-dash-menu-pop button.is-on { background: rgba(0, 0, 0, 0.05); }\n#cs-root.cs-dark .cs-dash-menu-pop button:hover, #cs-root.cs-dark .cs-dash-menu-pop button.is-on { background: rgba(255, 255, 255, 0.08); }\n#cs-root .cs-dash-top .cs-env-filter { margin: 0; }\n#cs-root .cs-dash-body {\n flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(225px, 42%) minmax(0, 1fr);\n gap: 9px; padding: 2px 9px 9px;\n}\n#cs-root .cs-dash-list { min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 6px; padding-right: 2px; }\n#cs-root .cs-dash-row {\n display: flex; align-items: flex-start; gap: 8px; padding: 9px 9px 9px 8px; border-radius: 11px;\n background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);\n}\n#cs-root.cs-dark .cs-dash-row { background: #2c2c2e; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06); }\n#cs-root .cs-dash-row.is-on { background: #eef1ff; box-shadow: 0 0 0 1px rgba(88, 101, 242, 0.22); }\n#cs-root.cs-dark .cs-dash-row.is-on { background: rgba(88, 101, 242, 0.18); }\n#cs-root .cs-dash-check {\n width: 12px; height: 12px; margin: 6px 0 0; padding: 0; flex: none; border-radius: 3px;\n accent-color: #1d1d1f; box-shadow: none; background: #fff;\n}\n#cs-root .cs-dash-check:focus { box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.25); }\n#cs-root .cs-dash-open {\n flex: 1; min-width: 0; display: grid; grid-template-columns: 27px minmax(0, 1fr) auto; gap: 8px; text-align: left;\n}\n#cs-root .cs-dash-copy { min-width: 0; display: grid; gap: 2px; }\n#cs-root .cs-dash-copy strong { font-size: 11px; font-weight: 650; letter-spacing: -0.01em; }\n#cs-root .cs-dash-copy p {\n color: #6e6e73; font-size: 10px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;\n}\n#cs-root.cs-dark .cs-dash-copy p { color: #a1a1a6; }\n#cs-root .cs-dash-tags { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 5px; }\n#cs-root .cs-dash-tag {\n display: inline-flex; align-items: center; max-width: 100%; height: 17px; padding: 0 6px; border-radius: 999px;\n background: rgba(0, 0, 0, 0.05); color: #6e6e73; font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n}\n#cs-root .cs-dash-tag.is-page {\n flex: 1 0 100%; max-width: 100%; height: auto; min-height: 17px; white-space: normal; overflow: visible;\n text-overflow: unset; overflow-wrap: anywhere; line-height: 1.35; padding-top: 2px; padding-bottom: 2px;\n}\n#cs-root.cs-dark .cs-dash-tag { background: rgba(255, 255, 255, 0.08); color: #a1a1a6; }\n#cs-root .cs-dash-tag.is-mention, #cs-root.cs-dark .cs-dash-tag.is-mention {\n background: var(--cs-accent-soft); color: var(--cs-accent); font-weight: 600;\n}\n#cs-root .cs-dash-side { display: grid; justify-items: end; gap: 2px; min-width: 54px; padding-top: 1px; }\n#cs-root .cs-dash-side b { font-weight: 500; font-size: 10px; }\n#cs-root .cs-dash-side time { color: #a1a1a6; font-size: 10px; }\n#cs-root .cs-dash-state { display: inline-flex; align-items: center; gap: 4px; margin-top: 3px; font-size: 10px; font-weight: 500; color: #3a3a3c; }\n#cs-root.cs-dark .cs-dash-state { color: #d2d2d7; }\n#cs-root .cs-dash-state.is-done { color: #8e8e93; }\n#cs-root .cs-dash-dot { width: 5px; height: 5px; border-radius: 999px; background: #34c759; }\n#cs-root .cs-dash-state.is-done .cs-dash-dot { background: #c7c7cc; }\n#cs-root .cs-dash-detail {\n min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; border-radius: 12px;\n background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05);\n}\n#cs-root.cs-dark .cs-dash-detail { background: #232325; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.06); }\n#cs-root .cs-dash-detail-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 15px 6px; }\n#cs-root .cs-dash-title { display: flex; align-items: flex-start; gap: 9px; }\n#cs-root .cs-dash-title h2 { flex: 1; font-size: 15px; line-height: 1.25; font-weight: 650; letter-spacing: -0.02em; }\n#cs-root .cs-dash-status { margin-left: auto; }\n#cs-root .cs-dash-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 9px; color: #6e6e73; font-size: 11px; }\n#cs-root .cs-dash-meta time, #cs-root .cs-dash-meta span { white-space: nowrap; }\n#cs-root .cs-dash-meta .cs-dash-tag { height: 18px; }\n#cs-root .cs-dash-meta .cs-dash-tag.is-page { height: auto; min-height: 18px; white-space: normal; }\n#cs-root .cs-dash-body-text { margin-top: 11px; font-size: 11px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }\n#cs-root .cs-dash-media { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 11px; }\n#cs-root .cs-dash-shot {\n width: 165px; height: 99px; padding: 0; border-radius: 9px; overflow: hidden; background: #f2f2f7;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);\n}\n#cs-root .cs-dash-shot img, #cs-root .cs-dash-shot video { width: 100%; height: 100%; object-fit: cover; display: block; }\n#cs-root .cs-dash-add {\n width: 126px; height: 99px; border-radius: 9px; display: grid; place-items: center; gap: 5px;\n color: #8e8e93; font-size: 11px; background: transparent; border: 1px dashed rgba(0, 0, 0, 0.16);\n}\n#cs-root .cs-dash-add:hover { color: #1d1d1f; border-color: rgba(0, 0, 0, 0.35); }\n#cs-root.cs-dark .cs-dash-add { border-color: rgba(255, 255, 255, 0.2); }\n#cs-root .cs-dash-file {\n display: flex; align-items: center; gap: 8px; min-width: 165px; max-width: 210px; padding: 6px 8px; border-radius: 9px;\n background: #f6f6f8; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05); text-align: left; color: inherit; text-decoration: none;\n}\n#cs-root.cs-dark .cs-dash-file { background: #2c2c2e; }\n#cs-root .cs-dash-file strong, #cs-root .cs-dash-file small { display: block; }\n#cs-root .cs-dash-file strong { font-size: 11px; font-weight: 600; }\n#cs-root .cs-dash-file small { color: #8e8e93; font-size: 10px; }\n#cs-root .cs-dash-play {\n width: 27px; height: 21px; border-radius: 5px; display: grid; place-items: center; background: #1d1d1f; color: #fff; flex: none;\n}\n#cs-root .cs-dash-tabs { display: flex; gap: 14px; margin-top: 14px; border-bottom: 1px solid rgba(0, 0, 0, 0.08); }\n#cs-root.cs-dark .cs-dash-tabs { border-bottom-color: rgba(255, 255, 255, 0.08); }\n#cs-root .cs-dash-tabs button {\n height: 27px; margin-bottom: -1px; border-bottom: 2px solid transparent; color: #8e8e93; font-size: 11px; font-weight: 600;\n}\n#cs-root .cs-dash-tabs button.is-on { color: #1d1d1f; border-bottom-color: #1d1d1f; }\n#cs-root.cs-dark .cs-dash-tabs button.is-on { color: #f5f5f7; border-bottom-color: #f5f5f7; }\n#cs-root .cs-dash-thread { display: grid; gap: 11px; padding: 11px 0 6px; }\n#cs-root .cs-dash-note { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 8px; }\n#cs-root .cs-dash-note header { display: flex; align-items: baseline; gap: 6px; }\n#cs-root .cs-dash-note header strong { font-size: 11px; }\n#cs-root .cs-dash-note header time { color: #8e8e93; font-size: 10px; }\n#cs-root .cs-dash-note p { margin-top: 2px; white-space: pre-wrap; overflow-wrap: anywhere; }\n#cs-root .cs-dash-reply {\n display: flex; align-items: flex-end; gap: 6px; flex: none; margin: 0 9px 9px; padding: 6px 6px 6px 8px; border-radius: 11px;\n background: #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);\n}\n#cs-root.cs-dark .cs-dash-reply { background: #2c2c2e; }\n#cs-root .cs-dash-reply textarea {\n flex: 1; width: auto; min-width: 0; background: transparent; box-shadow: none; border-radius: 0;\n padding: 6px 3px; min-height: 32px; max-height: 90px; field-sizing: content;\n}\n#cs-root .cs-dash-reply textarea:focus { background: transparent; box-shadow: none; }\n#cs-root .cs-dash-reply .cs-icon-button { color: #8e8e93; }\n#cs-root .cs-dash-send {\n width: 24px; height: 24px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center;\n background: #007aff; color: #fff; flex: none;\n}\n#cs-root .cs-dash-send:disabled { background: rgba(0, 122, 255, 0.35); }\n#cs-root .cs-dash-empty { padding: 21px 6px; text-align: center; color: #8e8e93; }\n@media (max-width: 860px) {\n #cs-root .cs-dash-body { grid-template-columns: 1fr; }\n #cs-root .cs-dash-detail { min-height: 270px; }\n}\n\n/* Panels rise out of the bar. The individual translate and scale leave each panel's own centering transform alone. */\n#cs-root .cs-unlock, #cs-root .cs-menu, #cs-root .cs-sheet { transform-origin: 50% 100%; animation: cs-rise var(--cs-d3) var(--cs-ease) backwards; }\n#cs-root .cs-dashboard { transform-origin: 50% 100%; animation: cs-rise-wide var(--cs-d3) var(--cs-ease) backwards; }\n#cs-root .cs-dash-scrim { animation: cs-fade var(--cs-d3) var(--cs-ease) backwards; }\n@keyframes cs-rise { from { opacity: 0; translate: 0 8px; scale: 0.97; } }\n@keyframes cs-rise-wide { from { opacity: 0; translate: 0 8px; scale: 0.98; } }\n@keyframes cs-fade { from { opacity: 0; } }\n"], readonly ["overlay", "\n/* Page overlays */\n#cs-root .cs-pin {\n position: absolute;\n z-index: 1;\n transform: translate(-50%, -50%);\n padding: 2px;\n border-radius: 999px;\n background: var(--cs-card);\n box-shadow: 0 0 0 2px #34c759, 0 3px 11px rgba(0, 0, 0, 0.16);\n pointer-events: auto;\n transition: transform 0.18s ease, box-shadow 0.18s ease;\n}\n#cs-root .cs-pin:hover { transform: translate(-50%, -50%) scale(1.08); }\n#cs-root .cs-pin.is-active {\n z-index: 2;\n transform: translate(-50%, -50%) scale(1.08);\n box-shadow: 0 0 0 2px var(--cs-accent), 0 0 0 5px var(--cs-accent-soft), 0 6px 17px rgba(0, 0, 0, 0.2);\n}\n#cs-root .cs-pin.is-drifted { box-shadow: 0 0 0 1px var(--cs-warn), 0 2px 8px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin.is-linked { box-shadow: 0 0 0 2px var(--cs-linked), 0 2px 8px rgba(0, 0, 0, 0.18); }\n#cs-root .cs-pin.is-drifted.is-active,\n#cs-root .cs-pin.is-linked.is-active {\n z-index: 2;\n transform: translate(-50%, -50%) scale(1.08);\n box-shadow: 0 0 0 2px var(--cs-accent), 0 0 0 5px var(--cs-accent-soft), 0 6px 17px rgba(0, 0, 0, 0.2);\n}\n#cs-root .cs-pin-env {\n position: absolute; left: 50%; bottom: -7px; transform: translateX(-50%); padding: 0 3px; height: 10px;\n border-radius: 999px; background: var(--cs-linked); color: #fff; font-size: 9px; font-weight: 700; line-height: 10px;\n text-transform: uppercase; letter-spacing: 0.03em; white-space: nowrap;\n}\n#cs-root .cs-pin-count {\n position: absolute; top: -4px; right: -5px; min-width: 11px; height: 11px; padding: 0 3px;\n border-radius: 999px; background: var(--cs-text); color: var(--cs-card);\n font-size: 9px; font-weight: 700; line-height: 11px; text-align: center;\n}\n#cs-root .cs-peek {\n position: absolute; z-index: 3; width: min(195px, calc(100% - 12px)); padding: 8px 9px; border-radius: 11px;\n background: var(--cs-card); box-shadow: var(--cs-shadow); pointer-events: none;\n}\n#cs-root .cs-focus {\n z-index: 4;\n width: min(255px, calc(100% - 12px));\n display: flex;\n flex-direction: column;\n border-radius: 14px;\n overflow: visible;\n}\n#cs-root .cs-media-pop {\n position: absolute; top: 0; z-index: 2; display: flex; pointer-events: auto;\n width: min(360px, 42vw); height: min(480px, calc(100vh - 105px));\n}\n#cs-root .cs-media-pop.is-right { left: calc(100% + 8px); }\n#cs-root .cs-media-pop.is-left { right: calc(100% + 8px); }\n#cs-root .cs-media-pop > .cs-media { flex: 1; width: 100%; height: 100%; }\n#cs-root .cs-sheet-anchor { position: absolute; inset: 0; pointer-events: none; }\n#cs-root .cs-sheet-anchor > .cs-sheet { pointer-events: auto; }\n#cs-root .cs-media-dock { position: absolute; z-index: 5; display: flex; pointer-events: none; }\n#cs-root .cs-media-dock > .cs-media { position: relative; right: auto; bottom: auto; width: 100%; height: 100%; pointer-events: auto; }\n#cs-root .cs-sheet-anchor > .cs-media {\n position: absolute; pointer-events: auto;\n top: auto; bottom: 9px;\n height: calc(100% - 18px);\n}\n#cs-root.cs-at-center .cs-sheet-anchor > .cs-media {\n right: calc(50% + min(128px, 50vw - 9px) + 9px); left: auto; width: max(165px, calc(50% - min(128px, 50vw - 9px) - 18px));\n}\n#cs-root.cs-at-right .cs-sheet-anchor > .cs-media {\n right: calc(12px + min(255px, 100vw - 18px) + 9px); left: auto; width: max(165px, calc(100% - min(255px, 100vw - 18px) - 30px));\n}\n#cs-root.cs-at-left .cs-sheet-anchor > .cs-media {\n left: calc(12px + min(255px, 100vw - 18px) + 9px); right: auto; width: max(165px, calc(100% - min(255px, 100vw - 18px) - 30px));\n}\n#cs-root .cs-focus-thread { display: flex; flex-direction: column; min-height: 0; }\n#cs-root .cs-focus-head {\n display: flex; align-items: center; gap: 8px; padding: 9px 8px 0 11px; flex: none;\n}\n#cs-root .cs-focus-ring {\n display: inline-flex; padding: 2px; border-radius: 999px; flex: none;\n box-shadow: 0 0 0 2px #34c759;\n}\n#cs-root .cs-focus-ring.is-done { box-shadow: 0 0 0 2px var(--cs-text-3); }\n#cs-root .cs-focus-ring.is-drifted { box-shadow: 0 0 0 2px var(--cs-warn); }\n#cs-root .cs-focus-ring.is-linked { box-shadow: 0 0 0 2px var(--cs-linked); }\n#cs-root .cs-focus-who {\n display: flex; align-items: center; gap: 5px; min-width: 0; flex: 1; flex-wrap: wrap;\n}\n#cs-root .cs-focus-who strong { font-size: 11px; font-weight: 600; letter-spacing: -0.015em; }\n#cs-root .cs-focus-who time { color: var(--cs-text-3); font-size: 10px; }\n#cs-root .cs-focus-tools { display: flex; align-items: center; gap: 1px; margin-left: auto; flex: none; }\n#cs-root .cs-focus-tools .cs-icon-button { width: 21px; height: 21px; color: var(--cs-text-3); }\n#cs-root .cs-focus-tools .cs-icon-button:hover { color: var(--cs-text); background: var(--cs-fill); }\n#cs-root .cs-focus-tools .cs-icon-button[data-cs-ui=resolve]:hover { color: #34c759; background: rgba(52, 199, 89, 0.14); }\n#cs-root .cs-focus-tools .cs-icon-button.is-done { color: #34c759; }\n#cs-root .cs-focus-menu {\n position: absolute; top: 35px; right: 9px; z-index: 2; width: 141px; padding: 3px;\n border-radius: 9px; background: var(--cs-card); box-shadow: var(--cs-shadow);\n}\n#cs-root .cs-focus-scroll {\n overflow: auto; min-height: 0; max-height: min(270px, calc(100vh - 165px));\n padding: 3px 11px 9px; overscroll-behavior: contain;\n}\n#cs-root .cs-focus-body { padding-left: 30px; }\n#cs-root .cs-focus-body .cs-text { margin-top: 0; font-size: 11px; line-height: 1.45; letter-spacing: -0.011em; }\n#cs-root .cs-focus-replies {\n display: grid; gap: 9px; margin-top: 9px; padding-top: 9px; border-top: 1px solid var(--cs-line);\n}\n#cs-root .cs-focus .cs-linked-note { display: block; margin: 8px 0 0; }\n#cs-root .cs-focus-foot {\n flex: none; padding: 0 8px 8px; border-top: 1px solid var(--cs-line);\n}\n#cs-root .cs-focus-foot .cs-reply {\n margin-top: 6px; padding: 2px 2px 2px 3px; border-radius: 17px; background: var(--cs-fill); align-items: flex-end;\n}\n#cs-root .cs-focus-foot .cs-reply textarea {\n background: transparent; box-shadow: none; border-radius: 0; min-height: 32px; padding: 5px 5px;\n}\n#cs-root .cs-focus-foot .cs-reply textarea:focus { background: transparent; box-shadow: none; }\n#cs-root .cs-focus-foot .cs-reply:focus-within {\n background: transparent; box-shadow: 0 0 0 1px var(--cs-accent), 0 0 0 3px var(--cs-accent-soft);\n}\n#cs-root .cs-focus-foot .cs-tray { padding: 6px 5px 0; }\n#cs-root .cs-focus .cs-details-edit { margin-top: 8px; }\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: 6px; 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: 270px; padding: 2px 6px; border-radius: 5px; pointer-events: none;\n background: var(--cs-accent); color: #fff; font-size: 10px; font-weight: 500;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n#cs-root .cs-banner {\n position: absolute; top: 11px; left: 50%; transform: translateX(-50%);\n display: flex; align-items: center; gap: 6px; padding: 4px 4px 4px 9px; border-radius: 999px;\n background: var(--cs-glass); -webkit-backdrop-filter: saturate(180%) blur(15px); backdrop-filter: saturate(180%) blur(15px);\n box-shadow: var(--cs-shadow); font-weight: 500; white-space: nowrap;\n}\n#cs-root .cs-banner button { padding: 2px 8px; border-radius: 999px; background: var(--cs-fill); font-size: 10px; }\n#cs-root .cs-banner button:hover { background: var(--cs-fill-2); }\n#cs-root .cs-toast {\n position: absolute; top: 11px; left: 50%; transform: translateX(-50%); padding: 5px 9px; border-radius: 999px;\n background: rgba(29, 29, 31, 0.9); color: #fff; font-size: 10px; font-weight: 500;\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); pointer-events: none; white-space: nowrap;\n}\n#cs-root .cs-banner, #cs-root .cs-toast { animation: cs-drop var(--cs-d3) var(--cs-spring) backwards; }\n@keyframes cs-drop { from { opacity: 0; translate: 0 -8px; scale: 0.96; } }\n/* The comment outline glides from element to element instead of jumping. While picking, it moves with a transform\n (composited, no layout or paint per frame) and only its size is laid out. */\n#cs-root .cs-outline.is-hover, #cs-root .cs-outline-label.is-hover {\n top: 0; left: 0; will-change: transform;\n transition: transform 140ms var(--cs-ease);\n}\n\n/* Docked inside an open <dialog> */\n#cs-root.cs-inside .cs-bar { bottom: 6px; }\n#cs-root.cs-inside.cs-at-left .cs-bar { left: 6px; }\n#cs-root.cs-inside.cs-at-right .cs-bar { right: 6px; }\n#cs-root.cs-inside .cs-sheet { bottom: 6px; max-height: 70%; width: min(225px, calc(100% - 12px)); }\n#cs-root.cs-inside.cs-at-center .cs-sheet { left: 50%; }\n#cs-root.cs-inside .cs-sheet-anchor > .cs-media {\n bottom: 6px;\n height: calc(100% - 12px);\n}\n#cs-root.cs-inside .cs-unlock, #cs-root.cs-inside .cs-menu { bottom: 45px; }\n#cs-root.cs-inside .cs-toast { top: 6px; }\n\n/* Recording hides the rest of the toolbar so the take is the page, not the comment UI. */\n#cs-root.is-recording > :not(.cs-record) { visibility: hidden; }\n#cs-root .cs-record {\n position: fixed; top: 12px; left: 50%; transform: translateX(-50%);\n display: flex; align-items: center; gap: 8px; max-width: calc(100% - 18px);\n padding: 6px 6px 6px 11px; border-radius: 999px; background: var(--cs-card); box-shadow: var(--cs-shadow);\n}\n#cs-root .cs-record-dot {\n width: 6px; height: 6px; border-radius: 999px; flex: none; background: var(--cs-danger);\n box-shadow: 0 0 0 3px rgba(255, 59, 48, 0.22);\n}\n#cs-root .cs-record-time { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 2.6em; }\n#cs-root .cs-record-note { color: var(--cs-text-2); font-size: 10px; white-space: nowrap; }\n#cs-root .cs-review {\n top: 50%; transform: translate(-50%, -50%); flex-direction: column; align-items: stretch;\n width: min(420px, calc(100% - 24px)); padding: 11px; border-radius: 12px; gap: 8px;\n}\n#cs-root .cs-review-head h2 { font-size: 11px; font-weight: 600; }\n#cs-root .cs-review-head p, #cs-root .cs-review .cs-record-note { white-space: normal; }\n#cs-root .cs-review video { width: 100%; max-height: min(270px, 50vh); background: #111; border-radius: 8px; }\n/* A dragged toolbar snaps to an edge. The unlock card and the comments list follow it; a card someone already placed stays put. */\n#cs-root.cs-docked .cs-unlock,\n#cs-root.cs-docked .cs-menu,\n#cs-root.cs-docked .cs-sheet:not(.is-free) {\n transition: left 300ms var(--cs-ease), top 300ms var(--cs-ease), right 300ms var(--cs-ease), bottom 300ms var(--cs-ease);\n}\n#cs-root.cs-docked[data-edge=\"bottom\"] .cs-unlock,\n#cs-root.cs-docked[data-edge=\"bottom\"] .cs-menu {\n left: max(6px, min(var(--cs-dock-x), calc(100% - 216px)));\n right: auto; top: auto; bottom: calc(100% - var(--cs-dock-y) + 9px); transform: none;\n}\n#cs-root.cs-docked[data-edge=\"top\"] .cs-unlock,\n#cs-root.cs-docked[data-edge=\"top\"] .cs-menu {\n left: max(6px, min(var(--cs-dock-x), calc(100% - 216px)));\n right: auto; bottom: auto; top: calc(var(--cs-dock-y) + var(--cs-dock-h) + 9px); transform: none;\n}\n#cs-root.cs-docked[data-edge=\"left\"] .cs-unlock,\n#cs-root.cs-docked[data-edge=\"left\"] .cs-menu {\n left: calc(var(--cs-dock-x) + var(--cs-dock-w) + 9px);\n right: auto; bottom: auto; top: min(var(--cs-dock-y), max(6px, calc(100% - 165px))); transform: none;\n}\n#cs-root.cs-docked[data-edge=\"right\"] .cs-unlock,\n#cs-root.cs-docked[data-edge=\"right\"] .cs-menu {\n left: auto; right: calc(100% - var(--cs-dock-x) + 9px);\n bottom: auto; top: min(var(--cs-dock-y), max(6px, calc(100% - 165px))); transform: none;\n}\n#cs-root.cs-docked[data-edge=\"bottom\"] .cs-sheet:not(.is-free),\n#cs-root.cs-docked[data-edge=\"top\"] .cs-sheet:not(.is-free) {\n left: max(6px, min(var(--cs-dock-x), calc(100% - min(255px, 100% - 12px) - 6px)));\n right: auto; transform: none;\n}\n#cs-root.cs-docked[data-edge=\"top\"] .cs-sheet:not(.is-free) {\n top: calc(var(--cs-dock-y) + var(--cs-dock-h) + 6px);\n bottom: auto;\n max-height: calc(100% - var(--cs-dock-y) - var(--cs-dock-h) - 12px);\n}\n#cs-root.cs-docked[data-edge=\"left\"] .cs-sheet:not(.is-free) {\n left: calc(var(--cs-dock-x) + var(--cs-dock-w) + 6px);\n right: auto;\n top: min(var(--cs-dock-y), max(6px, calc(100% - 180px)));\n bottom: auto;\n max-height: calc(100% - min(var(--cs-dock-y), max(6px, calc(100% - 180px))) - 12px);\n transform: none;\n}\n#cs-root.cs-docked[data-edge=\"right\"] .cs-sheet:not(.is-free) {\n left: auto;\n right: calc(100% - var(--cs-dock-x) + 6px);\n top: min(var(--cs-dock-y), max(6px, calc(100% - 180px)));\n bottom: auto;\n max-height: calc(100% - min(var(--cs-dock-y), max(6px, calc(100% - 180px))) - 12px);\n transform: none;\n}\n#cs-root.cs-docked[data-side=\"start\"] .cs-sheet-anchor > .cs-media {\n left: auto; right: 9px; width: max(165px, calc(100% - min(255px, 100% - 12px) - 30px));\n}\n#cs-root.cs-docked[data-side=\"end\"] .cs-sheet-anchor > .cs-media {\n right: auto; left: 9px; width: max(165px, calc(100% - min(255px, 100% - 12px) - 30px));\n}\n#cs-root.cs-docked[data-edge=\"top\"] .cs-sheet-anchor > .cs-media,\n#cs-root.cs-docked[data-edge=\"left\"] .cs-sheet-anchor > .cs-media,\n#cs-root.cs-docked[data-edge=\"right\"] .cs-sheet-anchor > .cs-media {\n top: 9px; bottom: auto; height: calc(100% - 18px);\n}\n"]];
2
+ export type StylePart = (typeof sheets)[number][0];
3
+ /** Adds a part of the toolbar's CSS once, in its original order relative to the parts already added. */
4
+ export declare function injectStyles(part: StylePart): void;
5
+ export {};
@@ -0,0 +1,25 @@
1
+ import { listThreads } from "./client.js";
2
+ import type { Session } from "./session.js";
3
+ export type ThreadsPayload = Awaited<ReturnType<typeof listThreads>>;
4
+ export declare function usePageVisible(): boolean;
5
+ /**
6
+ * Keeps threads current with as few requests as possible. The live socket pushes "changed" and each push batch
7
+ * refetches once; nothing is fetched while the tab is hidden. Without a socket, threads are polled only while the
8
+ * tab is visible and something on screen shows them (`wanted`), backing off while nothing changes. A refetch that
9
+ * returns what is already shown doesn't touch state.
10
+ */
11
+ export declare function useThreadsSync({ session, member, pollMs, wanted, href, onThreads, onUnauthorized, onDisabled, }: {
12
+ session: Session | null;
13
+ member: string | null;
14
+ pollMs: number;
15
+ wanted: boolean;
16
+ href: string;
17
+ onThreads: (next: ThreadsPayload) => void;
18
+ onUnauthorized: () => void;
19
+ /** The project turned comments off. The sign-in is kept, so turning them back on needs nothing from anyone. */
20
+ onDisabled?: () => void;
21
+ }): {
22
+ live: boolean;
23
+ refresh: () => Promise<boolean>;
24
+ visible: boolean;
25
+ };
@@ -0,0 +1,22 @@
1
+ import { type GetSession } from "./session.js";
2
+ export type ToolbarPosition = "bottom-left" | "bottom-center" | "bottom-right";
3
+ export type ToolbarTheme = "light" | "dark" | "system";
4
+ export type ToolbarProps = {
5
+ sessionPath?: string;
6
+ pollMs?: number;
7
+ /** Default bar position. Dragging the bar snaps it to an edge and overrides this until a preset is chosen. */
8
+ position?: ToolbarPosition;
9
+ /** "system" follows the OS appearance. Light by default so it matches most host pages. */
10
+ theme?: ToolbarTheme;
11
+ /**
12
+ * Opens another page of the app, for example `(href) => router.navigate({ href })`.
13
+ * Without it, selecting a comment from another page does a full page load.
14
+ */
15
+ navigate?: (href: string) => void;
16
+ };
17
+ /** What the npm loader passes to `mount`: the host's props plus the session it already fetched. */
18
+ export type RuntimeProps = ToolbarProps & {
19
+ /** Older loaders don't pass this; the toolbar then reads the session route itself. */
20
+ getSession?: GetSession;
21
+ };
22
+ export declare function ToolbarApp({ sessionPath, getSession, pollMs, position, theme, navigate, }: RuntimeProps): import("react").ReactPortal | null;
@@ -0,0 +1,14 @@
1
+ export type CommentServiceConfig = {
2
+ url: string;
3
+ project: string;
4
+ environment: string;
5
+ secret: string;
6
+ /**
7
+ * Other environments of the project whose comments this one shows, live, alongside its own.
8
+ * Replies are allowed; resolving stays in the comment's own environment. Sent on every session, so this list is the
9
+ * whole truth: removing a name unlinks it.
10
+ */
11
+ linked?: string[];
12
+ };
13
+ export declare function readCommentServiceConfig(env?: Record<string, string | undefined>): CommentServiceConfig | null;
14
+ export declare function handleSessionRequest(request: Request, config?: CommentServiceConfig | null): Promise<Response>;
@@ -0,0 +1,2 @@
1
+ export { handleSessionRequest, readCommentServiceConfig } from "./server/index.js";
2
+ export type { CommentServiceConfig } from "./server/index.js";
@@ -0,0 +1,20 @@
1
+ type ResolvedConfig = {
2
+ command: "build" | "serve";
3
+ mode: string;
4
+ logger: {
5
+ info(message: string): void;
6
+ };
7
+ };
8
+ export declare function commentService(options?: {
9
+ enabled?: boolean;
10
+ root?: string;
11
+ }): {
12
+ name: string;
13
+ enforce: "pre";
14
+ configResolved(config: ResolvedConfig): void;
15
+ transform(code: string, id: string): {
16
+ code: string;
17
+ map: null;
18
+ } | null;
19
+ };
20
+ export {};
@@ -0,0 +1 @@
1
+ export { commentService } from "./vite/plugin.js";
package/dist/vite.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./types/vite.js";
package/dist/vite.js ADDED
@@ -0,0 +1,173 @@
1
+ // src/compiler/annotate.ts
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("/")) relative = relative.slice(1);
19
+ return relative;
20
+ }
21
+ function componentName(path2, fallback) {
22
+ let current = path2;
23
+ while (current) {
24
+ if (current.isFunctionDeclaration() && current.node.id) return current.node.id.name;
25
+ if (current.isClassDeclaration() && current.node.id) return current.node.id.name;
26
+ if (current.isFunctionExpression() || current.isArrowFunctionExpression()) {
27
+ const parent = current.parentPath;
28
+ if (parent?.isVariableDeclarator() && parent.node.id.type === "Identifier")
29
+ return parent.node.id.name;
30
+ if ((parent?.isObjectProperty() || parent?.isClassProperty()) && parent.node.key.type === "Identifier") {
31
+ return parent.node.key.name;
32
+ }
33
+ }
34
+ current = current.parentPath;
35
+ }
36
+ return fallback;
37
+ }
38
+ function isRootJsx(path2) {
39
+ const fn = path2.getFunctionParent();
40
+ if (!fn) return false;
41
+ let current = path2.parentPath;
42
+ while (current && current !== fn) {
43
+ if (current.isJSXElement()) return false;
44
+ current = current.parentPath;
45
+ }
46
+ return true;
47
+ }
48
+ function hasAttr(path2, name) {
49
+ return path2.node.openingElement.attributes.some(
50
+ (attr) => attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier" && attr.name.name === name
51
+ );
52
+ }
53
+ function setAttr(path2, name, value) {
54
+ if (hasAttr(path2, name)) return;
55
+ path2.node.openingElement.attributes.push(
56
+ t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value))
57
+ );
58
+ }
59
+ var unstampable = /^(?:React\.)?(?:Fragment|Suspense|StrictMode|Profiler|Activity|ViewTransition)$|\.(?:Provider|Consumer)$/;
60
+ function jsxName(name) {
61
+ if (t.isJSXIdentifier(name)) return name.name;
62
+ if (t.isJSXMemberExpression(name)) {
63
+ const object = jsxName(name.object);
64
+ return object ? `${object}.${name.property.name}` : null;
65
+ }
66
+ return null;
67
+ }
68
+ function annotateSource(code, filename, root) {
69
+ if (!filename.match(/\.[jt]sx$/)) return code;
70
+ if (filename.includes("node_modules")) return code;
71
+ let ast;
72
+ try {
73
+ ast = parse(code, {
74
+ sourceType: "module",
75
+ plugins: ["jsx", "typescript"]
76
+ });
77
+ } catch {
78
+ return code;
79
+ }
80
+ const file = toProjectPath(filename, root);
81
+ const fallback = file.split("/").pop()?.replace(/\.[jt]sx$/, "") || "component";
82
+ let changed = false;
83
+ traverse()(ast, {
84
+ JSXElement(path2) {
85
+ const tag = jsxName(path2.node.openingElement.name);
86
+ if (!tag || unstampable.test(tag)) return;
87
+ if (!isRootJsx(path2)) return;
88
+ const name = componentName(path2, fallback);
89
+ const before = path2.node.openingElement.attributes.length;
90
+ setAttr(path2, "data-cs-cmp", name);
91
+ setAttr(path2, "data-cs-file", file);
92
+ if (path2.node.openingElement.attributes.length !== before) changed = true;
93
+ }
94
+ });
95
+ if (!changed) return code;
96
+ const printed = generate()(ast, { retainLines: true, comments: true }, code);
97
+ return printed.code;
98
+ }
99
+
100
+ // src/compiler/enabled.ts
101
+ import fs from "node:fs";
102
+ import path from "node:path";
103
+ var CONFIG_KEY = "COMMENT_SERVICE_URL";
104
+ function stampingEnabled({
105
+ option,
106
+ flag,
107
+ command,
108
+ configured
109
+ }) {
110
+ if (option !== void 0) return option;
111
+ if (flag === "0" || flag === "false") return false;
112
+ if (flag === "1" || flag === "true") return true;
113
+ return command === "serve" || configured();
114
+ }
115
+ function buildConfigured(root, mode, env = process.env) {
116
+ if (env[CONFIG_KEY]) return true;
117
+ const read = (file) => {
118
+ try {
119
+ return fs.readFileSync(path.join(root, file), "utf8");
120
+ } catch {
121
+ return "";
122
+ }
123
+ };
124
+ const assignment = new RegExp(`^\\s*(?:export\\s+)?${CONFIG_KEY}\\s*=\\s*\\S`, "m");
125
+ if ([".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`].some(
126
+ (file) => assignment.test(read(file))
127
+ ))
128
+ return true;
129
+ const jsonKey = new RegExp(`^\\s*"${CONFIG_KEY}"\\s*:`, "m");
130
+ const tomlKey = new RegExp(`^\\s*${CONFIG_KEY}\\s*=`, "m");
131
+ return jsonKey.test(read("wrangler.jsonc") + read("wrangler.json")) || tomlKey.test(read("wrangler.toml"));
132
+ }
133
+
134
+ // src/vite/plugin.ts
135
+ function commentService(options) {
136
+ const root = (options?.root ?? process.cwd()).replace(/\/$/, "");
137
+ let enabled = options?.enabled ?? process.env.COMMENT_SERVICE_ENABLED !== "0";
138
+ let told = false;
139
+ return {
140
+ name: "devaccel-comment-service",
141
+ enforce: "pre",
142
+ configResolved(config) {
143
+ enabled = stampingEnabled({
144
+ option: options?.enabled,
145
+ flag: process.env.COMMENT_SERVICE_ENABLED,
146
+ command: config.command,
147
+ configured: () => buildConfigured(root, config.mode)
148
+ });
149
+ if (config.command === "build" && !enabled && !told) {
150
+ told = true;
151
+ config.logger.info(
152
+ `[comment-service] This build sees no ${CONFIG_KEY}, so components are not stamped. If it serves the comment toolbar, set COMMENT_SERVICE_ENABLED=1 or commentService({ enabled: true }).`
153
+ );
154
+ }
155
+ },
156
+ transform(code, id) {
157
+ if (!enabled) return null;
158
+ const file = id.split("?")[0];
159
+ if (!/\.[jt]sx$/.test(file) || file.includes("/node_modules/")) return null;
160
+ if (!file.startsWith(`${root}/`)) return null;
161
+ try {
162
+ const next = annotateSource(code, file, root);
163
+ return next === code ? null : { code: next, map: null };
164
+ } catch (error) {
165
+ console.warn("[comment-service] annotation skipped", file, error);
166
+ return null;
167
+ }
168
+ }
169
+ };
170
+ }
171
+ export {
172
+ commentService
173
+ };
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@devaccel-labs/app-comments-toolbar",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "In-app comment toolbar for host apps on MyStatusFlow App comments: Vite plugin, <CommentToolbar>, and the same-origin session route.",
5
+ "license": "UNLICENSED",
6
+ "files": [
7
+ "dist",
8
+ "README.md"
9
+ ],
10
+ "type": "module",
11
+ "exports": {
12
+ "./react": {
13
+ "types": "./dist/react.d.ts",
14
+ "default": "./dist/react.js"
15
+ },
16
+ "./vite": {
17
+ "types": "./dist/vite.d.ts",
18
+ "default": "./dist/vite.js"
19
+ },
20
+ "./server": {
21
+ "types": "./dist/server.d.ts",
22
+ "default": "./dist/server.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "registry": "https://registry.npmjs.org/"
29
+ },
30
+ "scripts": {
31
+ "build": "bun build.mjs",
32
+ "build:runtime": "bun ../../scripts/app-comments/build-toolbar.mjs",
33
+ "typecheck": "tsc --noEmit --incremental --tsBuildInfoFile tsconfig.tsbuildinfo",
34
+ "lint": "bun ../../scripts/lint-package.mjs",
35
+ "lint:type-aware": "bun ../../scripts/lint-package.mjs --type-aware",
36
+ "format:check": "oxfmt --ignore-path=../../.oxfmtignore --check package.json tsconfig.json src",
37
+ "lint:fix": "bun ../../scripts/lint-package.mjs --fix",
38
+ "format": "oxfmt --ignore-path=../../.oxfmtignore package.json tsconfig.json src"
39
+ },
40
+ "dependencies": {
41
+ "@babel/generator": "^7.28.5",
42
+ "@babel/parser": "^7.28.5",
43
+ "@babel/traverse": "^7.28.5",
44
+ "@babel/types": "^7.28.5",
45
+ "modern-screenshot": "4.7.0"
46
+ },
47
+ "devDependencies": {
48
+ "esbuild": "0.28.1"
49
+ },
50
+ "peerDependencies": {
51
+ "react": ">=18",
52
+ "react-dom": ">=18"
53
+ }
54
+ }