@vintasoftware/pr-review-canvas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +192 -0
- package/bin/pr-review.mjs +5 -0
- package/docs/reference.md +340 -0
- package/package.json +74 -0
- package/pr-review.config.example.yml +68 -0
- package/prompts/chat-seed.md +64 -0
- package/prompts/generation-format.md +255 -0
- package/prompts/generation-strict.md +34 -0
- package/prompts/generation-surfacing.md +67 -0
- package/prompts/layers-default.md +13 -0
- package/prompts/quality-standards.md +32 -0
- package/skills/pr-review-canvas/SKILL.md +177 -0
- package/src/acpx/acpx.ts +530 -0
- package/src/acpx/agents.ts +85 -0
- package/src/acpx/events.ts +216 -0
- package/src/acpx/ndjson.ts +69 -0
- package/src/acpx/preflight.ts +44 -0
- package/src/canvas/export.ts +95 -0
- package/src/canvas/import.ts +138 -0
- package/src/canvas/name.ts +55 -0
- package/src/canvas/zip.ts +123 -0
- package/src/chat/chat-manager.ts +389 -0
- package/src/chat/context.ts +160 -0
- package/src/chat/seed.ts +71 -0
- package/src/chat/threads.ts +114 -0
- package/src/cli.ts +199 -0
- package/src/commands.ts +424 -0
- package/src/config.ts +142 -0
- package/src/contract/api.ts +190 -0
- package/src/contract/canvas-manifest.ts +29 -0
- package/src/contract/chat.ts +76 -0
- package/src/contract/comments.ts +96 -0
- package/src/contract/discovery.ts +20 -0
- package/src/contract/generation-context.ts +77 -0
- package/src/contract/keys.ts +14 -0
- package/src/contract/links.ts +5 -0
- package/src/contract/mermaid-fences.ts +4 -0
- package/src/contract/review-artifact.ts +324 -0
- package/src/contract/settings.ts +144 -0
- package/src/contract/state.ts +46 -0
- package/src/contract/validation.ts +43 -0
- package/src/git/diff-collector.ts +151 -0
- package/src/git/git.ts +115 -0
- package/src/git/lang.ts +1 -0
- package/src/git/materialize.ts +79 -0
- package/src/git/patch-lines.ts +60 -0
- package/src/github/attachments.ts +288 -0
- package/src/github/capabilities.ts +112 -0
- package/src/github/comments.ts +132 -0
- package/src/github/gh.ts +196 -0
- package/src/github/post-comment.ts +104 -0
- package/src/github/post-review.ts +44 -0
- package/src/github/pr.ts +133 -0
- package/src/github/review-body.ts +72 -0
- package/src/github/threads.ts +63 -0
- package/src/paths.ts +10 -0
- package/src/project-config.ts +219 -0
- package/src/prompt-files.ts +26 -0
- package/src/review/diagram-nodes.ts +227 -0
- package/src/review/doctor.ts +139 -0
- package/src/review/glob.ts +33 -0
- package/src/review/install-skill.ts +107 -0
- package/src/review/normalize.ts +209 -0
- package/src/review/prepare.ts +165 -0
- package/src/review/prompt.ts +233 -0
- package/src/review/publish.ts +209 -0
- package/src/review/skill-command.ts +4 -0
- package/src/review/test-paths.ts +32 -0
- package/src/review/text-length.ts +15 -0
- package/src/review/trim-caps.ts +114 -0
- package/src/review/validate-folds.ts +110 -0
- package/src/review/validate.ts +520 -0
- package/src/server/app.ts +46 -0
- package/src/server/bundle.ts +266 -0
- package/src/server/capped-body.ts +62 -0
- package/src/server/context.ts +174 -0
- package/src/server/env.ts +7 -0
- package/src/server/errors.ts +65 -0
- package/src/server/html.ts +140 -0
- package/src/server/node-server.ts +42 -0
- package/src/server/routes/api.ts +256 -0
- package/src/server/routes/chat-routes.ts +221 -0
- package/src/server/routes/pages.ts +64 -0
- package/src/server/routes/review-routes.ts +245 -0
- package/src/server/routes/static.ts +114 -0
- package/src/server/security.ts +104 -0
- package/src/server/sse.ts +67 -0
- package/src/store/atomic-json.ts +68 -0
- package/src/store/canvas-store.ts +120 -0
- package/src/store/data-dir.ts +29 -0
- package/src/store/derived-store.ts +93 -0
- package/src/store/pr-store.ts +69 -0
- package/src/store/settings-store.ts +152 -0
- package/src/store/state-store.ts +121 -0
- package/static/js/anchors.js +141 -0
- package/static/js/api.js +542 -0
- package/static/js/app.js +418 -0
- package/static/js/ask.js +35 -0
- package/static/js/chat-context.js +137 -0
- package/static/js/chat-scroll.js +114 -0
- package/static/js/chat.js +843 -0
- package/static/js/code-folds.js +200 -0
- package/static/js/commands.js +110 -0
- package/static/js/comment-link.js +37 -0
- package/static/js/composer.js +241 -0
- package/static/js/contract-types.d.ts +59 -0
- package/static/js/deep-link.js +160 -0
- package/static/js/diagram.js +582 -0
- package/static/js/diff-decorations.js +204 -0
- package/static/js/diff-renderer.js +860 -0
- package/static/js/dom.js +145 -0
- package/static/js/download.js +52 -0
- package/static/js/empty-state.js +161 -0
- package/static/js/errors.js +135 -0
- package/static/js/fences.js +90 -0
- package/static/js/header.js +134 -0
- package/static/js/hunks.js +62 -0
- package/static/js/import-zone.js +95 -0
- package/static/js/interactions.js +952 -0
- package/static/js/keyboard.js +131 -0
- package/static/js/keys.js +97 -0
- package/static/js/lang.js +54 -0
- package/static/js/layers.js +596 -0
- package/static/js/links.js +150 -0
- package/static/js/markdown.js +232 -0
- package/static/js/mermaid-fences.js +55 -0
- package/static/js/nav.js +91 -0
- package/static/js/overview.js +85 -0
- package/static/js/points.js +247 -0
- package/static/js/progress.js +49 -0
- package/static/js/proposed-comment.js +133 -0
- package/static/js/quick-questions.js +216 -0
- package/static/js/regenerate.js +69 -0
- package/static/js/review-session.js +257 -0
- package/static/js/scroll-spy.js +66 -0
- package/static/js/selection.js +193 -0
- package/static/js/settings.js +206 -0
- package/static/js/signoff.js +171 -0
- package/static/js/skin.js +56 -0
- package/static/js/store.js +35 -0
- package/static/js/theme.js +56 -0
- package/static/js/threads.js +78 -0
- package/static/js/vendor.d.ts +15 -0
- package/static/styles/base.css +223 -0
- package/static/styles/chat-tools.css +130 -0
- package/static/styles/chat.css +140 -0
- package/static/styles/commands.css +156 -0
- package/static/styles/diff.css +258 -0
- package/static/styles/header.css +114 -0
- package/static/styles/layout.css +123 -0
- package/static/styles/panels.css +152 -0
- package/static/styles/responsive.css +80 -0
- package/static/styles/review-actions.css +124 -0
- package/static/styles/review.css +473 -0
- package/static/styles/skin-github.css +356 -0
- package/static/styles.css +14 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// Canvas links as navigation. The markdown, the diagrams and the chat all write links in the
|
|
3
|
+
// canvas scheme (#layer:, #file:, #hunk:, #line:), and the same string works as a URL fragment,
|
|
4
|
+
// so `…/review/278#line:path/to/file.ts:40` opens the page on that line and a click on a link
|
|
5
|
+
// leaves a URL the reader can share. The jump itself is `jumpTo` in anchors.js; this module
|
|
6
|
+
// decides when it runs.
|
|
7
|
+
import { cssEscape, drawCardOf, jumpTo, keyFromPath, nearestRow } from './anchors.js'
|
|
8
|
+
import { flash, scrollIntoViewSafe } from './dom.js'
|
|
9
|
+
import { fileAnchorId, layerAnchorId } from './keys.js'
|
|
10
|
+
import { parseLink } from './links.js'
|
|
11
|
+
import { markRailCurrent } from './scroll-spy.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The fragment with its percent escapes resolved, for a browser that hands back an encoded hash.
|
|
15
|
+
* A path holds `/`, `:` and `-`, which the parser reads raw, and a malformed escape is left as
|
|
16
|
+
* written rather than throwing.
|
|
17
|
+
* @param {string} hash
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
export function decodeHash(hash) {
|
|
21
|
+
try {
|
|
22
|
+
return decodeURIComponent(hash)
|
|
23
|
+
} catch {
|
|
24
|
+
return hash
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Follows one canvas link: scrolls to its target and highlights it. A line whose row is folded
|
|
30
|
+
* as noise or missing lands on the nearest row instead, marked `is-approx` the way the point and
|
|
31
|
+
* thread rows are. Returns false when the link does not parse or has no target on the page.
|
|
32
|
+
*
|
|
33
|
+
* The string is tried as written first, so a path that holds a literal `%` keeps it, and then
|
|
34
|
+
* with its escapes resolved, which is how the same link comes back out of the URL bar.
|
|
35
|
+
* @param {string} href
|
|
36
|
+
* @param {ParentNode} [root]
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
export function followLink(href, root = document) {
|
|
40
|
+
if (jumpToTarget(href, root)) {
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
const decoded = decodeHash(href)
|
|
44
|
+
return decoded !== href && jumpToTarget(decoded, root)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} target
|
|
49
|
+
* @param {ParentNode} root
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
function jumpToTarget(target, root) {
|
|
53
|
+
const link = parseLink(target)
|
|
54
|
+
if (link === null) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
if (link.kind === 'line') {
|
|
58
|
+
return jumpToLine(root, link)
|
|
59
|
+
}
|
|
60
|
+
if (!jumpTo(target, root)) {
|
|
61
|
+
return false
|
|
62
|
+
}
|
|
63
|
+
if (link.kind === 'layer') {
|
|
64
|
+
markRailCurrent(root, layerAnchorId(link.layerKey))
|
|
65
|
+
}
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The row of a line link, or the closest one when that line is folded away as noise or is not
|
|
71
|
+
* among the rows on screen.
|
|
72
|
+
* @param {ParentNode} root
|
|
73
|
+
* @param {{ path: string, start: number, side: 'new' | 'old' }} link
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
function jumpToLine(root, link) {
|
|
77
|
+
const key = keyFromPath(link.path)
|
|
78
|
+
drawCardOf(root, link.path)
|
|
79
|
+
const card = root.querySelector(`#${cssEscape(fileAnchorId(key))}`)
|
|
80
|
+
const near = nearestRow(card ?? root, key, link.side, link.start)
|
|
81
|
+
if (near === null) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
const body = near.row.closest('.file')?.querySelector('.file-body')
|
|
85
|
+
if (body instanceof HTMLElement && body.hidden) {
|
|
86
|
+
body.hidden = false
|
|
87
|
+
}
|
|
88
|
+
if (near.approx) {
|
|
89
|
+
near.row.classList.add('is-approx')
|
|
90
|
+
}
|
|
91
|
+
scrollIntoViewSafe(near.row)
|
|
92
|
+
flash(near.row)
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A plain left click the page is free to take over. A modified click is the reader asking the
|
|
98
|
+
* browser for a new tab or window.
|
|
99
|
+
* @param {MouseEvent} event
|
|
100
|
+
*/
|
|
101
|
+
function plainClick(event) {
|
|
102
|
+
return (
|
|
103
|
+
!event.defaultPrevented &&
|
|
104
|
+
(event.button === undefined || event.button === 0) &&
|
|
105
|
+
!event.metaKey &&
|
|
106
|
+
!event.ctrlKey &&
|
|
107
|
+
!event.shiftKey &&
|
|
108
|
+
!event.altKey
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Wires canvas links for one screen: clicks on the anchors the renderers wrote, the fragment the
|
|
114
|
+
* page was opened with, and every later `hashchange`. `stop` removes all of it, so a re-render
|
|
115
|
+
* never leaves two sets of listeners behind.
|
|
116
|
+
* @param {HTMLElement | Document} root
|
|
117
|
+
* @param {{ view?: Window, jumpOnInit?: boolean }} [opts]
|
|
118
|
+
* @returns {{ stop: () => void, follow: (href: string) => boolean, followHash: () => boolean }}
|
|
119
|
+
*/
|
|
120
|
+
export function initDeepLinks(root, opts = {}) {
|
|
121
|
+
const view = opts.view ?? window
|
|
122
|
+
const listeners = new AbortController()
|
|
123
|
+
|
|
124
|
+
const followHash = () => followLink(view.location.hash, root)
|
|
125
|
+
|
|
126
|
+
root.addEventListener(
|
|
127
|
+
'click',
|
|
128
|
+
event => {
|
|
129
|
+
const mouse = /** @type {MouseEvent} */ (event)
|
|
130
|
+
const el = event.target instanceof Element ? event.target.closest('a[data-link], a[href^="#"]') : null
|
|
131
|
+
if (el === null || !plainClick(mouse)) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
const href = el.getAttribute('data-link') ?? el.getAttribute('href') ?? ''
|
|
135
|
+
if (parseLink(href) === null) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
event.preventDefault()
|
|
139
|
+
followLink(href, root)
|
|
140
|
+
// The URL stays shareable, and no second jump follows: replaceState fires no hashchange.
|
|
141
|
+
try {
|
|
142
|
+
view.history.replaceState(null, '', href)
|
|
143
|
+
} catch {
|
|
144
|
+
// A view without session history keeps the URL it had; the jump already happened.
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{ signal: listeners.signal }
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
view.addEventListener('hashchange', () => void followHash(), { signal: listeners.signal })
|
|
151
|
+
if (opts.jumpOnInit !== false) {
|
|
152
|
+
followHash()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
stop: () => listeners.abort(),
|
|
157
|
+
follow: href => followLink(href, root),
|
|
158
|
+
followHash,
|
|
159
|
+
}
|
|
160
|
+
}
|