@ascribed/review 0.2.1-next.9 → 0.3.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 (45) hide show
  1. package/README.md +16 -6
  2. package/dist/github/index.d.ts +3 -2
  3. package/dist/github/index.js +1 -0
  4. package/dist/github/index.js.map +1 -1
  5. package/dist/github/prompt.d.ts +40 -0
  6. package/dist/github/prompt.js +115 -0
  7. package/dist/github/prompt.js.map +1 -0
  8. package/dist/github/requests.d.ts +4 -0
  9. package/dist/github/requests.js +5 -1
  10. package/dist/github/requests.js.map +1 -1
  11. package/dist/github/transport.d.ts +13 -1
  12. package/dist/github/transport.js +10 -1
  13. package/dist/github/transport.js.map +1 -1
  14. package/dist/marks/index.d.ts +1 -0
  15. package/dist/marks/index.js +1 -0
  16. package/dist/marks/index.js.map +1 -1
  17. package/dist/marks/title.d.ts +8 -0
  18. package/dist/marks/title.js +17 -0
  19. package/dist/marks/title.js.map +1 -0
  20. package/dist/names.d.ts +5 -0
  21. package/dist/names.js +5 -0
  22. package/dist/names.js.map +1 -1
  23. package/dist/overlay/index.d.ts +1 -1
  24. package/dist/overlay/index.js.map +1 -1
  25. package/dist/overlay/overlay.js +41 -7
  26. package/dist/overlay/overlay.js.map +1 -1
  27. package/dist/overlay/style.d.ts +1 -1
  28. package/dist/overlay/style.js +24 -16
  29. package/dist/overlay/style.js.map +1 -1
  30. package/dist/overlay/types.d.ts +16 -3
  31. package/dist/overlay/types.js.map +1 -1
  32. package/dist/place/place.js +3 -2
  33. package/dist/place/place.js.map +1 -1
  34. package/dist/prompt/index.d.ts +90 -0
  35. package/dist/prompt/index.js +301 -0
  36. package/dist/prompt/index.js.map +1 -0
  37. package/dist/shapes.d.ts +34 -2
  38. package/dist/shapes.js.map +1 -1
  39. package/dist/shared/git.d.ts +11 -0
  40. package/dist/shared/git.js +24 -1
  41. package/dist/shared/git.js.map +1 -1
  42. package/dist/shared/types.d.ts +16 -0
  43. package/dist/shared/types.js.map +1 -1
  44. package/package.json +7 -4
  45. package/src/marks/marks.css +54 -49
