cssprobe-cli 0.2.0 → 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.
@@ -114,16 +114,12 @@ async function handleInspect(command, cmdEntry, args, output) {
114
114
  }
115
115
  const maxNodes = parsed['max-nodes'] || 60;
116
116
  const upTo = parsed['up-to'] || 'html';
117
- const headed = !!parsed.headed || config.headed;
117
+ const wait = !!parsed.wait;
118
+ const headed = !!parsed.headed || config.headed || wait;
118
119
  const browser = parsed.browser || config.browser;
119
120
  const state = parsed.state || undefined;
120
121
  const brief = !!parsed.brief;
121
122
  const layout = !!parsed.layout;
122
- const wait = !!parsed.wait;
123
- // --wait implies --headed
124
- if (wait) {
125
- args.headed = true;
126
- }
127
123
  if (!url)
128
124
  output.error('URL is required. Usage: cssprobe-cli inspect <url> [selector]');
129
125
  // Auto-detect root selector if not provided
@@ -4,277 +4,19 @@
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.renderMarkdown = renderMarkdown;
6
6
  exports.renderJSON = renderJSON;
7
- // ─── Node label (matches analyzer.ts nodeLabel) ───
8
- function nodeLabel(node) {
9
- const cls = node.classes.length ? `.${node.classes.join('.')}` : '';
10
- const id = node.id ? `#${node.id}` : '';
11
- return `${node.tag}${id}${cls}`;
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
- // ─── Layout diagram (ASCII box layout) ───
140
- const BOX = { tl: '\u250C', tr: '\u2510', bl: '\u2514', br: '\u2518', h: '\u2500', v: '\u2502' };
141
- function renderLayout(snapshot, findings) {
142
- if (!snapshot.tree)
143
- return ['(empty tree)'];
144
- const vpW = snapshot.viewport.width;
145
- const termWidth = Math.min(Math.max(process.stdout?.columns || 80, 60), 120);
146
- const indentUnit = 2;
147
- const maxDepth = 8;
148
- const boxPad = 4; // '│ ' prefix per depth level
149
- const issueLocations = new Set();
150
- for (const f of findings) {
151
- if (f.location && (f.level === 'warning' || f.level === 'error'))
152
- issueLocations.add(f.location);
153
- }
154
- function boxCharW(nodeW, depth) {
155
- const usedIndent = depth * indentUnit;
156
- const available = termWidth - usedIndent - boxPad;
157
- const ratio = nodeW / vpW;
158
- return Math.max(Math.round(available * ratio), 14);
159
- }
160
- const lines = [];
161
- function renderEl(node, depth, parentW) {
162
- if (depth > maxDepth) {
163
- lines.push(' '.repeat(depth) + '... (depth limit)');
164
- return;
165
- }
166
- const m = node.metrics.rect;
167
- // Skip zero-size elements (SVG symbols, hidden elements)
168
- if (m.width < 1 && m.height < 1 && node.children.length > 0) {
169
- for (const child of node.children)
170
- renderEl(child, depth, parentW);
171
- return;
172
- }
173
- if (m.width < 1 && m.height < 1)
174
- return;
175
- const label = nodeLabel(node);
176
- const hasIssue = issueLocations.has(label);
177
- const dimStr = `${Math.round(m.width)}\u00D7${Math.round(m.height)}`;
178
- const charW = boxCharW(m.width, depth);
179
- const innerW = charW - 2;
180
- const indent = ' '.repeat(depth);
181
- const issueMark = hasIssue ? ' \u26A0' : '';
182
- if (innerW < 3) {
183
- // Too narrow for a proper box
184
- lines.push(`${indent}${BOX.v} ${label.slice(0, charW - 3)}... ${dimStr}`);
185
- for (const child of node.children)
186
- renderEl(child, depth + 1, m.width);
187
- return;
188
- }
189
- // Top border: ┌─ label ────────┐ WxH
190
- const labelPart = ` ${label} `;
191
- const dimPart = ` ${dimStr} `;
192
- const topFillLen = Math.max(0, innerW - labelPart.length);
193
- lines.push(`${indent}${BOX.tl}${BOX.h}${labelPart}${BOX.h.repeat(topFillLen)}${BOX.tr}${dimPart}${issueMark}`);
194
- // Content
195
- if (node.children.length === 0) {
196
- // Empty box
197
- lines.push(`${indent}${BOX.v}${' '.repeat(innerW)}${BOX.v}`);
198
- }
199
- else {
200
- for (const child of node.children)
201
- renderEl(child, depth + 1, m.width);
202
- }
203
- // Bottom border: └───────────────┘
204
- lines.push(`${indent}${BOX.bl}${BOX.h.repeat(innerW)}${BOX.br}`);
205
- }
206
- renderEl(snapshot.tree, 0, vpW);
207
- const scaleStr = `${Math.round(vpW / termWidth)}px/char`;
208
- const result = [];
209
- result.push(`## Layout (${vpW}\u00D7${snapshot.viewport.height} viewport, scale: ${scaleStr})`);
210
- result.push('```');
211
- result.push(...lines);
212
- result.push('```');
213
- return result;
214
- }
215
- // ─── Findings rendering ───
216
- const CONF_BADGE = {
217
- DEFINITE: 'DEFINITE',
218
- INDEFINITE: 'INDEFINITE',
219
- UNVERIFIABLE: 'UNVERIFIABLE',
220
- };
221
- function renderFindings(findings) {
222
- const lines = [];
223
- lines.push('## Findings');
224
- if (findings.length === 0) {
225
- lines.push('- 无异常发现');
226
- return lines;
227
- }
228
- for (const f of findings) {
229
- const badge = `[${CONF_BADGE[f.confidence]}]`;
230
- const loc = f.location ? ` @ ${f.location}` : '';
231
- lines.push(`- ${f.level === 'error' ? '\u26A0' : f.level === 'warning' ? '\u26A0' : '\u2714'} ${f.message} ${badge}${loc}`);
232
- for (const ev of f.evidence.slice(0, 3)) {
233
- if (ev.type === 'computed') {
234
- lines.push(` - computed ${ev.property}: ${ev.value}`);
235
- }
236
- else if (ev.type === 'declared') {
237
- lines.push(` - declared ${ev.property}: ${ev.value} (${ev.source || '?'})${ev.accessible ? '' : ' [inaccessible]'}`);
238
- }
239
- }
240
- }
241
- return lines;
242
- }
243
- function renderBriefFindings(findings) {
244
- const lines = [];
245
- lines.push('## Findings');
246
- const important = findings.filter(f => f.level === 'warning' || f.level === 'error');
247
- if (important.length === 0) {
248
- lines.push('- No warnings or errors');
249
- return lines;
250
- }
251
- for (const f of important) {
252
- const badge = `[${CONF_BADGE[f.confidence]}]`;
253
- const loc = f.location ? ` @ ${f.location}` : '';
254
- const icon = f.level === 'error' ? '\u26A0' : '\u26A0';
255
- lines.push(`- ${icon} ${f.message} ${badge}${loc}`);
256
- }
257
- return lines;
258
- }
259
- function confidenceSummary(findings) {
260
- const counts = findings.reduce((acc, f) => {
261
- acc[f.confidence] = (acc[f.confidence] || 0) + 1;
262
- return acc;
263
- }, {});
264
- const parts = [
265
- `DEFINITE ${counts.DEFINITE || 0}`,
266
- `INDEFINITE ${counts.INDEFINITE || 0}`,
267
- `UNVERIFIABLE ${counts.UNVERIFIABLE || 0}`,
268
- ];
269
- return parts.join(' | ');
270
- }
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");
271
13
  // ─── Markdown ───
272
14
  function renderMarkdown(snapshot, findings, brief = false, layout = false) {
273
15
  const lines = [];
274
16
  const vp = snapshot.viewport ? `${snapshot.viewport.width}\u00D7${snapshot.viewport.height}` : 'unknown';
275
17
  lines.push('# cssprobe-cli report');
276
18
  lines.push(`viewport: ${vp} | root: ${snapshot.rootSelector} | nodes: ${snapshot.nodeCount}`);
277
- lines.push(`confidence: ${confidenceSummary(findings)}`);
19
+ lines.push(`confidence: ${(0, utils_1.confidenceSummary)(findings)}`);
278
20
  if (snapshot.error) {
279
21
  lines.push('');
280
22
  lines.push(`\u26A0 ${snapshot.error}`);
@@ -286,28 +28,28 @@ function renderMarkdown(snapshot, findings, brief = false, layout = false) {
286
28
  lines.push('');
287
29
  lines.push(`## Ancestor chain (root\u2192${snapshot.upTo})`);
288
30
  lines.push('```');
289
- lines.push(...renderAncestors(snapshot.ancestors));
31
+ lines.push(...(0, ancestors_1.renderAncestors)(snapshot.ancestors));
290
32
  lines.push('```');
291
33
  if (layout) {
292
34
  lines.push('');
293
- lines.push(...renderLayout(snapshot, findings));
35
+ lines.push(...(0, layout_1.renderLayout)(snapshot, findings));
294
36
  }
295
37
  if (brief) {
296
38
  lines.push('');
297
39
  lines.push('## Tree Sketch (issues only)');
298
40
  lines.push('```');
299
- lines.push(...renderSketch(snapshot.tree, findings));
41
+ lines.push(...(0, sketch_1.renderSketch)(snapshot.tree, findings));
300
42
  lines.push('```');
301
43
  }
302
44
  else if (!layout) {
303
45
  lines.push('');
304
46
  lines.push(`## DOM tree (${snapshot.downDepth} levels deep)`);
305
47
  lines.push('```');
306
- lines.push(...renderNode(snapshot.tree, 0));
48
+ lines.push(...(0, dom_tree_1.renderNode)(snapshot.tree, 0));
307
49
  lines.push('```');
308
50
  }
309
51
  lines.push('');
310
- lines.push(...(brief ? renderBriefFindings(findings) : renderFindings(findings)));
52
+ lines.push(...(brief ? (0, findings_1.renderBriefFindings)(findings) : (0, findings_1.renderFindings)(findings)));
311
53
  if (snapshot.crossOriginBlocked && snapshot.crossOriginBlocked > 0) {
312
54
  lines.push('');
313
55
  lines.push('## Cross-origin limitation');
@@ -0,0 +1,2 @@
1
+ import type { AncestorNode } from '../types';
2
+ export declare function renderAncestors(chain: AncestorNode[]): string[];
@@ -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,2 @@
1
+ import type { TreeNode } from '../types';
2
+ export declare function renderNode(node: TreeNode, indent: number): string[];
@@ -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,3 @@
1
+ import type { Finding } from '../types';
2
+ export declare function renderFindings(findings: Finding[]): string[];
3
+ export declare function renderBriefFindings(findings: Finding[]): string[];
@@ -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,2 @@
1
+ import type { Snapshot, Finding } from '../types';
2
+ export declare function renderLayout(snapshot: Snapshot, findings: Finding[]): string[];
@@ -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,2 @@
1
+ import type { TreeNode, Finding } from '../types';
2
+ export declare function renderSketch(tree: TreeNode | null, findings: Finding[]): string[];
@@ -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;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONF_BADGE = exports.BOX = void 0;
4
+ exports.nodeLabel = nodeLabel;
5
+ exports.smartLabel = smartLabel;
6
+ exports.isMultiColumn = isMultiColumn;
7
+ exports.confidenceSummary = confidenceSummary;
8
+ function nodeLabel(node) {
9
+ const cls = node.classes.length ? `.${node.classes.join('.')}` : '';
10
+ const id = node.id ? `#${node.id}` : '';
11
+ return `${node.tag}${id}${cls}`;
12
+ }
13
+ function smartLabel(node, maxLen) {
14
+ const full = nodeLabel(node);
15
+ if (full.length <= maxLen)
16
+ return full;
17
+ const id = node.id ? `#${node.id}` : '';
18
+ const firstCls = node.classes.length > 0 ? `.${node.classes[0]}` : '';
19
+ const short = `${node.tag}${id}${firstCls}`;
20
+ if (short.length <= maxLen)
21
+ return short;
22
+ const tagId = `${node.tag}${id}`;
23
+ if (tagId.length <= maxLen)
24
+ return tagId;
25
+ if (node.tag.length <= maxLen)
26
+ return node.tag;
27
+ return node.tag.slice(0, Math.max(maxLen - 3, 1)) + '...';
28
+ }
29
+ function isMultiColumn(node) {
30
+ const display = node.props.display;
31
+ return (node.shape?.role === 'flex-row' ||
32
+ (display === 'flex' && node.props.flexDirection !== 'column') ||
33
+ display === 'grid' ||
34
+ display === 'inline-grid');
35
+ }
36
+ exports.BOX = { tl: '\u250C', tr: '\u2510', bl: '\u2514', br: '\u2518', h: '\u2500', v: '\u2502' };
37
+ exports.CONF_BADGE = {
38
+ DEFINITE: 'DEFINITE',
39
+ INDEFINITE: 'INDEFINITE',
40
+ UNVERIFIABLE: 'UNVERIFIABLE',
41
+ };
42
+ function confidenceSummary(findings) {
43
+ const counts = findings.reduce((acc, f) => {
44
+ acc[f.confidence] = (acc[f.confidence] || 0) + 1;
45
+ return acc;
46
+ }, {});
47
+ const parts = [
48
+ `DEFINITE ${counts.DEFINITE || 0}`,
49
+ `INDEFINITE ${counts.INDEFINITE || 0}`,
50
+ `UNVERIFIABLE ${counts.UNVERIFIABLE || 0}`,
51
+ ];
52
+ return parts.join(' | ');
53
+ }
54
+ //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cssprobe-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.5",
4
4
  "description": "Runtime CSS probe — inspect layout, colors, backgrounds, fonts and more in a live browser. AI agents and shell automation.",
5
5
  "keywords": [
6
6
  "css",