cssprobe-cli 0.1.2 → 0.2.5
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 +31 -1
- package/dist/browser/launcher.d.ts +4 -0
- package/dist/browser/launcher.js +27 -0
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.js +16 -1
- package/dist/cli/program.js +282 -2
- package/dist/engine/renderer.d.ts +1 -1
- package/dist/engine/renderer.js +17 -195
- package/dist/engine/renderers/ancestors.d.ts +2 -0
- package/dist/engine/renderers/ancestors.js +17 -0
- package/dist/engine/renderers/dom-tree.d.ts +2 -0
- package/dist/engine/renderers/dom-tree.js +43 -0
- package/dist/engine/renderers/findings.d.ts +3 -0
- package/dist/engine/renderers/findings.js +44 -0
- package/dist/engine/renderers/layout.d.ts +2 -0
- package/dist/engine/renderers/layout.js +203 -0
- package/dist/engine/renderers/sketch.d.ts +2 -0
- package/dist/engine/renderers/sketch.js +78 -0
- package/dist/engine/renderers/utils.d.ts +16 -0
- package/dist/engine/renderers/utils.js +54 -0
- package/dist/help.json +18 -3
- package/dist/installer/skill-template.js +36 -0
- package/docs/guide/installation.md +36 -1
- package/package.json +1 -1
package/dist/engine/renderer.js
CHANGED
|
@@ -4,201 +4,19 @@
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.renderMarkdown = renderMarkdown;
|
|
6
6
|
exports.renderJSON = renderJSON;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// ─── Node rendering (DOM tree) ───
|
|
14
|
-
function renderNode(node, indent) {
|
|
15
|
-
const lines = [];
|
|
16
|
-
if (!node)
|
|
17
|
-
return lines;
|
|
18
|
-
const cls = node.classes.length ? `.${node.classes.join('.')}` : '';
|
|
19
|
-
const id = node.id ? `#${node.id}` : '';
|
|
20
|
-
const repeat = node.repeat ? ` \u00D7${node.repeat}` : '';
|
|
21
|
-
const m = node.metrics;
|
|
22
|
-
const s = node.shape || {};
|
|
23
|
-
const flag = [];
|
|
24
|
-
if (node.flags.overflowsViewport)
|
|
25
|
-
flag.push('\u26A0viewport-overflow');
|
|
26
|
-
if (node.flags.overflowsParent)
|
|
27
|
-
flag.push('\u26A0parent-overflow');
|
|
28
|
-
if (m.clientHeight === 0 && m.offsetHeight === 0)
|
|
29
|
-
flag.push('\u26A0collapsed(0px)');
|
|
30
|
-
if (node.flags.scrollable)
|
|
31
|
-
flag.push('\u2714scrollable');
|
|
32
|
-
if (node.flags.hasScrollY && !node.flags.scrollable) {
|
|
33
|
-
flag.push(`\u26A0overflow(${m.scrollHeight}>${m.clientHeight})but-overflow=${node.props.overflowY}`);
|
|
34
|
-
}
|
|
35
|
-
if (node.containingBlockModifiers.length) {
|
|
36
|
-
flag.push(`CB:${node.containingBlockModifiers.join(' ')}`);
|
|
37
|
-
}
|
|
38
|
-
const styleHint = node.inlineStyle ? ` style="${node.inlineStyle}"` : '';
|
|
39
|
-
const h = node.props.height;
|
|
40
|
-
const heightStr = h === 'auto' ? `auto\u2192${m.offsetHeight}px` : `${h}`;
|
|
41
|
-
const w = node.props.width;
|
|
42
|
-
const widthStr = w === 'auto' ? `auto\u2192${m.clientWidth}px` : `${w}`;
|
|
43
|
-
const mhStr = node.declared && node.declared['max-height'] ? ` maxH:${node.declared['max-height'].map(x => x.value).join(';')}` : '';
|
|
44
|
-
const shapeStr = s.role ? `[${s.role}${s.scrollTag ? ' ' + s.scrollTag : ''} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
45
|
-
lines.push(`${' '.repeat(indent)}<${node.tag}${id}${cls}${styleHint}${repeat}> ${shapeStr} [${node.props.position},${node.props.display},h:${heightStr},w:${widthStr}${mhStr}] rect(${m.rect.width}\u00D7${m.rect.height}) bottom=${m.rectBottom} scroll=${m.clientHeight}/${m.scrollHeight}` +
|
|
46
|
-
(flag.length ? ` ${flag.join(' ')}` : '') +
|
|
47
|
-
(node.text ? ` \u300C${node.text.slice(0, 30)}\u300D` : ''));
|
|
48
|
-
for (const child of node.children) {
|
|
49
|
-
lines.push(...renderNode(child, indent + 1));
|
|
50
|
-
}
|
|
51
|
-
return lines;
|
|
52
|
-
}
|
|
53
|
-
function renderAncestors(chain) {
|
|
54
|
-
return chain.map(a => {
|
|
55
|
-
const dh = a.declared && a.declared.height ? a.declared.height.map(x => `${x.selector}\u2192${x.value}`).join('; ') : 'auto';
|
|
56
|
-
const dw = a.declared && a.declared.width ? a.declared.width.map(x => `${x.selector}\u2192${x.value}`).join('; ') : 'auto';
|
|
57
|
-
const mh = a.declared && a.declared['max-height'] ? a.declared['max-height'].map(x => `${x.selector}\u2192${x.value}`).join('; ') : null;
|
|
58
|
-
const maxPart = mh ? ` max-height:${mh}` : '';
|
|
59
|
-
const s = a.shape || {};
|
|
60
|
-
const shapeStr = s.role ? `[${s.role}${s.scrollTag ? ' ' + s.scrollTag : ''} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
61
|
-
return `${a.label} ${shapeStr} [${a.props.position},${a.props.display}] h:${dh}${maxPart} w:${dw} \u2192 ${a.metrics.clientWidth}\u00D7${a.metrics.offsetHeight}` +
|
|
62
|
-
(a.containingBlockModifiers.length ? ` CB:${a.containingBlockModifiers.join(' ')}` : '') +
|
|
63
|
-
(a.inlineStyle ? ` style="${a.inlineStyle}"` : '');
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
function buildSketchTree(node, issueLabels) {
|
|
67
|
-
if (!node)
|
|
68
|
-
return null;
|
|
69
|
-
const label = nodeLabel(node);
|
|
70
|
-
const s = node.shape || {};
|
|
71
|
-
const shapeStr = s.role ? `[${s.role} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
72
|
-
const flags = [];
|
|
73
|
-
if (node.flags.overflowsParent)
|
|
74
|
-
flags.push('\u26A0parent-overflow');
|
|
75
|
-
if (node.flags.overflowsViewport)
|
|
76
|
-
flags.push('\u26A0viewport-overflow');
|
|
77
|
-
if (node.metrics.clientHeight === 0 && node.metrics.offsetHeight === 0)
|
|
78
|
-
flags.push('\u26A0collapsed');
|
|
79
|
-
if (node.flags.scrollable)
|
|
80
|
-
flags.push('\u2714scrollable');
|
|
81
|
-
if (node.containingBlockModifiers.length)
|
|
82
|
-
flags.push(`CB:${node.containingBlockModifiers.join(' ')}`);
|
|
83
|
-
const hasIssue = issueLabels.has(label) || flags.length > 0;
|
|
84
|
-
const children = [];
|
|
85
|
-
let omitted = 0;
|
|
86
|
-
for (const child of node.children) {
|
|
87
|
-
const sketchChild = buildSketchTree(child, issueLabels);
|
|
88
|
-
if (!sketchChild)
|
|
89
|
-
continue;
|
|
90
|
-
if (sketchChild.hasIssue || hasDescendantIssue(sketchChild)) {
|
|
91
|
-
children.push(sketchChild);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
omitted++;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return { label, shape: shapeStr, flags, children, hasIssue, omitted };
|
|
98
|
-
}
|
|
99
|
-
function hasDescendantIssue(node) {
|
|
100
|
-
if (node.hasIssue)
|
|
101
|
-
return true;
|
|
102
|
-
return node.children.some(c => hasDescendantIssue(c));
|
|
103
|
-
}
|
|
104
|
-
function renderSketchNode(node, prefix, isLast) {
|
|
105
|
-
const lines = [];
|
|
106
|
-
const connector = isLast ? '\u2514\u2500 ' : '\u251C\u2500 ';
|
|
107
|
-
const flagStr = node.flags.length ? ` ${node.flags.join(' ')}` : '';
|
|
108
|
-
const issueMark = node.hasIssue && node.flags.length === 0 ? ' \u26A0' : '';
|
|
109
|
-
lines.push(`${prefix}${connector}${node.label} ${node.shape}${flagStr}${issueMark}`);
|
|
110
|
-
const childPrefix = prefix + (isLast ? ' ' : '\u2502 ');
|
|
111
|
-
const totalChildren = node.children.length + (node.omitted > 0 ? 1 : 0);
|
|
112
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
113
|
-
lines.push(...renderSketchNode(node.children[i], childPrefix, i === totalChildren - 1));
|
|
114
|
-
}
|
|
115
|
-
if (node.omitted > 0) {
|
|
116
|
-
const omConnector = '\u2514\u2500 ';
|
|
117
|
-
lines.push(`${childPrefix}${omConnector}... (${node.omitted} subtrees omitted)`);
|
|
118
|
-
}
|
|
119
|
-
return lines;
|
|
120
|
-
}
|
|
121
|
-
function renderSketch(tree, findings) {
|
|
122
|
-
if (!tree)
|
|
123
|
-
return ['(empty tree)'];
|
|
124
|
-
const issueLabels = new Set();
|
|
125
|
-
for (const f of findings) {
|
|
126
|
-
if (f.level === 'warning' || f.level === 'error') {
|
|
127
|
-
if (f.location)
|
|
128
|
-
issueLabels.add(f.location);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const sketchRoot = buildSketchTree(tree, issueLabels);
|
|
132
|
-
if (!sketchRoot)
|
|
133
|
-
return ['(empty tree)'];
|
|
134
|
-
if (!hasDescendantIssue(sketchRoot) && !sketchRoot.hasIssue) {
|
|
135
|
-
return [`${sketchRoot.label} ${sketchRoot.shape} (no issues found in tree)`];
|
|
136
|
-
}
|
|
137
|
-
return renderSketchNode(sketchRoot, '', true);
|
|
138
|
-
}
|
|
139
|
-
// ─── Findings rendering ───
|
|
140
|
-
const CONF_BADGE = {
|
|
141
|
-
DEFINITE: 'DEFINITE',
|
|
142
|
-
INDEFINITE: 'INDEFINITE',
|
|
143
|
-
UNVERIFIABLE: 'UNVERIFIABLE',
|
|
144
|
-
};
|
|
145
|
-
function renderFindings(findings) {
|
|
146
|
-
const lines = [];
|
|
147
|
-
lines.push('## Findings');
|
|
148
|
-
if (findings.length === 0) {
|
|
149
|
-
lines.push('- 无异常发现');
|
|
150
|
-
return lines;
|
|
151
|
-
}
|
|
152
|
-
for (const f of findings) {
|
|
153
|
-
const badge = `[${CONF_BADGE[f.confidence]}]`;
|
|
154
|
-
const loc = f.location ? ` @ ${f.location}` : '';
|
|
155
|
-
lines.push(`- ${f.level === 'error' ? '\u26A0' : f.level === 'warning' ? '\u26A0' : '\u2714'} ${f.message} ${badge}${loc}`);
|
|
156
|
-
for (const ev of f.evidence.slice(0, 3)) {
|
|
157
|
-
if (ev.type === 'computed') {
|
|
158
|
-
lines.push(` - computed ${ev.property}: ${ev.value}`);
|
|
159
|
-
}
|
|
160
|
-
else if (ev.type === 'declared') {
|
|
161
|
-
lines.push(` - declared ${ev.property}: ${ev.value} (${ev.source || '?'})${ev.accessible ? '' : ' [inaccessible]'}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return lines;
|
|
166
|
-
}
|
|
167
|
-
function renderBriefFindings(findings) {
|
|
168
|
-
const lines = [];
|
|
169
|
-
lines.push('## Findings');
|
|
170
|
-
const important = findings.filter(f => f.level === 'warning' || f.level === 'error');
|
|
171
|
-
if (important.length === 0) {
|
|
172
|
-
lines.push('- No warnings or errors');
|
|
173
|
-
return lines;
|
|
174
|
-
}
|
|
175
|
-
for (const f of important) {
|
|
176
|
-
const badge = `[${CONF_BADGE[f.confidence]}]`;
|
|
177
|
-
const loc = f.location ? ` @ ${f.location}` : '';
|
|
178
|
-
const icon = f.level === 'error' ? '\u26A0' : '\u26A0';
|
|
179
|
-
lines.push(`- ${icon} ${f.message} ${badge}${loc}`);
|
|
180
|
-
}
|
|
181
|
-
return lines;
|
|
182
|
-
}
|
|
183
|
-
function confidenceSummary(findings) {
|
|
184
|
-
const counts = findings.reduce((acc, f) => {
|
|
185
|
-
acc[f.confidence] = (acc[f.confidence] || 0) + 1;
|
|
186
|
-
return acc;
|
|
187
|
-
}, {});
|
|
188
|
-
const parts = [
|
|
189
|
-
`DEFINITE ${counts.DEFINITE || 0}`,
|
|
190
|
-
`INDEFINITE ${counts.INDEFINITE || 0}`,
|
|
191
|
-
`UNVERIFIABLE ${counts.UNVERIFIABLE || 0}`,
|
|
192
|
-
];
|
|
193
|
-
return parts.join(' | ');
|
|
194
|
-
}
|
|
7
|
+
const utils_1 = require("./renderers/utils");
|
|
8
|
+
const dom_tree_1 = require("./renderers/dom-tree");
|
|
9
|
+
const ancestors_1 = require("./renderers/ancestors");
|
|
10
|
+
const sketch_1 = require("./renderers/sketch");
|
|
11
|
+
const layout_1 = require("./renderers/layout");
|
|
12
|
+
const findings_1 = require("./renderers/findings");
|
|
195
13
|
// ─── Markdown ───
|
|
196
|
-
function renderMarkdown(snapshot, findings, brief = false) {
|
|
14
|
+
function renderMarkdown(snapshot, findings, brief = false, layout = false) {
|
|
197
15
|
const lines = [];
|
|
198
16
|
const vp = snapshot.viewport ? `${snapshot.viewport.width}\u00D7${snapshot.viewport.height}` : 'unknown';
|
|
199
17
|
lines.push('# cssprobe-cli report');
|
|
200
18
|
lines.push(`viewport: ${vp} | root: ${snapshot.rootSelector} | nodes: ${snapshot.nodeCount}`);
|
|
201
|
-
lines.push(`confidence: ${confidenceSummary(findings)}`);
|
|
19
|
+
lines.push(`confidence: ${(0, utils_1.confidenceSummary)(findings)}`);
|
|
202
20
|
if (snapshot.error) {
|
|
203
21
|
lines.push('');
|
|
204
22
|
lines.push(`\u26A0 ${snapshot.error}`);
|
|
@@ -210,24 +28,28 @@ function renderMarkdown(snapshot, findings, brief = false) {
|
|
|
210
28
|
lines.push('');
|
|
211
29
|
lines.push(`## Ancestor chain (root\u2192${snapshot.upTo})`);
|
|
212
30
|
lines.push('```');
|
|
213
|
-
lines.push(...renderAncestors(snapshot.ancestors));
|
|
31
|
+
lines.push(...(0, ancestors_1.renderAncestors)(snapshot.ancestors));
|
|
214
32
|
lines.push('```');
|
|
33
|
+
if (layout) {
|
|
34
|
+
lines.push('');
|
|
35
|
+
lines.push(...(0, layout_1.renderLayout)(snapshot, findings));
|
|
36
|
+
}
|
|
215
37
|
if (brief) {
|
|
216
38
|
lines.push('');
|
|
217
39
|
lines.push('## Tree Sketch (issues only)');
|
|
218
40
|
lines.push('```');
|
|
219
|
-
lines.push(...renderSketch(snapshot.tree, findings));
|
|
41
|
+
lines.push(...(0, sketch_1.renderSketch)(snapshot.tree, findings));
|
|
220
42
|
lines.push('```');
|
|
221
43
|
}
|
|
222
|
-
else {
|
|
44
|
+
else if (!layout) {
|
|
223
45
|
lines.push('');
|
|
224
46
|
lines.push(`## DOM tree (${snapshot.downDepth} levels deep)`);
|
|
225
47
|
lines.push('```');
|
|
226
|
-
lines.push(...renderNode(snapshot.tree, 0));
|
|
48
|
+
lines.push(...(0, dom_tree_1.renderNode)(snapshot.tree, 0));
|
|
227
49
|
lines.push('```');
|
|
228
50
|
}
|
|
229
51
|
lines.push('');
|
|
230
|
-
lines.push(...(brief ? renderBriefFindings(findings) : renderFindings(findings)));
|
|
52
|
+
lines.push(...(brief ? (0, findings_1.renderBriefFindings)(findings) : (0, findings_1.renderFindings)(findings)));
|
|
231
53
|
if (snapshot.crossOriginBlocked && snapshot.crossOriginBlocked > 0) {
|
|
232
54
|
lines.push('');
|
|
233
55
|
lines.push('## Cross-origin limitation');
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderAncestors = renderAncestors;
|
|
4
|
+
function renderAncestors(chain) {
|
|
5
|
+
return chain.map(a => {
|
|
6
|
+
const dh = a.declared && a.declared.height ? a.declared.height.map(x => `${x.selector}\u2192${x.value}`).join('; ') : 'auto';
|
|
7
|
+
const dw = a.declared && a.declared.width ? a.declared.width.map(x => `${x.selector}\u2192${x.value}`).join('; ') : 'auto';
|
|
8
|
+
const mh = a.declared && a.declared['max-height'] ? a.declared['max-height'].map(x => `${x.selector}\u2192${x.value}`).join('; ') : null;
|
|
9
|
+
const maxPart = mh ? ` max-height:${mh}` : '';
|
|
10
|
+
const s = a.shape || {};
|
|
11
|
+
const shapeStr = s.role ? `[${s.role}${s.scrollTag ? ' ' + s.scrollTag : ''} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
12
|
+
return `${a.label} ${shapeStr} [${a.props.position},${a.props.display}] h:${dh}${maxPart} w:${dw} \u2192 ${a.metrics.clientWidth}\u00D7${a.metrics.offsetHeight}` +
|
|
13
|
+
(a.containingBlockModifiers.length ? ` CB:${a.containingBlockModifiers.join(' ')}` : '') +
|
|
14
|
+
(a.inlineStyle ? ` style="${a.inlineStyle}"` : '');
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=ancestors.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderNode = renderNode;
|
|
4
|
+
function renderNode(node, indent) {
|
|
5
|
+
const lines = [];
|
|
6
|
+
if (!node)
|
|
7
|
+
return lines;
|
|
8
|
+
const cls = node.classes.length ? `.${node.classes.join('.')}` : '';
|
|
9
|
+
const id = node.id ? `#${node.id}` : '';
|
|
10
|
+
const repeat = node.repeat ? ` \u00D7${node.repeat}` : '';
|
|
11
|
+
const m = node.metrics;
|
|
12
|
+
const s = node.shape || {};
|
|
13
|
+
const flag = [];
|
|
14
|
+
if (node.flags.overflowsViewport)
|
|
15
|
+
flag.push('\u26A0viewport-overflow');
|
|
16
|
+
if (node.flags.overflowsParent)
|
|
17
|
+
flag.push('\u26A0parent-overflow');
|
|
18
|
+
if (m.clientHeight === 0 && m.offsetHeight === 0)
|
|
19
|
+
flag.push('\u26A0collapsed(0px)');
|
|
20
|
+
if (node.flags.scrollable)
|
|
21
|
+
flag.push('\u2714scrollable');
|
|
22
|
+
if (node.flags.hasScrollY && !node.flags.scrollable) {
|
|
23
|
+
flag.push(`\u26A0overflow(${m.scrollHeight}>${m.clientHeight})but-overflow=${node.props.overflowY}`);
|
|
24
|
+
}
|
|
25
|
+
if (node.containingBlockModifiers.length) {
|
|
26
|
+
flag.push(`CB:${node.containingBlockModifiers.join(' ')}`);
|
|
27
|
+
}
|
|
28
|
+
const styleHint = node.inlineStyle ? ` style="${node.inlineStyle}"` : '';
|
|
29
|
+
const h = node.props.height;
|
|
30
|
+
const heightStr = h === 'auto' ? `auto\u2192${m.offsetHeight}px` : `${h}`;
|
|
31
|
+
const w = node.props.width;
|
|
32
|
+
const widthStr = w === 'auto' ? `auto\u2192${m.clientWidth}px` : `${w}`;
|
|
33
|
+
const mhStr = node.declared && node.declared['max-height'] ? ` maxH:${node.declared['max-height'].map(x => x.value).join(';')}` : '';
|
|
34
|
+
const shapeStr = s.role ? `[${s.role}${s.scrollTag ? ' ' + s.scrollTag : ''} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
35
|
+
lines.push(`${' '.repeat(indent)}<${node.tag}${id}${cls}${styleHint}${repeat}> ${shapeStr} [${node.props.position},${node.props.display},h:${heightStr},w:${widthStr}${mhStr}] rect(${m.rect.width}\u00D7${m.rect.height}) bottom=${m.rectBottom} scroll=${m.clientHeight}/${m.scrollHeight}` +
|
|
36
|
+
(flag.length ? ` ${flag.join(' ')}` : '') +
|
|
37
|
+
(node.text ? ` \u300C${node.text.slice(0, 30)}\u300D` : ''));
|
|
38
|
+
for (const child of node.children) {
|
|
39
|
+
lines.push(...renderNode(child, indent + 1));
|
|
40
|
+
}
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=dom-tree.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderFindings = renderFindings;
|
|
4
|
+
exports.renderBriefFindings = renderBriefFindings;
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
function renderFindings(findings) {
|
|
7
|
+
const lines = [];
|
|
8
|
+
lines.push('## Findings');
|
|
9
|
+
if (findings.length === 0) {
|
|
10
|
+
lines.push('- 无异常发现');
|
|
11
|
+
return lines;
|
|
12
|
+
}
|
|
13
|
+
for (const f of findings) {
|
|
14
|
+
const badge = `[${utils_1.CONF_BADGE[f.confidence]}]`;
|
|
15
|
+
const loc = f.location ? ` @ ${f.location}` : '';
|
|
16
|
+
lines.push(`- ${f.level === 'error' ? '\u26A0' : f.level === 'warning' ? '\u26A0' : '\u2714'} ${f.message} ${badge}${loc}`);
|
|
17
|
+
for (const ev of f.evidence.slice(0, 3)) {
|
|
18
|
+
if (ev.type === 'computed') {
|
|
19
|
+
lines.push(` - computed ${ev.property}: ${ev.value}`);
|
|
20
|
+
}
|
|
21
|
+
else if (ev.type === 'declared') {
|
|
22
|
+
lines.push(` - declared ${ev.property}: ${ev.value} (${ev.source || '?'})${ev.accessible ? '' : ' [inaccessible]'}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return lines;
|
|
27
|
+
}
|
|
28
|
+
function renderBriefFindings(findings) {
|
|
29
|
+
const lines = [];
|
|
30
|
+
lines.push('## Findings');
|
|
31
|
+
const important = findings.filter(f => f.level === 'warning' || f.level === 'error');
|
|
32
|
+
if (important.length === 0) {
|
|
33
|
+
lines.push('- No warnings or errors');
|
|
34
|
+
return lines;
|
|
35
|
+
}
|
|
36
|
+
for (const f of important) {
|
|
37
|
+
const badge = `[${utils_1.CONF_BADGE[f.confidence]}]`;
|
|
38
|
+
const loc = f.location ? ` @ ${f.location}` : '';
|
|
39
|
+
const icon = f.level === 'error' ? '\u26A0' : '\u26A0';
|
|
40
|
+
lines.push(`- ${icon} ${f.message} ${badge}${loc}`);
|
|
41
|
+
}
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=findings.js.map
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderLayout = renderLayout;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function renderLayout(snapshot, findings) {
|
|
6
|
+
if (!snapshot.tree)
|
|
7
|
+
return ['(empty tree)'];
|
|
8
|
+
const vpW = snapshot.viewport.width;
|
|
9
|
+
const termWidth = Math.min(Math.max(process.stdout?.columns || 80, 60), 120);
|
|
10
|
+
const indentUnit = 2;
|
|
11
|
+
const maxDepth = 8;
|
|
12
|
+
const boxPad = 4;
|
|
13
|
+
const issueLocations = new Set();
|
|
14
|
+
for (const f of findings) {
|
|
15
|
+
if (f.location && (f.level === 'warning' || f.level === 'error'))
|
|
16
|
+
issueLocations.add(f.location);
|
|
17
|
+
}
|
|
18
|
+
function boxCharW(nodeW, depth) {
|
|
19
|
+
const usedIndent = depth * indentUnit;
|
|
20
|
+
const available = termWidth - usedIndent - boxPad;
|
|
21
|
+
const ratio = nodeW / vpW;
|
|
22
|
+
return Math.max(Math.round(available * ratio), 14);
|
|
23
|
+
}
|
|
24
|
+
const lines = [];
|
|
25
|
+
function renderBox(node, charW, depth, flat = false) {
|
|
26
|
+
const m = node.metrics.rect;
|
|
27
|
+
const dimStr = `${Math.round(m.width)}\u00D7${Math.round(m.height)}`;
|
|
28
|
+
const innerW = Math.max(charW - 2, 1);
|
|
29
|
+
const label = (0, utils_1.smartLabel)(node, innerW - 4);
|
|
30
|
+
const hasIssue = issueLocations.has(label);
|
|
31
|
+
const issueMark = hasIssue ? ' \u26A0' : '';
|
|
32
|
+
const repeat = node.repeat && node.repeat > 1 ? node.repeat : 1;
|
|
33
|
+
const boxLines = [];
|
|
34
|
+
if (innerW < 3) {
|
|
35
|
+
boxLines.push(`${utils_1.BOX.v} ${label.slice(0, Math.max(charW - 3, 0))}... ${dimStr}`);
|
|
36
|
+
return boxLines;
|
|
37
|
+
}
|
|
38
|
+
const labelPart = ` ${label}${repeat > 1 ? ` \u00D7${repeat}` : ''} `;
|
|
39
|
+
const dimPart = ` ${dimStr} `;
|
|
40
|
+
const topFillLen = Math.max(0, innerW - labelPart.length - dimPart.length);
|
|
41
|
+
boxLines.push(`${utils_1.BOX.tl}${utils_1.BOX.h}${labelPart}${utils_1.BOX.h.repeat(topFillLen)}${utils_1.BOX.tr}${dimPart}${issueMark}`);
|
|
42
|
+
// Children
|
|
43
|
+
const children = node.children.filter(c => !(c.metrics.rect.width < 1 && c.metrics.rect.height < 1));
|
|
44
|
+
if (children.length === 0) {
|
|
45
|
+
boxLines.push(`${utils_1.BOX.v}${' '.repeat(innerW)}${utils_1.BOX.v}`);
|
|
46
|
+
}
|
|
47
|
+
else if (flat) {
|
|
48
|
+
for (const child of children) {
|
|
49
|
+
const childLabel = (0, utils_1.nodeLabel)(child);
|
|
50
|
+
const childRepeat = child.repeat && child.repeat > 1 ? ` \u00D7${child.repeat}` : '';
|
|
51
|
+
const childDim = `${Math.round(child.metrics.rect.width)}\u00D7${Math.round(child.metrics.rect.height)}`;
|
|
52
|
+
const childLine = ` ${childLabel}${childRepeat} ${childDim} `;
|
|
53
|
+
if (childLine.length <= innerW) {
|
|
54
|
+
boxLines.push(`${utils_1.BOX.v}${childLine}${' '.repeat(innerW - childLine.length)}${utils_1.BOX.v}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
boxLines.push(`${utils_1.BOX.v}${childLine.slice(0, innerW)}${utils_1.BOX.v}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if ((0, utils_1.isMultiColumn)(node)) {
|
|
62
|
+
// Calculate expanded count (considering repeat)
|
|
63
|
+
const expandedCount = children.reduce((sum, c) => sum + (c.repeat && c.repeat > 1 ? c.repeat : 1), 0);
|
|
64
|
+
if (expandedCount > 1) {
|
|
65
|
+
const isGrid = node.props.display === 'grid' || node.props.display === 'inline-grid';
|
|
66
|
+
const childLines = renderFlexChildren(children, innerW, depth + 1, m.width, isGrid);
|
|
67
|
+
for (const cl of childLines) {
|
|
68
|
+
boxLines.push(`${utils_1.BOX.v}${cl}${utils_1.BOX.v}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
for (const child of children) {
|
|
73
|
+
const childBox = renderBox(child, Math.min(boxCharW(child.metrics.rect.width, depth + 1), innerW - 2), depth + 1);
|
|
74
|
+
for (const cl of childBox) {
|
|
75
|
+
const line = ` ${cl}`;
|
|
76
|
+
if (line.length <= innerW + 1) {
|
|
77
|
+
boxLines.push(`${utils_1.BOX.v}${line}${' '.repeat(Math.max(0, innerW - line.length + 1))}${utils_1.BOX.v}`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
boxLines.push(`${utils_1.BOX.v}${line.slice(0, innerW)}${utils_1.BOX.v}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
for (const child of children) {
|
|
88
|
+
const childBox = renderBox(child, Math.min(boxCharW(child.metrics.rect.width, depth + 1), innerW - 2), depth + 1);
|
|
89
|
+
for (const cl of childBox) {
|
|
90
|
+
const line = ` ${cl}`;
|
|
91
|
+
if (line.length <= innerW + 1) {
|
|
92
|
+
boxLines.push(`${utils_1.BOX.v}${line}${' '.repeat(Math.max(0, innerW - line.length + 1))}${utils_1.BOX.v}`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
boxLines.push(`${utils_1.BOX.v}${line.slice(0, innerW)}${utils_1.BOX.v}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
boxLines.push(`${utils_1.BOX.bl}${utils_1.BOX.h.repeat(innerW)}${utils_1.BOX.br}`);
|
|
101
|
+
return boxLines;
|
|
102
|
+
}
|
|
103
|
+
function renderFlexChildren(children, availW, depth, parentPxW, isGrid) {
|
|
104
|
+
// Expand repeat nodes into multiple copies
|
|
105
|
+
const expanded = [];
|
|
106
|
+
for (const child of children) {
|
|
107
|
+
const repeat = child.repeat && child.repeat > 1 ? child.repeat : 1;
|
|
108
|
+
for (let r = 0; r < repeat; r++) {
|
|
109
|
+
expanded.push({ ...child, repeat: 1, children: child.children });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// For grid: group into rows based on column count
|
|
113
|
+
let rows;
|
|
114
|
+
if (isGrid && expanded.length > 1) {
|
|
115
|
+
const childW = expanded[0].metrics.rect.width;
|
|
116
|
+
const cols = Math.max(1, Math.floor(parentPxW / childW));
|
|
117
|
+
rows = [];
|
|
118
|
+
for (let i = 0; i < expanded.length; i += cols) {
|
|
119
|
+
rows.push(expanded.slice(i, i + cols));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
rows = [expanded];
|
|
124
|
+
}
|
|
125
|
+
const allResult = [];
|
|
126
|
+
for (const row of rows) {
|
|
127
|
+
const gap = 1;
|
|
128
|
+
const totalGaps = (row.length - 1) * gap;
|
|
129
|
+
const minPerChild = 10;
|
|
130
|
+
const usableW = Math.max(availW - totalGaps, row.length * minPerChild);
|
|
131
|
+
const childWidths = [];
|
|
132
|
+
let totalUsed = 0;
|
|
133
|
+
for (let i = 0; i < row.length; i++) {
|
|
134
|
+
const ratio = row[i].metrics.rect.width / parentPxW;
|
|
135
|
+
const w = Math.max(Math.round(usableW * ratio), minPerChild);
|
|
136
|
+
childWidths.push(w);
|
|
137
|
+
totalUsed += w;
|
|
138
|
+
}
|
|
139
|
+
if (childWidths.length > 0 && usableW > totalUsed) {
|
|
140
|
+
childWidths[childWidths.length - 1] += usableW - totalUsed;
|
|
141
|
+
}
|
|
142
|
+
const childBoxes = [];
|
|
143
|
+
let maxLines = 0;
|
|
144
|
+
for (let i = 0; i < row.length; i++) {
|
|
145
|
+
const box = renderBox(row[i], childWidths[i], depth, true);
|
|
146
|
+
childBoxes.push(box);
|
|
147
|
+
if (box.length > maxLines)
|
|
148
|
+
maxLines = box.length;
|
|
149
|
+
}
|
|
150
|
+
for (let lineIdx = 0; lineIdx < maxLines; lineIdx++) {
|
|
151
|
+
let merged = '';
|
|
152
|
+
for (let i = 0; i < childBoxes.length; i++) {
|
|
153
|
+
const w = childWidths[i];
|
|
154
|
+
if (i > 0)
|
|
155
|
+
merged += ' ';
|
|
156
|
+
if (lineIdx < childBoxes[i].length) {
|
|
157
|
+
const line = childBoxes[i][lineIdx];
|
|
158
|
+
if (line.length < w) {
|
|
159
|
+
merged += line + ' '.repeat(w - line.length);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
merged += line.slice(0, w);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
merged += ' '.repeat(w);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
allResult.push(merged);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return allResult;
|
|
173
|
+
}
|
|
174
|
+
function renderEl(node, depth, parentW) {
|
|
175
|
+
if (depth > maxDepth) {
|
|
176
|
+
lines.push(' '.repeat(depth) + '... (depth limit)');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const m = node.metrics.rect;
|
|
180
|
+
if (m.width < 1 && m.height < 1 && node.children.length > 0) {
|
|
181
|
+
for (const child of node.children)
|
|
182
|
+
renderEl(child, depth, parentW);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (m.width < 1 && m.height < 1)
|
|
186
|
+
return;
|
|
187
|
+
const charW = boxCharW(m.width, depth);
|
|
188
|
+
const indent = ' '.repeat(depth);
|
|
189
|
+
const box = renderBox(node, charW, depth);
|
|
190
|
+
for (const line of box) {
|
|
191
|
+
lines.push(`${indent}${line}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
renderEl(snapshot.tree, 0, vpW);
|
|
195
|
+
const scaleStr = `${Math.round(vpW / termWidth)}px/char`;
|
|
196
|
+
const result = [];
|
|
197
|
+
result.push(`## Layout (${vpW}\u00D7${snapshot.viewport.height} viewport, scale: ${scaleStr})`);
|
|
198
|
+
result.push('```');
|
|
199
|
+
result.push(...lines);
|
|
200
|
+
result.push('```');
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderSketch = renderSketch;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function buildSketchTree(node, issueLabels) {
|
|
6
|
+
if (!node)
|
|
7
|
+
return null;
|
|
8
|
+
const label = (0, utils_1.nodeLabel)(node);
|
|
9
|
+
const s = node.shape || {};
|
|
10
|
+
const shapeStr = s.role ? `[${s.role} h:${s.heightStrategy} w:${s.widthStrategy}]` : '';
|
|
11
|
+
const flags = [];
|
|
12
|
+
if (node.flags.overflowsParent)
|
|
13
|
+
flags.push('\u26A0parent-overflow');
|
|
14
|
+
if (node.flags.overflowsViewport)
|
|
15
|
+
flags.push('\u26A0viewport-overflow');
|
|
16
|
+
if (node.metrics.clientHeight === 0 && node.metrics.offsetHeight === 0)
|
|
17
|
+
flags.push('\u26A0collapsed');
|
|
18
|
+
if (node.flags.scrollable)
|
|
19
|
+
flags.push('\u2714scrollable');
|
|
20
|
+
if (node.containingBlockModifiers.length)
|
|
21
|
+
flags.push(`CB:${node.containingBlockModifiers.join(' ')}`);
|
|
22
|
+
const hasIssue = issueLabels.has(label) || flags.length > 0;
|
|
23
|
+
const children = [];
|
|
24
|
+
let omitted = 0;
|
|
25
|
+
for (const child of node.children) {
|
|
26
|
+
const sketchChild = buildSketchTree(child, issueLabels);
|
|
27
|
+
if (!sketchChild)
|
|
28
|
+
continue;
|
|
29
|
+
if (sketchChild.hasIssue || hasDescendantIssue(sketchChild)) {
|
|
30
|
+
children.push(sketchChild);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
omitted++;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { label, shape: shapeStr, flags, children, hasIssue, omitted };
|
|
37
|
+
}
|
|
38
|
+
function hasDescendantIssue(node) {
|
|
39
|
+
if (node.hasIssue)
|
|
40
|
+
return true;
|
|
41
|
+
return node.children.some(c => hasDescendantIssue(c));
|
|
42
|
+
}
|
|
43
|
+
function renderSketchNode(node, prefix, isLast) {
|
|
44
|
+
const lines = [];
|
|
45
|
+
const connector = isLast ? '\u2514\u2500 ' : '\u251C\u2500 ';
|
|
46
|
+
const flagStr = node.flags.length ? ` ${node.flags.join(' ')}` : '';
|
|
47
|
+
const issueMark = node.hasIssue && node.flags.length === 0 ? ' \u26A0' : '';
|
|
48
|
+
lines.push(`${prefix}${connector}${node.label} ${node.shape}${flagStr}${issueMark}`);
|
|
49
|
+
const childPrefix = prefix + (isLast ? ' ' : '\u2502 ');
|
|
50
|
+
const totalChildren = node.children.length + (node.omitted > 0 ? 1 : 0);
|
|
51
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
52
|
+
lines.push(...renderSketchNode(node.children[i], childPrefix, i === totalChildren - 1));
|
|
53
|
+
}
|
|
54
|
+
if (node.omitted > 0) {
|
|
55
|
+
const omConnector = '\u2514\u2500 ';
|
|
56
|
+
lines.push(`${childPrefix}${omConnector}... (${node.omitted} subtrees omitted)`);
|
|
57
|
+
}
|
|
58
|
+
return lines;
|
|
59
|
+
}
|
|
60
|
+
function renderSketch(tree, findings) {
|
|
61
|
+
if (!tree)
|
|
62
|
+
return ['(empty tree)'];
|
|
63
|
+
const issueLabels = new Set();
|
|
64
|
+
for (const f of findings) {
|
|
65
|
+
if (f.level === 'warning' || f.level === 'error') {
|
|
66
|
+
if (f.location)
|
|
67
|
+
issueLabels.add(f.location);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const sketchRoot = buildSketchTree(tree, issueLabels);
|
|
71
|
+
if (!sketchRoot)
|
|
72
|
+
return ['(empty tree)'];
|
|
73
|
+
if (!hasDescendantIssue(sketchRoot) && !sketchRoot.hasIssue) {
|
|
74
|
+
return [`${sketchRoot.label} ${sketchRoot.shape} (no issues found in tree)`];
|
|
75
|
+
}
|
|
76
|
+
return renderSketchNode(sketchRoot, '', true);
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=sketch.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TreeNode, Confidence } from '../types';
|
|
2
|
+
export declare function nodeLabel(node: TreeNode): string;
|
|
3
|
+
export declare function smartLabel(node: TreeNode, maxLen: number): string;
|
|
4
|
+
export declare function isMultiColumn(node: TreeNode): boolean;
|
|
5
|
+
export declare const BOX: {
|
|
6
|
+
readonly tl: "┌";
|
|
7
|
+
readonly tr: "┐";
|
|
8
|
+
readonly bl: "└";
|
|
9
|
+
readonly br: "┘";
|
|
10
|
+
readonly h: "─";
|
|
11
|
+
readonly v: "│";
|
|
12
|
+
};
|
|
13
|
+
export declare const CONF_BADGE: Record<Confidence, string>;
|
|
14
|
+
export declare function confidenceSummary(findings: {
|
|
15
|
+
confidence: Confidence;
|
|
16
|
+
}[]): string;
|