@@ -1 +1 @@
1
- export declare const OVERLAY_CSS = "\n:host {\n all: initial;\n --text: var(--ascribe-review-text, CanvasText);\n --muted: var(--ascribe-review-muted, #5b6475);\n --line: var(--ascribe-review-border, #d3d8e0);\n --surface: var(--ascribe-review-surface, Canvas);\n --thread: var(--ascribe-review-thread, #f5f7fb);\n --accent: var(--ascribe-review-focus, #1f6feb);\n --accent-text: var(--ascribe-review-accent-text, #ffffff);\n --input: var(--ascribe-review-input, var(--surface));\n --flash: var(--ascribe-review-flash, #fff6bf);\n --added: var(--ascribe-review-added, #1a7f37);\n --changed: var(--ascribe-review-changed, #8a5a00);\n --changed-background: var(--ascribe-review-changed-background, #fff1c2);\n --removed: var(--ascribe-review-removed, #c4222d);\n --removed-background: var(--ascribe-review-removed-background, #ffe4e2);\n --mono: var(--ascribe-review-mono, ui-monospace, monospace);\n font-family: var(--ascribe-review-font, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n font-size: 12.5px;\n line-height: 1.45;\n color: var(--text);\n}\n* {\n box-sizing: border-box;\n}\n[hidden] {\n display: none !important;\n}\nbutton,\ntextarea,\ninput {\n font: inherit;\n color: inherit;\n}\nbutton {\n cursor: pointer;\n}\nbutton:disabled,\nbutton[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.5;\n}\nbutton:focus-visible,\ntextarea:focus-visible,\ninput:focus-visible,\n.thread:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n/* Buttons */\n.ghost {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n padding: 2px 8px;\n font-size: 12px;\n min-height: 24px;\n}\n.ghost:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n border-color: var(--accent);\n color: var(--accent);\n}\n.primary {\n border: 1px solid var(--accent);\n background: var(--accent);\n color: var(--accent-text);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.danger {\n border: 1px solid var(--removed);\n background: var(--removed);\n color: var(--surface);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.link {\n border: 0;\n background: transparent;\n color: var(--accent);\n padding: 0;\n font-size: inherit;\n text-align: inherit;\n}\na.link {\n text-decoration: none;\n}\n.link:hover {\n text-decoration: underline;\n}\n.hint {\n color: var(--muted);\n font-size: 11.5px;\n}\n.refuse,\n.error {\n color: var(--removed);\n font-size: 12px;\n}\n.spacer {\n flex: 1;\n}\n\n/* The layer over the page: the column, markers' panel, dialogs. */\n.layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.layer > *,\n.column > * {\n pointer-events: auto;\n}\n.column {\n position: absolute;\n top: 0;\n width: 250px;\n pointer-events: none;\n}\n.slot {\n position: absolute;\n left: 0;\n right: 0;\n}\n.slot > .thread,\n.slot > .composer {\n margin: 0 0 6px;\n}\n.slot.linked .thread,\n.slot.linked .composer {\n border-color: var(--accent);\n}\n.highlight {\n position: absolute;\n pointer-events: none;\n background: color-mix(in srgb, var(--flash) 55%, transparent);\n border-radius: 3px;\n /* Over the page's text, a tint that keeps the text as it was: multiplied\n on a light page, screened on a dark one. */\n mix-blend-mode: var(--ascribe-review-blend, multiply);\n}\nsvg.connector {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: visible;\n pointer-events: none;\n}\nsvg.connector path {\n fill: none;\n stroke: var(--accent);\n stroke-width: 1.5;\n}\n\n/* The block tools: the Comment button, and the count marker at narrow widths. */\n.tool {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n font-size: 11.5px;\n padding: 0 7px;\n min-height: 22px;\n color: var(--accent);\n white-space: nowrap;\n}\n.hint-count {\n margin-inline-start: 2px;\n font-size: 11px;\n font-weight: 600;\n color: var(--accent);\n}\n\n.marker {\n border: 1px solid var(--accent);\n background: var(--surface);\n color: var(--accent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n padding: 0 8px;\n min-width: 30px;\n min-height: 24px;\n}\n\n/* Threads */\n.thread {\n background: var(--thread);\n border: 1px solid var(--line);\n border-radius: 6px;\n margin: 6px 0 10px;\n}\n.th {\n display: flex;\n flex-wrap: wrap;\n gap: 3px 8px;\n align-items: center;\n padding: 5px 9px 2px;\n font-size: 11.5px;\n color: var(--muted);\n}\n.acts {\n display: flex;\n flex-wrap: wrap;\n gap: 2px 12px;\n padding: 0 9px 5px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n}\n.thread.collapsed .acts {\n border-bottom: 0;\n}\n.orig,\n.inside {\n padding: 5px 9px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n color: var(--muted);\n}\n.orig q {\n color: var(--text);\n white-space: pre-wrap;\n}\n.badge {\n border-radius: 999px;\n padding: 0 7px;\n font-size: 10.5px;\n font-weight: 600;\n border: 1px solid currentColor;\n white-space: nowrap;\n}\n.badge.unsent {\n color: var(--changed);\n}\n.badge.resolved {\n color: var(--added);\n}\n.badge.outdated {\n color: var(--muted);\n}\n.badge.summary {\n color: var(--accent);\n}\n.badge.removed,\n.badge.detached {\n color: var(--removed);\n}\n.comment {\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr);\n gap: 7px;\n padding: 6px 9px;\n}\n.comment + .comment {\n border-top: 1px dashed var(--line);\n}\n.avatar {\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: var(--accent);\n color: var(--accent-text);\n display: grid;\n place-items: center;\n font-size: 9.5px;\n font-weight: 700;\n}\n.who {\n font-weight: 600;\n}\n.when {\n color: var(--muted);\n font-size: 11px;\n margin-left: 6px;\n}\n.body {\n overflow-wrap: anywhere;\n}\n.body > :first-child {\n margin-top: 1px;\n}\n.body > :last-child {\n margin-bottom: 0;\n}\n.body p,\n.body ul,\n.body ol,\n.body blockquote,\n.body pre {\n margin: 4px 0;\n}\n.body ul,\n.body ol {\n padding-left: 18px;\n}\n.body blockquote {\n border-left: 3px solid var(--line);\n padding-left: 8px;\n color: var(--muted);\n}\n.body code {\n font-family: var(--mono);\n font-size: 0.92em;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n padding: 0.05em 0.3em;\n border-radius: 3px;\n}\n.body pre {\n overflow-x: auto;\n padding: 6px 8px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n}\n.body pre code {\n background: none;\n padding: 0;\n}\n.body a {\n color: var(--accent);\n}\n.reply {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 6px 9px 8px;\n border-top: 1px solid var(--line);\n}\n.reply textarea {\n flex: 1 1 100%;\n min-width: 80px;\n padding: 2px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n resize: vertical;\n field-sizing: content;\n min-height: 26px;\n max-height: 160px;\n}\n.reply .error,\n.reply .hint {\n flex-basis: 100%;\n}\n.composer {\n background: var(--thread);\n border: 1px solid var(--accent);\n border-radius: 6px;\n padding: 8px 10px;\n margin: 6px 0 10px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.composer textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.row {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n align-items: center;\n}\n\n/* The detached list, above the page */\n.detached {\n margin: 12px 0;\n border: 1px dashed var(--line);\n border-radius: 6px;\n padding: 8px 10px 2px;\n color: var(--muted);\n max-width: 520px;\n}\n.detached .thread {\n color: var(--text);\n}\n\n/* The unsent bar, at the bottom */\n.unsent-bar {\n background: var(--changed-background);\n border-top: 1px solid var(--line);\n padding: 6px 10px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px 10px;\n align-items: center;\n}\n.unsent-bar .hint {\n font-size: 12px;\n}\n\n/* The panel for one block's threads, at narrow widths */\n.sheet {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n max-height: 62vh;\n overflow: auto;\n background: var(--surface);\n border-top: 2px solid var(--accent);\n box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.22);\n padding: 8px 12px 10px;\n z-index: 4;\n}\n.sheet .head {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--muted);\n}\n\n/* Dialogs */\n.backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 8;\n display: grid;\n place-items: center;\n padding: 12px;\n}\n.dialog {\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 8px;\n width: min(480px, 100%);\n max-height: 100%;\n overflow: auto;\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 13px;\n}\n.dialog h2 {\n margin: 0;\n font-size: 14px;\n}\n.dialog ul.unsent-list {\n margin: 0;\n padding-left: 18px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dialog fieldset {\n border: 0;\n padding: 0;\n margin: 0;\n display: flex;\n flex-wrap: wrap;\n gap: 4px 14px;\n}\n.dialog textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.confirm {\n border: 1px solid var(--removed);\n background: var(--removed-background);\n border-radius: 6px;\n padding: 8px 10px;\n}\n.segmented {\n display: inline-flex;\n flex-wrap: wrap;\n align-self: flex-start;\n border: 1px solid var(--line);\n border-radius: 5px;\n overflow: hidden;\n}\n.segmented button {\n border: 0;\n background: transparent;\n padding: 2px 9px;\n font-size: 12px;\n color: var(--muted);\n min-height: 24px;\n}\n.segmented button + button {\n border-left: 1px solid var(--line);\n}\n.segmented button[aria-pressed=\"true\"] {\n background: var(--accent);\n color: var(--accent-text);\n}\n.thread-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.thread-list li + li {\n border-top: 1px solid var(--line);\n}\n.thread-list button {\n display: flex;\n flex-direction: column;\n gap: 1px;\n width: 100%;\n text-align: left;\n border: 0;\n background: transparent;\n padding: 6px 4px;\n}\n.thread-list button:hover,\n.thread-list button:focus-visible {\n background: color-mix(in srgb, var(--muted) 12%, transparent);\n}\n.thread-list .where {\n font-size: 11.5px;\n color: var(--muted);\n}\n.thread-list .first {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.flash {\n animation: flash 1.4s ease-out;\n}\n@keyframes flash {\n from {\n box-shadow: 0 0 0 3px var(--accent);\n }\n to {\n box-shadow: 0 0 0 0 transparent;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .flash {\n animation: none;\n outline: 2px solid var(--accent);\n }\n}\n";
1
+ export declare const OVERLAY_CSS = "\n:host {\n all: initial;\n /* Generated from design/tokens.toml by scripts/design/tokens.ts: host, light. */\n --muted: var(--ascribe-review-muted, #676c75);\n --line: var(--ascribe-review-border, #e3e6ec);\n --thread: var(--ascribe-review-thread, #f7f9fd);\n --accent: var(--ascribe-review-focus, #5962e8);\n --accent-text: var(--ascribe-review-accent-text, #ffffff);\n --flash: var(--ascribe-review-flash, #fee9d0);\n --added: var(--ascribe-review-added, #057256);\n --changed: var(--ascribe-review-changed, #845502);\n --changed-background: var(--ascribe-review-changed-background, #fee9d0);\n --removed: var(--ascribe-review-removed, #ca3f02);\n --removed-background: var(--ascribe-review-removed-background, #fef4f1);\n --mono: var(--ascribe-review-mono, ui-monospace, monospace);\n font-family: var(--ascribe-review-font, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n /* End of generated host. */\n --text: var(--ascribe-review-text, CanvasText);\n --surface: var(--ascribe-review-surface, Canvas);\n --input: var(--ascribe-review-input, var(--surface));\n font-size: 12.5px;\n line-height: 1.45;\n color: var(--text);\n}\n* {\n box-sizing: border-box;\n}\n[hidden] {\n display: none !important;\n}\nbutton,\ntextarea,\ninput {\n font: inherit;\n color: inherit;\n}\nbutton {\n cursor: pointer;\n}\nbutton:disabled,\nbutton[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.5;\n}\nbutton:focus-visible,\ntextarea:focus-visible,\ninput:focus-visible,\n.thread:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n/* Buttons */\n.ghost {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n padding: 2px 8px;\n font-size: 12px;\n min-height: 24px;\n}\n.ghost:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n border-color: var(--accent);\n color: var(--accent);\n}\n.primary {\n border: 1px solid var(--accent);\n background: var(--accent);\n color: var(--accent-text);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.danger {\n border: 1px solid var(--removed);\n background: var(--removed);\n color: var(--surface);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.link {\n border: 0;\n background: transparent;\n color: var(--accent);\n padding: 0;\n font-size: inherit;\n text-align: inherit;\n}\na.link {\n text-decoration: none;\n}\n.link:hover {\n text-decoration: underline;\n}\n.hint {\n color: var(--muted);\n font-size: 11.5px;\n}\n.refuse,\n.error {\n color: var(--removed);\n font-size: 12px;\n}\n.spacer {\n flex: 1;\n}\n\n/* The layer over the page: the column, markers' panel, dialogs. */\n.layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.layer > *,\n.column > * {\n pointer-events: auto;\n}\n.column {\n position: absolute;\n top: 0;\n width: 250px;\n pointer-events: none;\n}\n.slot {\n position: absolute;\n left: 0;\n right: 0;\n}\n.slot > .thread,\n.slot > .composer {\n margin: 0 0 6px;\n}\n.slot.linked .thread,\n.slot.linked .composer {\n border-color: var(--accent);\n}\n.highlight {\n position: absolute;\n pointer-events: none;\n background: color-mix(in srgb, var(--flash) 55%, transparent);\n border-radius: 3px;\n /* Over the page's text, a tint that keeps the text as it was: multiplied\n on a light page, screened on a dark one. */\n mix-blend-mode: var(--ascribe-review-blend, multiply);\n}\nsvg.connector {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: visible;\n pointer-events: none;\n}\nsvg.connector path {\n fill: none;\n stroke: var(--accent);\n stroke-width: 1.5;\n}\n\n/* The block tools: the Comment button, and the count marker at narrow widths. */\n.tool {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n font-size: 11.5px;\n padding: 0 7px;\n min-height: 22px;\n color: var(--accent);\n white-space: nowrap;\n}\n.hint-count {\n margin-inline-start: 2px;\n font-size: 11px;\n font-weight: 600;\n color: var(--accent);\n}\n\n.marker {\n border: 1px solid var(--accent);\n background: var(--surface);\n color: var(--accent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n padding: 0 8px;\n min-width: 30px;\n min-height: 24px;\n}\n\n/* Threads */\n.thread {\n background: var(--thread);\n border: 1px solid var(--line);\n border-radius: 6px;\n margin: 6px 0 10px;\n}\n.th {\n display: flex;\n flex-wrap: wrap;\n gap: 3px 8px;\n align-items: center;\n padding: 5px 9px 2px;\n font-size: 11.5px;\n color: var(--muted);\n}\n.acts {\n display: flex;\n flex-wrap: wrap;\n gap: 2px 12px;\n padding: 0 9px 5px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n}\n.thread.collapsed .acts {\n border-bottom: 0;\n}\n.orig,\n.inside {\n padding: 5px 9px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n color: var(--muted);\n}\n.orig q {\n color: var(--text);\n white-space: pre-wrap;\n}\n.badge {\n border-radius: 999px;\n padding: 0 7px;\n font-size: 10.5px;\n font-weight: 600;\n border: 1px solid currentColor;\n white-space: nowrap;\n}\n.badge.unsent {\n color: var(--changed);\n}\n.badge.resolved {\n color: var(--added);\n}\n.badge.outdated {\n color: var(--muted);\n}\n.badge.summary {\n color: var(--accent);\n}\n.badge.removed,\n.badge.detached {\n color: var(--removed);\n}\n.comment {\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr);\n gap: 7px;\n padding: 6px 9px;\n}\n.comment + .comment {\n border-top: 1px dashed var(--line);\n}\n.avatar {\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: var(--accent);\n color: var(--accent-text);\n display: grid;\n place-items: center;\n font-size: 9.5px;\n font-weight: 700;\n}\n.who {\n font-weight: 600;\n}\n.when {\n color: var(--muted);\n font-size: 11px;\n margin-left: 6px;\n}\n.body {\n overflow-wrap: anywhere;\n}\n.body > :first-child {\n margin-top: 1px;\n}\n.body > :last-child {\n margin-bottom: 0;\n}\n.body p,\n.body ul,\n.body ol,\n.body blockquote,\n.body pre {\n margin: 4px 0;\n}\n.body ul,\n.body ol {\n padding-left: 18px;\n}\n.body blockquote {\n border-left: 3px solid var(--line);\n padding-left: 8px;\n color: var(--muted);\n}\n.body code,\n.thread-list .where code {\n font-family: var(--mono);\n font-size: 0.92em;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n padding: 0.05em 0.3em;\n border-radius: 3px;\n}\n.body pre {\n overflow-x: auto;\n padding: 6px 8px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n}\n.body pre code {\n background: none;\n padding: 0;\n}\n.body a {\n color: var(--accent);\n}\n.reply {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 6px 9px 8px;\n border-top: 1px solid var(--line);\n}\n.reply textarea {\n flex: 1 1 100%;\n min-width: 80px;\n padding: 2px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n resize: vertical;\n field-sizing: content;\n min-height: 26px;\n max-height: 160px;\n}\n.reply .error,\n.reply .hint {\n flex-basis: 100%;\n}\n.composer {\n background: var(--thread);\n border: 1px solid var(--accent);\n border-radius: 6px;\n padding: 8px 10px;\n margin: 6px 0 10px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.composer textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.row {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n align-items: center;\n}\n\n/* The detached list, above the page */\n.detached {\n margin: 12px 0;\n border: 1px dashed var(--line);\n border-radius: 6px;\n padding: 8px 10px 2px;\n color: var(--muted);\n max-width: 520px;\n}\n.detached .thread {\n color: var(--text);\n}\n\n/* The unsent bar, at the bottom */\n.unsent-bar {\n background: var(--changed-background);\n border-top: 1px solid var(--line);\n padding: 6px 10px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px 10px;\n align-items: center;\n}\n.unsent-bar .hint {\n color: var(--text);\n font-size: 12px;\n}\n\n/* The panel for one block's threads, at narrow widths */\n.sheet {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n max-height: 62vh;\n overflow: auto;\n background: var(--surface);\n border-top: 2px solid var(--accent);\n /* Generated from design/tokens.toml by scripts/design/tokens.ts: sheet, light. */\n box-shadow: 0 -8px 24px rgb(0 0 0 / 0.22);\n /* End of generated sheet. */\n padding: 8px 12px 10px;\n z-index: 4;\n}\n.sheet .head {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--muted);\n}\n\n/* Dialogs */\n.backdrop {\n position: fixed;\n inset: 0;\n /* Generated from design/tokens.toml by scripts/design/tokens.ts: backdrop, light. */\n background: rgb(0 0 0 / 0.4);\n /* End of generated backdrop. */\n z-index: 8;\n display: grid;\n place-items: center;\n padding: 12px;\n}\n.dialog {\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 8px;\n width: min(480px, 100%);\n max-height: 100%;\n overflow: auto;\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 13px;\n}\n.dialog h2 {\n margin: 0;\n font-size: 14px;\n}\n.dialog ul.unsent-list {\n margin: 0;\n padding-left: 18px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dialog fieldset {\n border: 0;\n padding: 0;\n margin: 0;\n display: flex;\n flex-wrap: wrap;\n gap: 4px 14px;\n}\n.dialog textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.confirm {\n border: 1px solid var(--removed);\n background: var(--removed-background);\n border-radius: 6px;\n padding: 8px 10px;\n}\n.segmented {\n display: inline-flex;\n flex-wrap: wrap;\n align-self: flex-start;\n border: 1px solid var(--line);\n border-radius: 5px;\n overflow: hidden;\n}\n.segmented button {\n border: 0;\n background: transparent;\n padding: 2px 9px;\n font-size: 12px;\n color: var(--muted);\n min-height: 24px;\n}\n.segmented button + button {\n border-left: 1px solid var(--line);\n}\n.segmented button[aria-pressed=\"true\"] {\n background: var(--accent);\n color: var(--accent-text);\n}\n.thread-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.thread-list li + li {\n border-top: 1px solid var(--line);\n}\n.thread-list button {\n display: flex;\n flex-direction: column;\n gap: 1px;\n width: 100%;\n text-align: left;\n border: 0;\n background: transparent;\n padding: 6px 4px;\n}\n.thread-list button:hover,\n.thread-list button:focus-visible {\n background: color-mix(in srgb, var(--muted) 12%, transparent);\n}\n.thread-list .where {\n font-size: 11.5px;\n color: var(--muted);\n}\n.thread-list .first {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.flash {\n animation: flash 1.4s ease-out;\n}\n@keyframes flash {\n from {\n box-shadow: 0 0 0 3px var(--accent);\n }\n to {\n box-shadow: 0 0 0 0 transparent;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .flash {\n animation: none;\n outline: 2px solid var(--accent);\n }\n}\n";
@@ -6,22 +6,24 @@
6
6
  export const OVERLAY_CSS = `
7
7
  :host {
8
8
  all: initial;
9
- --text: var(--ascribe-review-text, CanvasText);
10
- --muted: var(--ascribe-review-muted, #5b6475);
11
- --line: var(--ascribe-review-border, #d3d8e0);
12
- --surface: var(--ascribe-review-surface, Canvas);
13
- --thread: var(--ascribe-review-thread, #f5f7fb);
14
- --accent: var(--ascribe-review-focus, #1f6feb);
9
+ /* Generated from design/tokens.toml by scripts/design/tokens.ts: host, light. */
10
+ --muted: var(--ascribe-review-muted, #676c75);
11
+ --line: var(--ascribe-review-border, #e3e6ec);
12
+ --thread: var(--ascribe-review-thread, #f7f9fd);
13
+ --accent: var(--ascribe-review-focus, #5962e8);
15
14
  --accent-text: var(--ascribe-review-accent-text, #ffffff);
16
- --input: var(--ascribe-review-input, var(--surface));
17
- --flash: var(--ascribe-review-flash, #fff6bf);
18
- --added: var(--ascribe-review-added, #1a7f37);
19
- --changed: var(--ascribe-review-changed, #8a5a00);
20
- --changed-background: var(--ascribe-review-changed-background, #fff1c2);
21
- --removed: var(--ascribe-review-removed, #c4222d);
22
- --removed-background: var(--ascribe-review-removed-background, #ffe4e2);
15
+ --flash: var(--ascribe-review-flash, #fee9d0);
16
+ --added: var(--ascribe-review-added, #057256);
17
+ --changed: var(--ascribe-review-changed, #845502);
18
+ --changed-background: var(--ascribe-review-changed-background, #fee9d0);
19
+ --removed: var(--ascribe-review-removed, #ca3f02);
20
+ --removed-background: var(--ascribe-review-removed-background, #fef4f1);
23
21
  --mono: var(--ascribe-review-mono, ui-monospace, monospace);
24
22
  font-family: var(--ascribe-review-font, system-ui, -apple-system, "Segoe UI", sans-serif);
23
+ /* End of generated host. */
24
+ --text: var(--ascribe-review-text, CanvasText);
25
+ --surface: var(--ascribe-review-surface, Canvas);
26
+ --input: var(--ascribe-review-input, var(--surface));
25
27
  font-size: 12.5px;
26
28
  line-height: 1.45;
27
29
  color: var(--text);
@@ -319,7 +321,8 @@ svg.connector path {
319
321
  padding-left: 8px;
320
322
  color: var(--muted);
321
323
  }
322
- .body code {
324
+ .body code,
325
+ .thread-list .where code {
323
326
  font-family: var(--mono);
324
327
  font-size: 0.92em;
325
328
  background: color-mix(in srgb, var(--muted) 14%, transparent);
@@ -412,6 +415,7 @@ svg.connector path {
412
415
  align-items: center;
413
416
  }
414
417
  .unsent-bar .hint {
418
+ color: var(--text);
415
419
  font-size: 12px;
416
420
  }
417
421
 
@@ -425,7 +429,9 @@ svg.connector path {
425
429
  overflow: auto;
426
430
  background: var(--surface);
427
431
  border-top: 2px solid var(--accent);
428
- box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.22);
432
+ /* Generated from design/tokens.toml by scripts/design/tokens.ts: sheet, light. */
433
+ box-shadow: 0 -8px 24px rgb(0 0 0 / 0.22);
434
+ /* End of generated sheet. */
429
435
  padding: 8px 12px 10px;
430
436
  z-index: 4;
431
437
  }
@@ -441,7 +447,9 @@ svg.connector path {
441
447
  .backdrop {
442
448
  position: fixed;
443
449
  inset: 0;
444
- background: rgba(0, 0, 0, 0.4);
450
+ /* Generated from design/tokens.toml by scripts/design/tokens.ts: backdrop, light. */
451
+ background: rgb(0 0 0 / 0.4);
452
+ /* End of generated backdrop. */
445
453
  z-index: 8;
446
454
  display: grid;
447
455
  place-items: center;
@@ -1 +1 @@
1
- {"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/overlay/style.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,kDAAkD;AAElD,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmjB1B,CAAC","sourcesContent":["// The overlay's stylesheet. It applies inside the overlay's shadow roots only,\n// so the page's styles don't reach the overlay and the overlay's don't reach\n// the page. Colors come from the page's --ascribe-review-* custom properties\n// (`marks.css` sets them, with light and dark values; a host can set its own),\n// which reach inside shadow roots by inheritance.\n\nexport const OVERLAY_CSS = `\n:host {\n all: initial;\n --text: var(--ascribe-review-text, CanvasText);\n --muted: var(--ascribe-review-muted, #5b6475);\n --line: var(--ascribe-review-border, #d3d8e0);\n --surface: var(--ascribe-review-surface, Canvas);\n --thread: var(--ascribe-review-thread, #f5f7fb);\n --accent: var(--ascribe-review-focus, #1f6feb);\n --accent-text: var(--ascribe-review-accent-text, #ffffff);\n --input: var(--ascribe-review-input, var(--surface));\n --flash: var(--ascribe-review-flash, #fff6bf);\n --added: var(--ascribe-review-added, #1a7f37);\n --changed: var(--ascribe-review-changed, #8a5a00);\n --changed-background: var(--ascribe-review-changed-background, #fff1c2);\n --removed: var(--ascribe-review-removed, #c4222d);\n --removed-background: var(--ascribe-review-removed-background, #ffe4e2);\n --mono: var(--ascribe-review-mono, ui-monospace, monospace);\n font-family: var(--ascribe-review-font, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n font-size: 12.5px;\n line-height: 1.45;\n color: var(--text);\n}\n* {\n box-sizing: border-box;\n}\n[hidden] {\n display: none !important;\n}\nbutton,\ntextarea,\ninput {\n font: inherit;\n color: inherit;\n}\nbutton {\n cursor: pointer;\n}\nbutton:disabled,\nbutton[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.5;\n}\nbutton:focus-visible,\ntextarea:focus-visible,\ninput:focus-visible,\n.thread:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n/* Buttons */\n.ghost {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n padding: 2px 8px;\n font-size: 12px;\n min-height: 24px;\n}\n.ghost:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n border-color: var(--accent);\n color: var(--accent);\n}\n.primary {\n border: 1px solid var(--accent);\n background: var(--accent);\n color: var(--accent-text);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.danger {\n border: 1px solid var(--removed);\n background: var(--removed);\n color: var(--surface);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.link {\n border: 0;\n background: transparent;\n color: var(--accent);\n padding: 0;\n font-size: inherit;\n text-align: inherit;\n}\na.link {\n text-decoration: none;\n}\n.link:hover {\n text-decoration: underline;\n}\n.hint {\n color: var(--muted);\n font-size: 11.5px;\n}\n.refuse,\n.error {\n color: var(--removed);\n font-size: 12px;\n}\n.spacer {\n flex: 1;\n}\n\n/* The layer over the page: the column, markers' panel, dialogs. */\n.layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.layer > *,\n.column > * {\n pointer-events: auto;\n}\n.column {\n position: absolute;\n top: 0;\n width: 250px;\n pointer-events: none;\n}\n.slot {\n position: absolute;\n left: 0;\n right: 0;\n}\n.slot > .thread,\n.slot > .composer {\n margin: 0 0 6px;\n}\n.slot.linked .thread,\n.slot.linked .composer {\n border-color: var(--accent);\n}\n.highlight {\n position: absolute;\n pointer-events: none;\n background: color-mix(in srgb, var(--flash) 55%, transparent);\n border-radius: 3px;\n /* Over the page's text, a tint that keeps the text as it was: multiplied\n on a light page, screened on a dark one. */\n mix-blend-mode: var(--ascribe-review-blend, multiply);\n}\nsvg.connector {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: visible;\n pointer-events: none;\n}\nsvg.connector path {\n fill: none;\n stroke: var(--accent);\n stroke-width: 1.5;\n}\n\n/* The block tools: the Comment button, and the count marker at narrow widths. */\n.tool {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n font-size: 11.5px;\n padding: 0 7px;\n min-height: 22px;\n color: var(--accent);\n white-space: nowrap;\n}\n.hint-count {\n margin-inline-start: 2px;\n font-size: 11px;\n font-weight: 600;\n color: var(--accent);\n}\n\n.marker {\n border: 1px solid var(--accent);\n background: var(--surface);\n color: var(--accent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n padding: 0 8px;\n min-width: 30px;\n min-height: 24px;\n}\n\n/* Threads */\n.thread {\n background: var(--thread);\n border: 1px solid var(--line);\n border-radius: 6px;\n margin: 6px 0 10px;\n}\n.th {\n display: flex;\n flex-wrap: wrap;\n gap: 3px 8px;\n align-items: center;\n padding: 5px 9px 2px;\n font-size: 11.5px;\n color: var(--muted);\n}\n.acts {\n display: flex;\n flex-wrap: wrap;\n gap: 2px 12px;\n padding: 0 9px 5px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n}\n.thread.collapsed .acts {\n border-bottom: 0;\n}\n.orig,\n.inside {\n padding: 5px 9px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n color: var(--muted);\n}\n.orig q {\n color: var(--text);\n white-space: pre-wrap;\n}\n.badge {\n border-radius: 999px;\n padding: 0 7px;\n font-size: 10.5px;\n font-weight: 600;\n border: 1px solid currentColor;\n white-space: nowrap;\n}\n.badge.unsent {\n color: var(--changed);\n}\n.badge.resolved {\n color: var(--added);\n}\n.badge.outdated {\n color: var(--muted);\n}\n.badge.summary {\n color: var(--accent);\n}\n.badge.removed,\n.badge.detached {\n color: var(--removed);\n}\n.comment {\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr);\n gap: 7px;\n padding: 6px 9px;\n}\n.comment + .comment {\n border-top: 1px dashed var(--line);\n}\n.avatar {\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: var(--accent);\n color: var(--accent-text);\n display: grid;\n place-items: center;\n font-size: 9.5px;\n font-weight: 700;\n}\n.who {\n font-weight: 600;\n}\n.when {\n color: var(--muted);\n font-size: 11px;\n margin-left: 6px;\n}\n.body {\n overflow-wrap: anywhere;\n}\n.body > :first-child {\n margin-top: 1px;\n}\n.body > :last-child {\n margin-bottom: 0;\n}\n.body p,\n.body ul,\n.body ol,\n.body blockquote,\n.body pre {\n margin: 4px 0;\n}\n.body ul,\n.body ol {\n padding-left: 18px;\n}\n.body blockquote {\n border-left: 3px solid var(--line);\n padding-left: 8px;\n color: var(--muted);\n}\n.body code {\n font-family: var(--mono);\n font-size: 0.92em;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n padding: 0.05em 0.3em;\n border-radius: 3px;\n}\n.body pre {\n overflow-x: auto;\n padding: 6px 8px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n}\n.body pre code {\n background: none;\n padding: 0;\n}\n.body a {\n color: var(--accent);\n}\n.reply {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 6px 9px 8px;\n border-top: 1px solid var(--line);\n}\n.reply textarea {\n flex: 1 1 100%;\n min-width: 80px;\n padding: 2px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n resize: vertical;\n field-sizing: content;\n min-height: 26px;\n max-height: 160px;\n}\n.reply .error,\n.reply .hint {\n flex-basis: 100%;\n}\n.composer {\n background: var(--thread);\n border: 1px solid var(--accent);\n border-radius: 6px;\n padding: 8px 10px;\n margin: 6px 0 10px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.composer textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.row {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n align-items: center;\n}\n\n/* The detached list, above the page */\n.detached {\n margin: 12px 0;\n border: 1px dashed var(--line);\n border-radius: 6px;\n padding: 8px 10px 2px;\n color: var(--muted);\n max-width: 520px;\n}\n.detached .thread {\n color: var(--text);\n}\n\n/* The unsent bar, at the bottom */\n.unsent-bar {\n background: var(--changed-background);\n border-top: 1px solid var(--line);\n padding: 6px 10px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px 10px;\n align-items: center;\n}\n.unsent-bar .hint {\n font-size: 12px;\n}\n\n/* The panel for one block's threads, at narrow widths */\n.sheet {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n max-height: 62vh;\n overflow: auto;\n background: var(--surface);\n border-top: 2px solid var(--accent);\n box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.22);\n padding: 8px 12px 10px;\n z-index: 4;\n}\n.sheet .head {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--muted);\n}\n\n/* Dialogs */\n.backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n z-index: 8;\n display: grid;\n place-items: center;\n padding: 12px;\n}\n.dialog {\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 8px;\n width: min(480px, 100%);\n max-height: 100%;\n overflow: auto;\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 13px;\n}\n.dialog h2 {\n margin: 0;\n font-size: 14px;\n}\n.dialog ul.unsent-list {\n margin: 0;\n padding-left: 18px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dialog fieldset {\n border: 0;\n padding: 0;\n margin: 0;\n display: flex;\n flex-wrap: wrap;\n gap: 4px 14px;\n}\n.dialog textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.confirm {\n border: 1px solid var(--removed);\n background: var(--removed-background);\n border-radius: 6px;\n padding: 8px 10px;\n}\n.segmented {\n display: inline-flex;\n flex-wrap: wrap;\n align-self: flex-start;\n border: 1px solid var(--line);\n border-radius: 5px;\n overflow: hidden;\n}\n.segmented button {\n border: 0;\n background: transparent;\n padding: 2px 9px;\n font-size: 12px;\n color: var(--muted);\n min-height: 24px;\n}\n.segmented button + button {\n border-left: 1px solid var(--line);\n}\n.segmented button[aria-pressed=\"true\"] {\n background: var(--accent);\n color: var(--accent-text);\n}\n.thread-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.thread-list li + li {\n border-top: 1px solid var(--line);\n}\n.thread-list button {\n display: flex;\n flex-direction: column;\n gap: 1px;\n width: 100%;\n text-align: left;\n border: 0;\n background: transparent;\n padding: 6px 4px;\n}\n.thread-list button:hover,\n.thread-list button:focus-visible {\n background: color-mix(in srgb, var(--muted) 12%, transparent);\n}\n.thread-list .where {\n font-size: 11.5px;\n color: var(--muted);\n}\n.thread-list .first {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.flash {\n animation: flash 1.4s ease-out;\n}\n@keyframes flash {\n from {\n box-shadow: 0 0 0 3px var(--accent);\n }\n to {\n box-shadow: 0 0 0 0 transparent;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .flash {\n animation: none;\n outline: 2px solid var(--accent);\n }\n}\n`;\n"]}
1
+ {"version":3,"file":"style.js","sourceRoot":"","sources":["../../src/overlay/style.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,+EAA+E;AAC/E,kDAAkD;AAElD,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2jB1B,CAAC","sourcesContent":["// The overlay's stylesheet. It applies inside the overlay's shadow roots only,\n// so the page's styles don't reach the overlay and the overlay's don't reach\n// the page. Colors come from the page's --ascribe-review-* custom properties\n// (`marks.css` sets them, with light and dark values; a host can set its own),\n// which reach inside shadow roots by inheritance.\n\nexport const OVERLAY_CSS = `\n:host {\n all: initial;\n /* Generated from design/tokens.toml by scripts/design/tokens.ts: host, light. */\n --muted: var(--ascribe-review-muted, #676c75);\n --line: var(--ascribe-review-border, #e3e6ec);\n --thread: var(--ascribe-review-thread, #f7f9fd);\n --accent: var(--ascribe-review-focus, #5962e8);\n --accent-text: var(--ascribe-review-accent-text, #ffffff);\n --flash: var(--ascribe-review-flash, #fee9d0);\n --added: var(--ascribe-review-added, #057256);\n --changed: var(--ascribe-review-changed, #845502);\n --changed-background: var(--ascribe-review-changed-background, #fee9d0);\n --removed: var(--ascribe-review-removed, #ca3f02);\n --removed-background: var(--ascribe-review-removed-background, #fef4f1);\n --mono: var(--ascribe-review-mono, ui-monospace, monospace);\n font-family: var(--ascribe-review-font, system-ui, -apple-system, \"Segoe UI\", sans-serif);\n /* End of generated host. */\n --text: var(--ascribe-review-text, CanvasText);\n --surface: var(--ascribe-review-surface, Canvas);\n --input: var(--ascribe-review-input, var(--surface));\n font-size: 12.5px;\n line-height: 1.45;\n color: var(--text);\n}\n* {\n box-sizing: border-box;\n}\n[hidden] {\n display: none !important;\n}\nbutton,\ntextarea,\ninput {\n font: inherit;\n color: inherit;\n}\nbutton {\n cursor: pointer;\n}\nbutton:disabled,\nbutton[aria-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: 0.5;\n}\nbutton:focus-visible,\ntextarea:focus-visible,\ninput:focus-visible,\n.thread:focus-visible {\n outline: 2px solid var(--accent);\n outline-offset: 1px;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\n\n/* Buttons */\n.ghost {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n padding: 2px 8px;\n font-size: 12px;\n min-height: 24px;\n}\n.ghost:hover:not(:disabled):not([aria-disabled=\"true\"]) {\n border-color: var(--accent);\n color: var(--accent);\n}\n.primary {\n border: 1px solid var(--accent);\n background: var(--accent);\n color: var(--accent-text);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.danger {\n border: 1px solid var(--removed);\n background: var(--removed);\n color: var(--surface);\n border-radius: 5px;\n padding: 2px 10px;\n font-size: 12.5px;\n font-weight: 600;\n min-height: 24px;\n}\n.link {\n border: 0;\n background: transparent;\n color: var(--accent);\n padding: 0;\n font-size: inherit;\n text-align: inherit;\n}\na.link {\n text-decoration: none;\n}\n.link:hover {\n text-decoration: underline;\n}\n.hint {\n color: var(--muted);\n font-size: 11.5px;\n}\n.refuse,\n.error {\n color: var(--removed);\n font-size: 12px;\n}\n.spacer {\n flex: 1;\n}\n\n/* The layer over the page: the column, markers' panel, dialogs. */\n.layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.layer > *,\n.column > * {\n pointer-events: auto;\n}\n.column {\n position: absolute;\n top: 0;\n width: 250px;\n pointer-events: none;\n}\n.slot {\n position: absolute;\n left: 0;\n right: 0;\n}\n.slot > .thread,\n.slot > .composer {\n margin: 0 0 6px;\n}\n.slot.linked .thread,\n.slot.linked .composer {\n border-color: var(--accent);\n}\n.highlight {\n position: absolute;\n pointer-events: none;\n background: color-mix(in srgb, var(--flash) 55%, transparent);\n border-radius: 3px;\n /* Over the page's text, a tint that keeps the text as it was: multiplied\n on a light page, screened on a dark one. */\n mix-blend-mode: var(--ascribe-review-blend, multiply);\n}\nsvg.connector {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow: visible;\n pointer-events: none;\n}\nsvg.connector path {\n fill: none;\n stroke: var(--accent);\n stroke-width: 1.5;\n}\n\n/* The block tools: the Comment button, and the count marker at narrow widths. */\n.tool {\n border: 1px solid var(--line);\n background: var(--surface);\n border-radius: 5px;\n font-size: 11.5px;\n padding: 0 7px;\n min-height: 22px;\n color: var(--accent);\n white-space: nowrap;\n}\n.hint-count {\n margin-inline-start: 2px;\n font-size: 11px;\n font-weight: 600;\n color: var(--accent);\n}\n\n.marker {\n border: 1px solid var(--accent);\n background: var(--surface);\n color: var(--accent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n padding: 0 8px;\n min-width: 30px;\n min-height: 24px;\n}\n\n/* Threads */\n.thread {\n background: var(--thread);\n border: 1px solid var(--line);\n border-radius: 6px;\n margin: 6px 0 10px;\n}\n.th {\n display: flex;\n flex-wrap: wrap;\n gap: 3px 8px;\n align-items: center;\n padding: 5px 9px 2px;\n font-size: 11.5px;\n color: var(--muted);\n}\n.acts {\n display: flex;\n flex-wrap: wrap;\n gap: 2px 12px;\n padding: 0 9px 5px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n}\n.thread.collapsed .acts {\n border-bottom: 0;\n}\n.orig,\n.inside {\n padding: 5px 9px;\n border-bottom: 1px solid var(--line);\n font-size: 11.5px;\n color: var(--muted);\n}\n.orig q {\n color: var(--text);\n white-space: pre-wrap;\n}\n.badge {\n border-radius: 999px;\n padding: 0 7px;\n font-size: 10.5px;\n font-weight: 600;\n border: 1px solid currentColor;\n white-space: nowrap;\n}\n.badge.unsent {\n color: var(--changed);\n}\n.badge.resolved {\n color: var(--added);\n}\n.badge.outdated {\n color: var(--muted);\n}\n.badge.summary {\n color: var(--accent);\n}\n.badge.removed,\n.badge.detached {\n color: var(--removed);\n}\n.comment {\n display: grid;\n grid-template-columns: 22px minmax(0, 1fr);\n gap: 7px;\n padding: 6px 9px;\n}\n.comment + .comment {\n border-top: 1px dashed var(--line);\n}\n.avatar {\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: var(--accent);\n color: var(--accent-text);\n display: grid;\n place-items: center;\n font-size: 9.5px;\n font-weight: 700;\n}\n.who {\n font-weight: 600;\n}\n.when {\n color: var(--muted);\n font-size: 11px;\n margin-left: 6px;\n}\n.body {\n overflow-wrap: anywhere;\n}\n.body > :first-child {\n margin-top: 1px;\n}\n.body > :last-child {\n margin-bottom: 0;\n}\n.body p,\n.body ul,\n.body ol,\n.body blockquote,\n.body pre {\n margin: 4px 0;\n}\n.body ul,\n.body ol {\n padding-left: 18px;\n}\n.body blockquote {\n border-left: 3px solid var(--line);\n padding-left: 8px;\n color: var(--muted);\n}\n.body code,\n.thread-list .where code {\n font-family: var(--mono);\n font-size: 0.92em;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n padding: 0.05em 0.3em;\n border-radius: 3px;\n}\n.body pre {\n overflow-x: auto;\n padding: 6px 8px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--muted) 14%, transparent);\n}\n.body pre code {\n background: none;\n padding: 0;\n}\n.body a {\n color: var(--accent);\n}\n.reply {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 6px 9px 8px;\n border-top: 1px solid var(--line);\n}\n.reply textarea {\n flex: 1 1 100%;\n min-width: 80px;\n padding: 2px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n resize: vertical;\n field-sizing: content;\n min-height: 26px;\n max-height: 160px;\n}\n.reply .error,\n.reply .hint {\n flex-basis: 100%;\n}\n.composer {\n background: var(--thread);\n border: 1px solid var(--accent);\n border-radius: 6px;\n padding: 8px 10px;\n margin: 6px 0 10px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.composer textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.row {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n align-items: center;\n}\n\n/* The detached list, above the page */\n.detached {\n margin: 12px 0;\n border: 1px dashed var(--line);\n border-radius: 6px;\n padding: 8px 10px 2px;\n color: var(--muted);\n max-width: 520px;\n}\n.detached .thread {\n color: var(--text);\n}\n\n/* The unsent bar, at the bottom */\n.unsent-bar {\n background: var(--changed-background);\n border-top: 1px solid var(--line);\n padding: 6px 10px;\n display: flex;\n flex-wrap: wrap;\n gap: 6px 10px;\n align-items: center;\n}\n.unsent-bar .hint {\n color: var(--text);\n font-size: 12px;\n}\n\n/* The panel for one block's threads, at narrow widths */\n.sheet {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n max-height: 62vh;\n overflow: auto;\n background: var(--surface);\n border-top: 2px solid var(--accent);\n /* Generated from design/tokens.toml by scripts/design/tokens.ts: sheet, light. */\n box-shadow: 0 -8px 24px rgb(0 0 0 / 0.22);\n /* End of generated sheet. */\n padding: 8px 12px 10px;\n z-index: 4;\n}\n.sheet .head {\n display: flex;\n gap: 8px;\n align-items: center;\n font-size: 12px;\n color: var(--muted);\n}\n\n/* Dialogs */\n.backdrop {\n position: fixed;\n inset: 0;\n /* Generated from design/tokens.toml by scripts/design/tokens.ts: backdrop, light. */\n background: rgb(0 0 0 / 0.4);\n /* End of generated backdrop. */\n z-index: 8;\n display: grid;\n place-items: center;\n padding: 12px;\n}\n.dialog {\n background: var(--surface);\n border: 1px solid var(--line);\n border-radius: 8px;\n width: min(480px, 100%);\n max-height: 100%;\n overflow: auto;\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n font-size: 13px;\n}\n.dialog h2 {\n margin: 0;\n font-size: 14px;\n}\n.dialog ul.unsent-list {\n margin: 0;\n padding-left: 18px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dialog fieldset {\n border: 0;\n padding: 0;\n margin: 0;\n display: flex;\n flex-wrap: wrap;\n gap: 4px 14px;\n}\n.dialog textarea {\n width: 100%;\n min-height: 56px;\n resize: vertical;\n padding: 6px 8px;\n border: 1px solid var(--line);\n border-radius: 5px;\n background: var(--input);\n}\n.confirm {\n border: 1px solid var(--removed);\n background: var(--removed-background);\n border-radius: 6px;\n padding: 8px 10px;\n}\n.segmented {\n display: inline-flex;\n flex-wrap: wrap;\n align-self: flex-start;\n border: 1px solid var(--line);\n border-radius: 5px;\n overflow: hidden;\n}\n.segmented button {\n border: 0;\n background: transparent;\n padding: 2px 9px;\n font-size: 12px;\n color: var(--muted);\n min-height: 24px;\n}\n.segmented button + button {\n border-left: 1px solid var(--line);\n}\n.segmented button[aria-pressed=\"true\"] {\n background: var(--accent);\n color: var(--accent-text);\n}\n.thread-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.thread-list li + li {\n border-top: 1px solid var(--line);\n}\n.thread-list button {\n display: flex;\n flex-direction: column;\n gap: 1px;\n width: 100%;\n text-align: left;\n border: 0;\n background: transparent;\n padding: 6px 4px;\n}\n.thread-list button:hover,\n.thread-list button:focus-visible {\n background: color-mix(in srgb, var(--muted) 12%, transparent);\n}\n.thread-list .where {\n font-size: 11.5px;\n color: var(--muted);\n}\n.thread-list .first {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.flash {\n animation: flash 1.4s ease-out;\n}\n@keyframes flash {\n from {\n box-shadow: 0 0 0 3px var(--accent);\n }\n to {\n box-shadow: 0 0 0 0 transparent;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .flash {\n animation: none;\n outline: 2px solid var(--accent);\n }\n}\n`;\n"]}
@@ -1,8 +1,9 @@
1
1
  import type { CommentTarget, PendingReview, ReviewEvent } from "../github/session.js";
2
2
  import type { Anchor } from "../place/anchor.js";
3
3
  import type { LocatedThread, PlacedThreads } from "../place/place.js";
4
- import type { Thread, ThreadComment } from "../shared/types.js";
5
- export type { Anchor, CommentTarget, LocatedThread, PendingReview, PlacedThreads, ReviewEvent };
4
+ import type { FormattedPiece } from "../shapes.js";
5
+ import type { PromptRequest, Thread, ThreadComment } from "../shared/types.js";
6
+ export type { Anchor, CommentTarget, FormattedPiece, LocatedThread, PendingReview, PlacedThreads, PromptRequest, ReviewEvent, };
6
7
  /** What the overlay draws for the page it's on. */
7
8
  export interface OverlayData {
8
9
  /** The pull request: its number names it ("#128"). */
@@ -20,10 +21,15 @@ export interface OverlayData {
20
21
  /** A thread on the pull request, and the pages it's on. */
21
22
  export interface ThreadSummary {
22
23
  thread: LocatedThread;
23
- /** The pages that show it, by content path; empty when no page is known to. */
24
+ /**
25
+ * The pages that show it, by content path; empty when no page is known to.
26
+ * `formatted_title` is the title formatted, when its field sets `inline`;
27
+ * a host that doesn't know it leaves it out.
28
+ */
24
29
  pages: {
25
30
  path: string;
26
31
  title: string | null;
32
+ formatted_title?: FormattedPiece[] | null;
27
33
  }[];
28
34
  }
29
35
  /** A failure the host reports: `message` is a sentence to show; `code` is a `ReviewError` code. */
@@ -55,6 +61,13 @@ export interface OverlayHost {
55
61
  openLink?(url: string): void;
56
62
  /** Tells the reviewer something that happened, as the host shows notices. */
57
63
  notify?(message: string): void;
64
+ /**
65
+ * Builds a prompt for the reviewer's agent and puts it where they send it
66
+ * (the clipboard, or an agent's chat, filled in and not sent), saying so as
67
+ * the host shows notices. The overlay only says what was asked for. Without
68
+ * it, the overlay offers no **Prompt agent** actions.
69
+ */
70
+ promptAgent?(request: PromptRequest): Promise<void>;
58
71
  /** Calls `listener` when the page re-rendered or the threads changed. */
59
72
  onDidChange(listener: () => void): void;
60
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/overlay/types.ts"],"names":[],"mappings":"","sourcesContent":["// What the overlay gets from its host, and what it gives back. The types are\n// shared with `./github` and `./place`, but only as types: nothing of them\n// runs in the browser.\nimport type { CommentTarget, PendingReview, ReviewEvent } from \"../github/session.js\";\nimport type { Anchor } from \"../place/anchor.js\";\nimport type { LocatedThread, PlacedThreads } from \"../place/place.js\";\nimport type { Thread, ThreadComment } from \"../shared/types.js\";\n\nexport type { Anchor, CommentTarget, LocatedThread, PendingReview, PlacedThreads, ReviewEvent };\n\n/** What the overlay draws for the page it's on. */\nexport interface OverlayData {\n /** The pull request: its number names it (\"#128\"). */\n pullRequest: { number: number; url: string };\n /** The page's threads, placed on its blocks (`ReviewSession.threads`). */\n threads: PlacedThreads;\n /** The viewer's pending review: what's unsent. */\n pending: PendingReview;\n /** The signed-in viewer's login. */\n viewer: string;\n}\n\n/** A thread on the pull request, and the pages it's on. */\nexport interface ThreadSummary {\n thread: LocatedThread;\n /** The pages that show it, by content path; empty when no page is known to. */\n pages: { path: string; title: string | null }[];\n}\n\n/** A failure the host reports: `message` is a sentence to show; `code` is a `ReviewError` code. */\nexport interface HostError {\n message: string;\n code?: string;\n}\n\n/**\n * Where the overlay gets its data and sends what the reviewer does. A host\n * keeps GitHub to itself: the overlay never sees a token or makes a request.\n */\nexport interface OverlayHost {\n load(): Promise<OverlayData>;\n /** How a comment on the block would go: as a thread, in the review's summary, or not yet. */\n commentTarget(anchor: Anchor): Promise<CommentTarget>;\n /** Comments on a block. `quote` is the block's text as the page shows it. */\n comment(anchor: Anchor, body: string, quote?: string): Promise<Thread>;\n reply(threadId: string, body: string, when: \"now\" | \"withReview\"): Promise<ThreadComment>;\n /** Every thread on the pull request's pages, with the pages each is on. */\n allThreads(): Promise<ThreadSummary[]>;\n resolve(threadId: string, resolved: boolean): Promise<void>;\n submit(event: ReviewEvent, body?: string): Promise<void>;\n /** Deletes the pending review and everything in it. */\n discard(): Promise<void>;\n openSource(anchor: Anchor): void;\n /** Shows a thread that's on another page: the host opens that page, and its overlay goes to the thread. */\n openThread(threadId: string, page: string): void;\n /** Opens a link from a comment. Without it, the link opens as the page would open it. */\n openLink?(url: string): void;\n /** Tells the reviewer something that happened, as the host shows notices. */\n notify?(message: string): void;\n /** Calls `listener` when the page re-rendered or the threads changed. */\n onDidChange(listener: () => void): void;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/overlay/types.ts"],"names":[],"mappings":"","sourcesContent":["// What the overlay gets from its host, and what it gives back. The types are\n// shared with `./github` and `./place`, but only as types: nothing of them\n// runs in the browser.\nimport type { CommentTarget, PendingReview, ReviewEvent } from \"../github/session.js\";\nimport type { Anchor } from \"../place/anchor.js\";\nimport type { LocatedThread, PlacedThreads } from \"../place/place.js\";\nimport type { FormattedPiece } from \"../shapes.js\";\nimport type { PromptRequest, Thread, ThreadComment } from \"../shared/types.js\";\n\nexport type {\n Anchor,\n CommentTarget,\n FormattedPiece,\n LocatedThread,\n PendingReview,\n PlacedThreads,\n PromptRequest,\n ReviewEvent,\n};\n\n/** What the overlay draws for the page it's on. */\nexport interface OverlayData {\n /** The pull request: its number names it (\"#128\"). */\n pullRequest: { number: number; url: string };\n /** The page's threads, placed on its blocks (`ReviewSession.threads`). */\n threads: PlacedThreads;\n /** The viewer's pending review: what's unsent. */\n pending: PendingReview;\n /** The signed-in viewer's login. */\n viewer: string;\n}\n\n/** A thread on the pull request, and the pages it's on. */\nexport interface ThreadSummary {\n thread: LocatedThread;\n /**\n * The pages that show it, by content path; empty when no page is known to.\n * `formatted_title` is the title formatted, when its field sets `inline`;\n * a host that doesn't know it leaves it out.\n */\n pages: { path: string; title: string | null; formatted_title?: FormattedPiece[] | null }[];\n}\n\n/** A failure the host reports: `message` is a sentence to show; `code` is a `ReviewError` code. */\nexport interface HostError {\n message: string;\n code?: string;\n}\n\n/**\n * Where the overlay gets its data and sends what the reviewer does. A host\n * keeps GitHub to itself: the overlay never sees a token or makes a request.\n */\nexport interface OverlayHost {\n load(): Promise<OverlayData>;\n /** How a comment on the block would go: as a thread, in the review's summary, or not yet. */\n commentTarget(anchor: Anchor): Promise<CommentTarget>;\n /** Comments on a block. `quote` is the block's text as the page shows it. */\n comment(anchor: Anchor, body: string, quote?: string): Promise<Thread>;\n reply(threadId: string, body: string, when: \"now\" | \"withReview\"): Promise<ThreadComment>;\n /** Every thread on the pull request's pages, with the pages each is on. */\n allThreads(): Promise<ThreadSummary[]>;\n resolve(threadId: string, resolved: boolean): Promise<void>;\n submit(event: ReviewEvent, body?: string): Promise<void>;\n /** Deletes the pending review and everything in it. */\n discard(): Promise<void>;\n openSource(anchor: Anchor): void;\n /** Shows a thread that's on another page: the host opens that page, and its overlay goes to the thread. */\n openThread(threadId: string, page: string): void;\n /** Opens a link from a comment. Without it, the link opens as the page would open it. */\n openLink?(url: string): void;\n /** Tells the reviewer something that happened, as the host shows notices. */\n notify?(message: string): void;\n /**\n * Builds a prompt for the reviewer's agent and puts it where they send it\n * (the clipboard, or an agent's chat, filled in and not sent), saying so as\n * the host shows notices. The overlay only says what was asked for. Without\n * it, the overlay offers no **Prompt agent** actions.\n */\n promptAgent?(request: PromptRequest): Promise<void>;\n /** Calls `listener` when the page re-rendered or the threads changed. */\n onDidChange(listener: () => void): void;\n}\n"]}
@@ -129,8 +129,9 @@ export function placeOnPage(page, threads) {
129
129
  continue;
130
130
  }
131
131
  const into = left ? onRemoved : onBlocks;
132
- for (const match of matches) {
133
- const key = anchorKey(match.anchor);
132
+ // A block and the element around it can share an anchor, as a one-line
133
+ // note and its paragraph do: the thread goes on the anchor once.
134
+ for (const key of new Set(matches.map((match) => anchorKey(match.anchor)))) {
134
135
  const list = into.get(key) ?? [];
135
136
  list.push(thread);
136
137
  into.set(key, list);
@@ -1 +1 @@
1
- {"version":3,"file":"place.js","sourceRoot":"","sources":["../../src/place/place.ts"],"names":[],"mappings":"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","sourcesContent":["// Placing review threads on rendered blocks. A thread is on a repository path\n// and a line at a commit; a block's anchor is a content path and lines in the\n// working tree. Locating moves each thread into the working tree's terms, once\n// per pull request; placing matches the result to one page's anchors.\nimport type { Side, Thread } from \"../shared/types.js\";\nimport { anchorKey, parseSource, type Anchor } from \"./anchor.js\";\nimport { lineMap, linesAt, type LineMap } from \"./lines.js\";\n\n/** A rendered page, as placing needs it. */\nexport interface PageRef {\n /** The build the page is rendered for. */\n build: string;\n /** The page's own content path. */\n path: string;\n /** Every anchored block on the page, in the page's order. */\n anchors: Anchor[];\n /**\n * Where the diff's removed, moved, and changed blocks on this page were\n * (each such change's `was`), for threads on the text they had.\n */\n removed?: Anchor[] | undefined;\n}\n\n/** Why a thread isn't on a block. */\nexport type DetachReason =\n /** The line it's on isn't in the working tree any more. */\n | \"line-gone\"\n /** It's on a whole file, not a line. */\n | \"file\"\n /** No block on the page holds its line. */\n | \"no-block\";\n\n/** A thread, located in the working tree. */\nexport interface LocatedThread extends Thread {\n /** The file's content path. */\n path: string;\n /**\n * The thread's lines: in the working tree for the right side, at the diff's\n * base for the left side. `undefined` when it's detached.\n */\n lines: { first: number; last: number } | undefined;\n /** Why it's detached, or `undefined` when it's on lines. */\n detached: DetachReason | undefined;\n}\n\n/** A page's threads. */\nexport interface PlacedThreads {\n /** Threads on the page's blocks, by anchor, in the page's order. */\n blocks: { anchor: Anchor; threads: LocatedThread[] }[];\n /** Threads on removed text, by the removed block's anchor. */\n removed: { anchor: Anchor; threads: LocatedThread[] }[];\n /** Threads on the page's files that aren't on any block. */\n detached: LocatedThread[];\n}\n\n/** What locating needs to know about the checkout and the pull request. */\nexport interface LocateContext {\n /** The repository's top-level directory. */\n root: string;\n /** The content root's path in the repository, with a trailing `/`, or `\"\"` at the root. */\n contentPrefix: string;\n /** The pull request's head commit, which current threads' lines are counted at. */\n headOid: string;\n}\n\n/**\n * Locates threads in the working tree. Threads on files outside the content\n * root are left out; every other thread is returned, on lines or detached.\n */\nexport async function locateThreads(\n threads: readonly Thread[],\n context: LocateContext,\n): Promise<LocatedThread[]> {\n const maps = new Map<string, Promise<LineMap>>();\n const mapFor = (commit: string, file: string): Promise<LineMap> => {\n const key = `${commit}\\0${file}`;\n let map = maps.get(key);\n if (map === undefined) {\n map = lineMap(context.root, commit, file, \"to-worktree\");\n maps.set(key, map);\n }\n return map;\n };\n const located: LocatedThread[] = [];\n for (const thread of threads) {\n if (!thread.repositoryPath.startsWith(context.contentPrefix)) continue;\n const path = thread.repositoryPath.slice(context.contentPrefix.length);\n const result = (lines: LocatedThread[\"lines\"], detached?: DetachReason, quote?: string) =>\n located.push({\n ...thread,\n path,\n lines,\n detached,\n quote: quote ?? thread.quote,\n });\n\n if (thread.kind === \"conversation\" || thread.side === \"LEFT\") {\n const last = thread.kind === \"review\" && thread.outdated ? undefined : thread.line;\n if (thread.subject === \"file\") result(undefined, \"file\");\n else if (last === null || last === undefined) {\n result(undefined, \"line-gone\", hunkQuote(thread, thread.side));\n } else {\n const first = thread.startLine ?? last;\n result({ first, last }, undefined, thread.quote ?? hunkQuote(thread, thread.side));\n }\n continue;\n }\n if (thread.subject === \"file\") {\n result(undefined, \"file\");\n continue;\n }\n const commit = thread.outdated ? thread.originalCommit : context.headOid;\n const last = thread.outdated ? thread.originalLine : thread.line;\n const start = (thread.outdated ? thread.originalStartLine : thread.startLine) ?? last;\n if (commit === undefined || last === null || start === null) {\n result(undefined, \"line-gone\", hunkQuote(thread, \"RIGHT\"));\n continue;\n }\n const original = async () =>\n (await linesAt(context.root, commit, thread.repositoryPath, start, last)) ??\n hunkQuote(thread, \"RIGHT\");\n const map = await mapFor(commit, thread.repositoryPath);\n // When some of the thread's lines went but others stayed, it keeps what's left.\n const kept: number[] = [];\n let replaced = false;\n for (let line = start; line <= last; line++) {\n const mapped = map.map(line);\n if (mapped === undefined) replaced = true;\n else {\n kept.push(mapped.line);\n replaced ||= mapped.replaced;\n }\n }\n if (kept.length === 0) {\n result(undefined, \"line-gone\", await original());\n continue;\n }\n // Reworded text keeps its place, marked outdated, with what it said.\n const outdated = thread.outdated || replaced;\n located.push({\n ...thread,\n path,\n lines: { first: Math.min(...kept), last: Math.max(...kept) },\n detached: undefined,\n outdated,\n quote: outdated ? await original() : thread.quote,\n });\n }\n return located;\n}\n\n/** The thread's lines, from the end of GitHub's diff hunk. */\nfunction hunkQuote(thread: Thread, side: Side): string | undefined {\n if (thread.diffHunk === undefined) return undefined;\n const skip = side === \"LEFT\" ? \"+\" : \"-\";\n const lines = thread.diffHunk\n .split(/\\r?\\n/)\n .filter((line) => !line.startsWith(\"@@\") && !line.startsWith(skip))\n .map((line) => line.slice(1));\n const last = thread.outdated ? thread.originalLine : thread.line;\n const first = (thread.outdated ? thread.originalStartLine : thread.startLine) ?? last;\n const count = first !== null && last !== null ? last - first + 1 : 1;\n return lines.slice(-Math.max(1, count)).join(\"\\n\");\n}\n\n/**\n * The threads on one page. A thread goes on the smallest block that holds all\n * its lines (or, failing that, its last line), on every place that block is\n * shown: a fragment included twice is two blocks with the same source. A\n * thread on one of the page's files that no block holds is detached, as are\n * detached threads on those files. Conversation comments marked for another\n * build are left out.\n */\nexport function placeOnPage(page: PageRef, threads: readonly LocatedThread[]): PlacedThreads {\n const anchors = parseAnchors(page.anchors);\n const removedAnchors = parseAnchors(page.removed ?? []);\n const files = new Set([page.path, ...anchors.map((a) => a.range.path)]);\n const onBlocks = new Map<string, LocatedThread[]>();\n const onRemoved = new Map<string, LocatedThread[]>();\n const detached: LocatedThread[] = [];\n\n for (const thread of threads) {\n const build = thread.marker?.build;\n if (build !== undefined && build !== page.build) continue;\n if (thread.lines === undefined) {\n if (files.has(thread.path)) detached.push(thread);\n continue;\n }\n const left = thread.kind === \"review\" && thread.side === \"LEFT\";\n const matches = holding(left ? removedAnchors : anchors, thread.path, thread.lines);\n if (matches.length === 0) {\n if (files.has(thread.path)) detached.push({ ...thread, detached: \"no-block\" });\n continue;\n }\n const into = left ? onRemoved : onBlocks;\n for (const match of matches) {\n const key = anchorKey(match.anchor);\n const list = into.get(key) ?? [];\n list.push(thread);\n into.set(key, list);\n }\n }\n\n const collect = (list: ParsedAnchor[], by: Map<string, LocatedThread[]>) => {\n const out: PlacedThreads[\"blocks\"] = [];\n const seen = new Set<string>();\n for (const { anchor } of list) {\n const key = anchorKey(anchor);\n const found = by.get(key);\n if (found === undefined || seen.has(key)) continue;\n seen.add(key);\n out.push({ anchor, threads: found });\n }\n return out;\n };\n return {\n blocks: collect(anchors, onBlocks),\n removed: collect(removedAnchors, onRemoved),\n detached,\n };\n}\n\ninterface ParsedAnchor {\n anchor: Anchor;\n range: { path: string; first: number; last: number };\n}\n\nfunction parseAnchors(anchors: readonly Anchor[]): ParsedAnchor[] {\n const parsed: ParsedAnchor[] = [];\n for (const anchor of anchors) {\n const range = parseSource(anchor.source);\n if (range !== undefined) parsed.push({ anchor, range });\n }\n return parsed;\n}\n\n/** The smallest anchors on `path` holding `lines`, else the smallest holding its last line. */\nfunction holding(\n anchors: readonly ParsedAnchor[],\n path: string,\n lines: { first: number; last: number },\n): ParsedAnchor[] {\n const onFile = anchors.filter((a) => a.range.path === path);\n for (const [first, last] of [\n [lines.first, lines.last],\n [lines.last, lines.last],\n ] as const) {\n const containing = onFile.filter((a) => a.range.first <= first && a.range.last >= last);\n if (containing.length === 0) continue;\n const size = Math.min(...containing.map((a) => a.range.last - a.range.first));\n return containing.filter((a) => a.range.last - a.range.first === size);\n }\n return [];\n}\n"]}
1
+ {"version":3,"file":"place.js","sourceRoot":"","sources":["../../src/place/place.ts"],"names":[],"mappings":"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","sourcesContent":["// Placing review threads on rendered blocks. A thread is on a repository path\n// and a line at a commit; a block's anchor is a content path and lines in the\n// working tree. Locating moves each thread into the working tree's terms, once\n// per pull request; placing matches the result to one page's anchors.\nimport type { Side, Thread } from \"../shared/types.js\";\nimport { anchorKey, parseSource, type Anchor } from \"./anchor.js\";\nimport { lineMap, linesAt, type LineMap } from \"./lines.js\";\n\n/** A rendered page, as placing needs it. */\nexport interface PageRef {\n /** The build the page is rendered for. */\n build: string;\n /** The page's own content path. */\n path: string;\n /** Every anchored block on the page, in the page's order. */\n anchors: Anchor[];\n /**\n * Where the diff's removed, moved, and changed blocks on this page were\n * (each such change's `was`), for threads on the text they had.\n */\n removed?: Anchor[] | undefined;\n}\n\n/** Why a thread isn't on a block. */\nexport type DetachReason =\n /** The line it's on isn't in the working tree any more. */\n | \"line-gone\"\n /** It's on a whole file, not a line. */\n | \"file\"\n /** No block on the page holds its line. */\n | \"no-block\";\n\n/** A thread, located in the working tree. */\nexport interface LocatedThread extends Thread {\n /** The file's content path. */\n path: string;\n /**\n * The thread's lines: in the working tree for the right side, at the diff's\n * base for the left side. `undefined` when it's detached.\n */\n lines: { first: number; last: number } | undefined;\n /** Why it's detached, or `undefined` when it's on lines. */\n detached: DetachReason | undefined;\n}\n\n/** A page's threads. */\nexport interface PlacedThreads {\n /** Threads on the page's blocks, by anchor, in the page's order. */\n blocks: { anchor: Anchor; threads: LocatedThread[] }[];\n /** Threads on removed text, by the removed block's anchor. */\n removed: { anchor: Anchor; threads: LocatedThread[] }[];\n /** Threads on the page's files that aren't on any block. */\n detached: LocatedThread[];\n}\n\n/** What locating needs to know about the checkout and the pull request. */\nexport interface LocateContext {\n /** The repository's top-level directory. */\n root: string;\n /** The content root's path in the repository, with a trailing `/`, or `\"\"` at the root. */\n contentPrefix: string;\n /** The pull request's head commit, which current threads' lines are counted at. */\n headOid: string;\n}\n\n/**\n * Locates threads in the working tree. Threads on files outside the content\n * root are left out; every other thread is returned, on lines or detached.\n */\nexport async function locateThreads(\n threads: readonly Thread[],\n context: LocateContext,\n): Promise<LocatedThread[]> {\n const maps = new Map<string, Promise<LineMap>>();\n const mapFor = (commit: string, file: string): Promise<LineMap> => {\n const key = `${commit}\\0${file}`;\n let map = maps.get(key);\n if (map === undefined) {\n map = lineMap(context.root, commit, file, \"to-worktree\");\n maps.set(key, map);\n }\n return map;\n };\n const located: LocatedThread[] = [];\n for (const thread of threads) {\n if (!thread.repositoryPath.startsWith(context.contentPrefix)) continue;\n const path = thread.repositoryPath.slice(context.contentPrefix.length);\n const result = (lines: LocatedThread[\"lines\"], detached?: DetachReason, quote?: string) =>\n located.push({\n ...thread,\n path,\n lines,\n detached,\n quote: quote ?? thread.quote,\n });\n\n if (thread.kind === \"conversation\" || thread.side === \"LEFT\") {\n const last = thread.kind === \"review\" && thread.outdated ? undefined : thread.line;\n if (thread.subject === \"file\") result(undefined, \"file\");\n else if (last === null || last === undefined) {\n result(undefined, \"line-gone\", hunkQuote(thread, thread.side));\n } else {\n const first = thread.startLine ?? last;\n result({ first, last }, undefined, thread.quote ?? hunkQuote(thread, thread.side));\n }\n continue;\n }\n if (thread.subject === \"file\") {\n result(undefined, \"file\");\n continue;\n }\n const commit = thread.outdated ? thread.originalCommit : context.headOid;\n const last = thread.outdated ? thread.originalLine : thread.line;\n const start = (thread.outdated ? thread.originalStartLine : thread.startLine) ?? last;\n if (commit === undefined || last === null || start === null) {\n result(undefined, \"line-gone\", hunkQuote(thread, \"RIGHT\"));\n continue;\n }\n const original = async () =>\n (await linesAt(context.root, commit, thread.repositoryPath, start, last)) ??\n hunkQuote(thread, \"RIGHT\");\n const map = await mapFor(commit, thread.repositoryPath);\n // When some of the thread's lines went but others stayed, it keeps what's left.\n const kept: number[] = [];\n let replaced = false;\n for (let line = start; line <= last; line++) {\n const mapped = map.map(line);\n if (mapped === undefined) replaced = true;\n else {\n kept.push(mapped.line);\n replaced ||= mapped.replaced;\n }\n }\n if (kept.length === 0) {\n result(undefined, \"line-gone\", await original());\n continue;\n }\n // Reworded text keeps its place, marked outdated, with what it said.\n const outdated = thread.outdated || replaced;\n located.push({\n ...thread,\n path,\n lines: { first: Math.min(...kept), last: Math.max(...kept) },\n detached: undefined,\n outdated,\n quote: outdated ? await original() : thread.quote,\n });\n }\n return located;\n}\n\n/** The thread's lines, from the end of GitHub's diff hunk. */\nfunction hunkQuote(thread: Thread, side: Side): string | undefined {\n if (thread.diffHunk === undefined) return undefined;\n const skip = side === \"LEFT\" ? \"+\" : \"-\";\n const lines = thread.diffHunk\n .split(/\\r?\\n/)\n .filter((line) => !line.startsWith(\"@@\") && !line.startsWith(skip))\n .map((line) => line.slice(1));\n const last = thread.outdated ? thread.originalLine : thread.line;\n const first = (thread.outdated ? thread.originalStartLine : thread.startLine) ?? last;\n const count = first !== null && last !== null ? last - first + 1 : 1;\n return lines.slice(-Math.max(1, count)).join(\"\\n\");\n}\n\n/**\n * The threads on one page. A thread goes on the smallest block that holds all\n * its lines (or, failing that, its last line), on every place that block is\n * shown: a fragment included twice is two blocks with the same source. A\n * thread on one of the page's files that no block holds is detached, as are\n * detached threads on those files. Conversation comments marked for another\n * build are left out.\n */\nexport function placeOnPage(page: PageRef, threads: readonly LocatedThread[]): PlacedThreads {\n const anchors = parseAnchors(page.anchors);\n const removedAnchors = parseAnchors(page.removed ?? []);\n const files = new Set([page.path, ...anchors.map((a) => a.range.path)]);\n const onBlocks = new Map<string, LocatedThread[]>();\n const onRemoved = new Map<string, LocatedThread[]>();\n const detached: LocatedThread[] = [];\n\n for (const thread of threads) {\n const build = thread.marker?.build;\n if (build !== undefined && build !== page.build) continue;\n if (thread.lines === undefined) {\n if (files.has(thread.path)) detached.push(thread);\n continue;\n }\n const left = thread.kind === \"review\" && thread.side === \"LEFT\";\n const matches = holding(left ? removedAnchors : anchors, thread.path, thread.lines);\n if (matches.length === 0) {\n if (files.has(thread.path)) detached.push({ ...thread, detached: \"no-block\" });\n continue;\n }\n const into = left ? onRemoved : onBlocks;\n // A block and the element around it can share an anchor, as a one-line\n // note and its paragraph do: the thread goes on the anchor once.\n for (const key of new Set(matches.map((match) => anchorKey(match.anchor)))) {\n const list = into.get(key) ?? [];\n list.push(thread);\n into.set(key, list);\n }\n }\n\n const collect = (list: ParsedAnchor[], by: Map<string, LocatedThread[]>) => {\n const out: PlacedThreads[\"blocks\"] = [];\n const seen = new Set<string>();\n for (const { anchor } of list) {\n const key = anchorKey(anchor);\n const found = by.get(key);\n if (found === undefined || seen.has(key)) continue;\n seen.add(key);\n out.push({ anchor, threads: found });\n }\n return out;\n };\n return {\n blocks: collect(anchors, onBlocks),\n removed: collect(removedAnchors, onRemoved),\n detached,\n };\n}\n\ninterface ParsedAnchor {\n anchor: Anchor;\n range: { path: string; first: number; last: number };\n}\n\nfunction parseAnchors(anchors: readonly Anchor[]): ParsedAnchor[] {\n const parsed: ParsedAnchor[] = [];\n for (const anchor of anchors) {\n const range = parseSource(anchor.source);\n if (range !== undefined) parsed.push({ anchor, range });\n }\n return parsed;\n}\n\n/** The smallest anchors on `path` holding `lines`, else the smallest holding its last line. */\nfunction holding(\n anchors: readonly ParsedAnchor[],\n path: string,\n lines: { first: number; last: number },\n): ParsedAnchor[] {\n const onFile = anchors.filter((a) => a.range.path === path);\n for (const [first, last] of [\n [lines.first, lines.last],\n [lines.last, lines.last],\n ] as const) {\n const containing = onFile.filter((a) => a.range.first <= first && a.range.last >= last);\n if (containing.length === 0) continue;\n const size = Math.min(...containing.map((a) => a.range.last - a.range.first));\n return containing.filter((a) => a.range.last - a.range.first === size);\n }\n return [];\n}\n"]}
@@ -0,0 +1,90 @@
1
+ import type { LocatedThread } from "../place/place.js";
2
+ /** The most characters a prompt has: what the agents' own links take. */
3
+ export declare const LIMIT = 5000;
4
+ /** The most pages a prompt names as showing a fragment. */
5
+ export declare const MAX_SHOWN_ON = 10;
6
+ /** The most threads the prompt about every open thread lists. */
7
+ export declare const MAX_OPEN = 15;
8
+ /** The most characters of a thread's first comment that prompt quotes. */
9
+ export declare const MAX_FIRST = 200;
10
+ /** The most lines of a thread's source a prompt quotes. */
11
+ export declare const MAX_SOURCE_LINES = 40;
12
+ /** Where the project is, for the paths and commands a prompt writes. */
13
+ export interface PromptProject {
14
+ /** The project's folder in the repository, `/`-separated, or `""` at its root. */
15
+ folder: string;
16
+ /** The content root, relative to the project's folder, or `""` when it's the same. */
17
+ contentRoot: string;
18
+ /** The `AGENTS.md` the agent follows, from the repository's root, or `undefined` when there's none. */
19
+ agents: string | undefined;
20
+ /**
21
+ * The command that runs Ascribe where the agent is, when `ascribe` isn't
22
+ * it: the path of the binary the editor runs. The prompt's commands start
23
+ * with it; without it, with `ascribe`.
24
+ */
25
+ program?: string | undefined;
26
+ }
27
+ /** What the prompt about one thread is built from. */
28
+ export interface ThreadPromptInput {
29
+ project: PromptProject;
30
+ thread: LocatedThread;
31
+ /**
32
+ * The thread's lines as the file on disk has them, from 1; `undefined` when
33
+ * the thread has no lines in the working tree, or they couldn't be read.
34
+ */
35
+ source?: {
36
+ first: number;
37
+ last: number;
38
+ text: string;
39
+ } | undefined;
40
+ /** Whether the file has unsaved changes in an editor. */
41
+ unsaved?: boolean;
42
+ /** The pages that show the thread's block when it's in a fragment, by content path. */
43
+ shownOn?: readonly string[];
44
+ }
45
+ /** What the prompt about every open thread is built from. */
46
+ export interface OpenThreadsInput {
47
+ project: PromptProject;
48
+ /** The pull request's number. */
49
+ pullRequest: number;
50
+ /** Every thread on the pull request's pages. */
51
+ threads: readonly LocatedThread[];
52
+ }
53
+ /** The prompt about one thread: "Address this review comment on `<file>`." */
54
+ export declare function threadPrompt(input: ThreadPromptInput): string;
55
+ /**
56
+ * The prompt about every open thread: "Address the N open review comments on
57
+ * pull request #128." Resolved threads are left out, and so are threads whose
58
+ * only comments are the viewer's unsent ones. `undefined` when none is left.
59
+ */
60
+ export declare function openThreadsPrompt(input: OpenThreadsInput): string | undefined;
61
+ /** The most threads the list of open threads for an agent has. */
62
+ export declare const MAX_LISTED = 20;
63
+ /** The most characters of a comment that list quotes. */
64
+ export declare const MAX_COMMENT = 2000;
65
+ /** What the list of open threads for an agent is built from. */
66
+ export interface OpenThreadsListInput {
67
+ project: PromptProject;
68
+ /** The pull request's number. */
69
+ pullRequest: number;
70
+ /** Every thread on the pull request's pages. */
71
+ threads: readonly LocatedThread[];
72
+ /**
73
+ * The pages that show a thread's file, by content path: its own page, and
74
+ * those that include it.
75
+ */
76
+ shownOn(path: string): readonly string[];
77
+ }
78
+ /**
79
+ * The pull request's open threads, for an agent's tool (VS Code's
80
+ * `ascribe_review_threads`) rather than a task: each with its place, the
81
+ * pages that show it, what's known about it, and its comments, which are
82
+ * other people's text and so go in under the same sentence and fence as in
83
+ * a prompt. The open threads are those `openThreadsPrompt` lists, in its
84
+ * order; at most `MAX_LISTED`, then how many more.
85
+ */
86
+ export declare function openThreadsList(input: OpenThreadsListInput): string;
87
+ /** Takes out HTML comments, where hidden text, and an instruction, would hide. */
88
+ export declare function stripComments(body: string): string;
89
+ /** A word as a POSIX shell reads it: as it is when it's plain, else in single quotes. */
90
+ export declare function shellWord(word: string): string;