reviewzy 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +2 -2
- package/src/dashboard/mount.ts +20 -3
- package/src/dashboard/static/components.css +7579 -600
- package/src/dashboard/static/cursor-rules.js +79 -0
- package/src/dashboard/static/cursor.css +134 -0
- package/src/dashboard/static/cursor.js +1749 -0
- package/src/dashboard/static/dashboard.css +281 -258
- package/src/dashboard/static/dashboard.js +331 -209
- package/src/dashboard/static/fonts/jetbrainsmono-latin-ext.woff2 +0 -0
- package/src/dashboard/static/fonts/jetbrainsmono-latin.woff2 +0 -0
- package/src/dashboard/static/fonts/onest-cyrillic.woff2 +0 -0
- package/src/dashboard/static/fonts/onest-latin-ext.woff2 +0 -0
- package/src/dashboard/static/fonts/onest-latin.woff2 +0 -0
- package/src/dashboard/static/icons.svg +184 -0
- package/src/dashboard/static/tokens.css +607 -110
- package/src/dashboard/static/ui.js +2487 -0
- package/src/dashboard/views.tsx +1204 -950
- package/src/dashboard/word-diff.ts +18 -25
- package/src/db/migrations.ts +5 -6
- package/src/db/queries.ts +38 -1
- package/src/mcp/await-approved.ts +17 -10
- package/src/mcp/fetch-approved.ts +11 -6
- package/src/mcp/file-entries.ts +45 -22
- package/src/mcp/list-entries.ts +51 -55
- package/src/mcp/mark-applied.ts +5 -5
- package/src/mcp/server.ts +8 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// cursor-rules.js - pure, DOM-free, dynamic-imported by the ring at boot.
|
|
2
|
+
const UNUSABLE = /::|&/; // a pseudo-element is a box closest() can never return
|
|
3
|
+
const STATE_PSEUDO = /:(?:hover|active|focus|focus-visible|focus-within|visited|link|target)\b/g;
|
|
4
|
+
|
|
5
|
+
// clickable with no cursor declaration anywhere: the browser draws the pointer itself.
|
|
6
|
+
// No `label`: a field label is not a target, and a label that WRAPS its control takes its
|
|
7
|
+
// pointer from the stylesheet sweep instead, which is where the difference belongs.
|
|
8
|
+
export const SEMANTIC_POINTER = [
|
|
9
|
+
'a[href]', 'area[href]', 'button', 'summary', 'select',
|
|
10
|
+
'input[type="checkbox"]', 'input[type="radio"]', 'input[type="range"]',
|
|
11
|
+
'input[type="color"]', 'input[type="file"]', 'input[type="submit"]',
|
|
12
|
+
'input[type="reset"]', 'input[type="button"]',
|
|
13
|
+
'[role="button"]', '[role="link"]', '[role="tab"]',
|
|
14
|
+
'[role="menuitem"]', '[role="option"]', '[role="switch"]',
|
|
15
|
+
].join(', ');
|
|
16
|
+
export const SEMANTIC_BLOCKED = ':disabled, [aria-disabled="true"]';
|
|
17
|
+
|
|
18
|
+
// split on the commas separating selectors, not the ones inside :is() or an attribute value
|
|
19
|
+
export function splitSelectorList(text) {
|
|
20
|
+
const parts = [];
|
|
21
|
+
let depth = 0, quote = '', start = 0;
|
|
22
|
+
for (let i = 0; i < text.length; i++) {
|
|
23
|
+
const c = text[i];
|
|
24
|
+
if (quote) { if (c === quote && text[i - 1] !== '\\') quote = ''; continue; }
|
|
25
|
+
if (c === '"' || c === "'") quote = c;
|
|
26
|
+
else if (c === '(' || c === '[') depth++;
|
|
27
|
+
else if (c === ')' || c === ']') depth--;
|
|
28
|
+
else if (c === ',' && depth === 0) { parts.push(text.slice(start, i)); start = i + 1; }
|
|
29
|
+
}
|
|
30
|
+
parts.push(text.slice(start));
|
|
31
|
+
return parts.map(s => s.trim()).filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* One walk for both collectors: every rule of every sheet, in document order. `classify`
|
|
35
|
+
reads a rule and returns its verdict, or a falsy value to skip it; `take` gets each
|
|
36
|
+
usable selector that rule names, with that verdict. A state pseudo is stripped before
|
|
37
|
+
`take` sees it, so `.x:hover` names `.x` and folds onto whatever `.x` already said. */
|
|
38
|
+
const sweep = (sheets, classify, take) => {
|
|
39
|
+
const visit = rules => {
|
|
40
|
+
for (const rule of rules || []) {
|
|
41
|
+
const verdict = rule.selectorText && rule.style ? classify(rule) : null;
|
|
42
|
+
if (verdict) for (const part of splitSelectorList(rule.selectorText)) {
|
|
43
|
+
if (UNUSABLE.test(part)) continue;
|
|
44
|
+
const clean = part.replace(STATE_PSEUDO, '').trim();
|
|
45
|
+
if (clean) take(clean, verdict);
|
|
46
|
+
}
|
|
47
|
+
if (rule.cssRules) visit(rule.cssRules); // @media, @supports, nesting
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
for (const sheet of sheets || []) {
|
|
51
|
+
try { visit(sheet.cssRules); } catch { /* cross-origin sheet, nothing worth a broken boot */ }
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/* Every selector any rule gives one of `values` for `prop`, de-duplicated. It NARROWS a
|
|
56
|
+
walk, never decides one: the computed style still has the last word, so a selector
|
|
57
|
+
whose rule lives under a media query the viewport does not match costs one match and
|
|
58
|
+
nothing else. The alternative, reading `getComputedStyle` for every element in the
|
|
59
|
+
document, forces rendering inside any `content-visibility` subtree it crosses, which
|
|
60
|
+
Chrome reports and this system's collapsed rows all carry. */
|
|
61
|
+
export function collectPropSelectors(sheets, prop, values) {
|
|
62
|
+
const found = new Set();
|
|
63
|
+
sweep(sheets, rule => values.includes(rule.style[prop]), clean => found.add(clean));
|
|
64
|
+
return [...found];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// `inert` is the `cursor: default` set: it lets a rule that switches a clickable class back off
|
|
68
|
+
// (a current breadcrumb, a readonly field) outrank the pointer entry it was written to override.
|
|
69
|
+
export function collectCursorSelectors(sheets) {
|
|
70
|
+
const buckets = { pointer: new Set(), inert: new Set(), blocked: new Set() };
|
|
71
|
+
const bucketFor = c => c === 'pointer' ? buckets.pointer
|
|
72
|
+
: c === 'default' ? buckets.inert
|
|
73
|
+
: c === 'not-allowed' ? buckets.blocked : null;
|
|
74
|
+
sweep(sheets, rule => bucketFor(rule.style.cursor), (clean, bucket) => {
|
|
75
|
+
for (const b of Object.values(buckets)) b.delete(clean); // document order stands in for the cascade
|
|
76
|
+
bucket.add(clean);
|
|
77
|
+
});
|
|
78
|
+
return { pointer: [...buckets.pointer], inert: [...buckets.inert], blocked: [...buckets.blocked] };
|
|
79
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
the ambient layer, cursor v3
|
|
3
|
+
The jelly ring with its aim dot, lit up with speed; its tube
|
|
4
|
+
trail in the comet blue, leaving the mark 4px narrower than it is across the
|
|
5
|
+
motion and stopping 2px short of its keyline; the parting dot grid, its droplets at
|
|
6
|
+
speed and its click ripple plus splash. Everything the cursor
|
|
7
|
+
draws lives on one canvas inside #cursor, so every stroke keeps its
|
|
8
|
+
declared width whatever the ring's shape does. Load after components.css;
|
|
9
|
+
cursor.js boots it. Rules and mechanisms: cursor.md.
|
|
10
|
+
Contrast, from the repo root, `node tools/contrast.mjs "<fg>" "<bg>"`, on
|
|
11
|
+
--bg / --surface-1 / --surface-2 / --surface-3 / --well / --sky-peak:
|
|
12
|
+
dark ink oklch(0.76 0.110 238) 7.49 / 6.48 / 5.48 / 4.48 / 8.31 / 6.33
|
|
13
|
+
dark lit ink oklch(0.90 0.080 238) 11.73 / 10.15 / 8.59 / 7.01 / 13.02 / 9.92
|
|
14
|
+
dark ripple oklch(0.82 0.115 238) the trail core's row
|
|
15
|
+
dark keyline --bg on the --accent fill 7.49, on --accent-press 6.02
|
|
16
|
+
dark refuse oklch(0.72 0.140 15) 5.98 --bg, 5.18 --surface-1
|
|
17
|
+
dark trail core oklch(0.82 0.115 238) 9.15 / 7.91 / 6.70 / 5.47 / 10.15 / 7.73
|
|
18
|
+
dark trail edge --accent, the ink's row
|
|
19
|
+
dark spray oklch(0.88 0.070 238) 11.13 / 9.62 / 8.14 / 6.65 / 12.34 / 9.40
|
|
20
|
+
dark spray hot oklch(0.80 0.130 39) 7.93 / 6.86 / 5.81 / 4.74 / 8.80 / 6.71
|
|
21
|
+
light ink oklch(0.50 0.130 238) 5.06 / 5.69 / 4.76 / 4.34 / 4.62 / 5.68
|
|
22
|
+
light lit ink oklch(0.55 0.160 238) 4.01 / 4.51 / 3.78 / 3.44 / 3.66 / 4.50
|
|
23
|
+
light ripple the ink's row
|
|
24
|
+
light keyline --bg on the --accent fill 4.09, on --accent-press 6.50
|
|
25
|
+
light refuse oklch(0.52 0.190 15) 5.36 --bg, 6.02 --surface-1
|
|
26
|
+
light trail core oklch(0.42 0.120 238) 7.10 / 7.98 / 6.68 / 6.10 / 6.48 / 7.97
|
|
27
|
+
light trail edge --accent 4.09 / 4.60 / 3.85 / 3.52 / 3.74 / 4.60
|
|
28
|
+
light spray oklch(0.45 0.130 238) 6.21 / 6.98 / 5.85 / 5.33 / 5.67 / 6.98
|
|
29
|
+
light spray hot oklch(0.50 0.140 39) 5.61 / 6.30 / 5.28 / 4.81 / 5.12 / 6.30
|
|
30
|
+
The tube paints at 0.9 alpha, 0.6 over text; composited on --surface-3, its
|
|
31
|
+
worst surface, `node tools/contrast.mjs "<fg> / 0.9" "<surface-3>"`: dark
|
|
32
|
+
core 4.76, edge 3.95; light core 5.01, edge 3.10. Over the accent fill the trail core
|
|
33
|
+
reads 1.22 dark and 1.73 light, as v2's comet reads 1.00 / 1.24:
|
|
34
|
+
the mark and the droplets carry the page keyline there, the tube does not.
|
|
35
|
+
The bloom is the glow ink at 0.105 at full speed on its own node, blended
|
|
36
|
+
`screen` in dark and `darken` in light, the halo's two directions, so it
|
|
37
|
+
lightens what it covers and never shades it. Its peak is the halo's bound:
|
|
38
|
+
the highest opacity at which no text pair reads lower than under the ring's
|
|
39
|
+
12% hover fill, the bloom and the fill each blended over the glyph and its
|
|
40
|
+
ground alike, from the tool's exports. Dark holds to 0.105, where --accent
|
|
41
|
+
on --surface-3 reads 3.687 against its floor of 3.667 and --text-4 on
|
|
42
|
+
--surface-1 3.987 against 3.878 (plain 4.729). Light, where the ring fills with
|
|
43
|
+
--cursor-ink, holds to 0.480, where --on-accent on --accent reads 3.966
|
|
44
|
+
against 3.967; at 0.105 --text-4 on --surface-1 reads 5.380 against 4.772
|
|
45
|
+
(plain 5.555).
|
|
46
|
+
============================================================ */
|
|
47
|
+
|
|
48
|
+
#bg-dots { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
|
|
49
|
+
|
|
50
|
+
/* `--cursor-v3` is the module's proof that this sheet applies: no colour token a page may
|
|
51
|
+
reset, and declared by no v1 or v2 file */
|
|
52
|
+
:root {
|
|
53
|
+
--cursor-v3: 1;
|
|
54
|
+
--cursor-ink: var(--accent);
|
|
55
|
+
--cursor-lit: oklch(0.90 0.080 238);
|
|
56
|
+
--cursor-glow: oklch(0.90 0.080 238);
|
|
57
|
+
--cursor-glow-blend: screen;
|
|
58
|
+
--cursor-keyline: var(--bg);
|
|
59
|
+
--cursor-refuse: var(--danger);
|
|
60
|
+
--cursor-ripple: oklch(0.82 0.115 238);
|
|
61
|
+
--cursor-trail-core: oklch(0.82 0.115 238);
|
|
62
|
+
--cursor-trail-edge: var(--accent);
|
|
63
|
+
--cursor-spray: oklch(0.88 0.070 238);
|
|
64
|
+
--cursor-spray-hot: oklch(0.80 0.130 39);
|
|
65
|
+
--cursor-size-idle: 18px;
|
|
66
|
+
--cursor-size-hover: 24px;
|
|
67
|
+
--cursor-size-press: 19px;
|
|
68
|
+
--cursor-size-press-idle: 14px;
|
|
69
|
+
--cursor-size-blocked: 24px;
|
|
70
|
+
}
|
|
71
|
+
[data-theme="light"] {
|
|
72
|
+
--cursor-ink: oklch(0.50 0.130 238);
|
|
73
|
+
--cursor-lit: oklch(0.55 0.160 238);
|
|
74
|
+
--cursor-glow: oklch(0.89 0.070 238);
|
|
75
|
+
--cursor-glow-blend: darken;
|
|
76
|
+
--cursor-keyline: var(--bg);
|
|
77
|
+
--cursor-refuse: var(--danger);
|
|
78
|
+
--cursor-ripple: oklch(0.50 0.130 238);
|
|
79
|
+
--cursor-trail-core: oklch(0.42 0.120 238);
|
|
80
|
+
--cursor-trail-edge: var(--accent);
|
|
81
|
+
--cursor-spray: oklch(0.45 0.130 238);
|
|
82
|
+
--cursor-spray-hot: oklch(0.50 0.140 39);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* The host is its own view-transition group: the theme swap would otherwise
|
|
86
|
+
freeze the cursor inside the root's old snapshot while the live one fades in,
|
|
87
|
+
so for the crossfade there would be two cursors and a ghost. It never fades
|
|
88
|
+
as a whole: the mark fades on the canvas, so a droplet still flying when the
|
|
89
|
+
pointer leaves the window plays out. */
|
|
90
|
+
#cursor {
|
|
91
|
+
position: fixed;
|
|
92
|
+
inset: 0;
|
|
93
|
+
z-index: 10000;
|
|
94
|
+
pointer-events: none;
|
|
95
|
+
view-transition-name: cursor;
|
|
96
|
+
}
|
|
97
|
+
#cursor-paint { position: absolute; top: 0; left: 0; }
|
|
98
|
+
::view-transition-old(cursor) { display: none; }
|
|
99
|
+
::view-transition-new(cursor) { animation: none; }
|
|
100
|
+
|
|
101
|
+
/* The light-up's bloom, under the mark: a 68px box scaled to the speed, its
|
|
102
|
+
blend the one direction per theme that cannot darken what it covers, as the
|
|
103
|
+
legacy halo blends. cursor.js places and fades it every frame. */
|
|
104
|
+
#cursor-glow {
|
|
105
|
+
position: fixed;
|
|
106
|
+
top: 0;
|
|
107
|
+
left: 0;
|
|
108
|
+
z-index: 9989;
|
|
109
|
+
width: 68px;
|
|
110
|
+
height: 68px;
|
|
111
|
+
pointer-events: none;
|
|
112
|
+
opacity: 0;
|
|
113
|
+
mix-blend-mode: var(--cursor-glow-blend);
|
|
114
|
+
background: radial-gradient(circle closest-side, var(--cursor-glow), transparent);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* A cache can serve another version's cursor.js beside this file. A v1 or v2 module arms
|
|
118
|
+
`custom-cursor` for a mark this sheet does not style, so the native cursor hides only for
|
|
119
|
+
a v3 mark: under `cursor-v3`, which only this sheet's own module sets, or beside
|
|
120
|
+
`#cursor-paint`, which an older v3 module builds without setting the class. The `:has()`
|
|
121
|
+
guard is a rule of its own: an engine without `:has()` drops the whole selector list. Both
|
|
122
|
+
hide only inside the module's own gate query, where the mark can show, and the rule below
|
|
123
|
+
hides #cursor outside it: whatever a module does with `custom-cursor` when the gate changes,
|
|
124
|
+
the native cursor and the mark never hide together. */
|
|
125
|
+
@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
|
|
126
|
+
html.custom-cursor.cursor-v3, html.custom-cursor.cursor-v3 * { cursor: none !important; -webkit-user-drag: none; }
|
|
127
|
+
html.custom-cursor:has(#cursor-paint), html.custom-cursor:has(#cursor-paint) * { cursor: none !important; -webkit-user-drag: none; }
|
|
128
|
+
}
|
|
129
|
+
/* a v1 or v2 module's ring, comet and halo stay out of the page flow beside this sheet:
|
|
130
|
+
unstyled, the comet's viewport-sized canvas and the halo would push the page taller */
|
|
131
|
+
#cursor-ring, #cursor-comet, #cursor-halo { display: none !important; }
|
|
132
|
+
/* a native drag ghost carries its own OS cursor, class or no class */
|
|
133
|
+
a, img { -webkit-user-drag: none; }
|
|
134
|
+
@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) { #cursor, #cursor-glow { display: none; } }
|