eduevidence 5.2.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 +21 -0
- package/README.md +506 -0
- package/README.zh-CN.md +434 -0
- package/SKILL.md +195 -0
- package/bin/eduevidence.js +127 -0
- package/domains/education/manifest.json +183 -0
- package/domains/education/outcome_taxonomy.json +127 -0
- package/domains/manifest.json +26 -0
- package/domains/policy/frame.schema.json +234 -0
- package/domains/policy/manifest.json +10 -0
- package/domains/policy/methodology_checklist.json +109 -0
- package/domains/policy/outcome_taxonomy.json +53 -0
- package/domains/policy/references/causal-identification.md +45 -0
- package/domains/policy/references/cost-evidence.md +44 -0
- package/domains/policy/references/equity.md +42 -0
- package/domains/policy/references/evidence-hierarchy.md +41 -0
- package/domains/policy/references/implementation-evidence.md +47 -0
- package/eduevidence_cli.py +26 -0
- package/engine/__init__.py +11 -0
- package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/bias.cpython-312.pyc +0 -0
- package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
- package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
- package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
- package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
- package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
- package/engine/__pycache__/events.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
- package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
- package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
- package/engine/__pycache__/ids.cpython-312.pyc +0 -0
- package/engine/__pycache__/library.cpython-312.pyc +0 -0
- package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
- package/engine/__pycache__/living.cpython-312.pyc +0 -0
- package/engine/__pycache__/log.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/migration.cpython-312.pyc +0 -0
- package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
- package/engine/__pycache__/paths.cpython-312.pyc +0 -0
- package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
- package/engine/__pycache__/planner.cpython-312.pyc +0 -0
- package/engine/__pycache__/project.cpython-312.pyc +0 -0
- package/engine/__pycache__/projections.cpython-312.pyc +0 -0
- package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
- package/engine/__pycache__/run.cpython-312.pyc +0 -0
- package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
- package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
- package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
- package/engine/__pycache__/update.cpython-312.pyc +0 -0
- package/engine/__pycache__/versions.cpython-312.pyc +0 -0
- package/engine/analysis.py +308 -0
- package/engine/bias.py +178 -0
- package/engine/briefs.py +106 -0
- package/engine/capabilities.py +99 -0
- package/engine/citation_check.py +192 -0
- package/engine/contracts.py +117 -0
- package/engine/datasets.py +165 -0
- package/engine/events.py +67 -0
- package/engine/evidence_graph.py +571 -0
- package/engine/evidence_review.py +88 -0
- package/engine/evidencecore.py +182 -0
- package/engine/gap_lens.py +132 -0
- package/engine/gaps.py +169 -0
- package/engine/graph_store.py +335 -0
- package/engine/graph_validate.py +87 -0
- package/engine/ids.py +77 -0
- package/engine/library.py +268 -0
- package/engine/library_builtin.py +301 -0
- package/engine/living.py +671 -0
- package/engine/log.py +39 -0
- package/engine/meta_analysis.py +333 -0
- package/engine/meta_synthesis.py +111 -0
- package/engine/migration.py +397 -0
- package/engine/mode_router.py +72 -0
- package/engine/paths.py +15 -0
- package/engine/pilot.py +368 -0
- package/engine/planner.py +126 -0
- package/engine/project.py +118 -0
- package/engine/projections.py +240 -0
- package/engine/robustness.py +109 -0
- package/engine/run.py +85 -0
- package/engine/semantics.py +135 -0
- package/engine/study_design.py +87 -0
- package/engine/synthesis.py +187 -0
- package/engine/tribunal.py +408 -0
- package/engine/update.py +113 -0
- package/engine/versions.py +12 -0
- package/install.sh +510 -0
- package/integrations/__init__.py +1 -0
- package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
- package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
- package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
- package/integrations/agent_mcp.py +856 -0
- package/integrations/smart_web_fetch.py +59 -0
- package/package.json +50 -0
- package/pyproject.toml +55 -0
- package/references/applicability-policy.md +88 -0
- package/references/education-framing.md +132 -0
- package/references/effect_size_formulas.md +35 -0
- package/references/evaluation-design.md +111 -0
- package/references/evidence-quality.md +79 -0
- package/references/grade_framework.md +29 -0
- package/references/intervention-design.md +98 -0
- package/references/methodology-audit.md +103 -0
- package/references/outcome-taxonomy.md +106 -0
- package/references/retrieval-protocol.md +142 -0
- package/references/skeptic-protocol.md +93 -0
- package/references/social_science_pitfalls.md +48 -0
- package/references/source-validity.md +140 -0
- package/references/tribunal-policy.md +112 -0
- package/references/wwc_standards.md +29 -0
- package/retrieval/__init__.py +1 -0
- package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
- package/retrieval/corpus_store.py +181 -0
- package/retrieval/dedupe.py +127 -0
- package/retrieval/failures.py +90 -0
- package/retrieval/fetch.py +435 -0
- package/retrieval/search.py +493 -0
- package/retrieval/source.py +160 -0
- package/retrieval/validate.py +257 -0
- package/schemas/agent-mcp-approval.schema.json +57 -0
- package/schemas/chart-spec.schema.json +88 -0
- package/schemas/cross-model-review.schema.json +28 -0
- package/schemas/education-frame.schema.json +106 -0
- package/schemas/evaluation.schema.json +35 -0
- package/schemas/evidence.schema.json +81 -0
- package/schemas/fetch-result.schema.json +119 -0
- package/schemas/intervention.schema.json +46 -0
- package/schemas/methodology.schema.json +102 -0
- package/schemas/report-result.schema.json +381 -0
- package/schemas/report-spec.schema.json +130 -0
- package/schemas/source.schema.json +311 -0
- package/schemas/v2/analysis-plan.schema.json +28 -0
- package/schemas/v2/analysis-run.schema.json +33 -0
- package/schemas/v2/claim.schema.json +26 -0
- package/schemas/v2/dataset-asset.schema.json +40 -0
- package/schemas/v2/decision-snapshot.schema.json +53 -0
- package/schemas/v2/evidence-link.schema.json +38 -0
- package/schemas/v2/finding.schema.json +47 -0
- package/schemas/v2/graph-revision.schema.json +30 -0
- package/schemas/v2/knowledge-gap.schema.json +40 -0
- package/schemas/v2/methodology-audit.schema.json +30 -0
- package/schemas/v2/outcome.schema.json +18 -0
- package/schemas/v2/project.schema.json +31 -0
- package/schemas/v2/research-intent.schema.json +24 -0
- package/schemas/v2/run.schema.json +43 -0
- package/schemas/v2/source.schema.json +24 -0
- package/schemas/v2/study-design.schema.json +67 -0
- package/schemas/v2/study.schema.json +37 -0
- package/schemas/v3/pilot-outcome.schema.json +132 -0
- package/schemas/v3/run-manifest.schema.json +193 -0
- package/schemas/v3/synthesis.schema.json +86 -0
- package/schemas/v4/drift-report.schema.json +66 -0
- package/schemas/v4/evidence-library.schema.json +42 -0
- package/schemas/v4/living-subscription.schema.json +55 -0
- package/schemas/v4/meta-analysis.schema.json +152 -0
- package/schemas/verdict.schema.json +56 -0
- package/scripts/__init__.py +0 -0
- package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
- package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
- package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
- package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
- package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
- package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
- package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
- package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
- package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
- package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
- package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
- package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
- package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
- package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
- package/scripts/audit_dois.py +292 -0
- package/scripts/bake_pack.sh +37 -0
- package/scripts/benchmark.py +183 -0
- package/scripts/benchmark_evaluator.py +371 -0
- package/scripts/benchmark_judge.py +535 -0
- package/scripts/benchmark_routing.py +120 -0
- package/scripts/benchmark_v2.py +304 -0
- package/scripts/benchmark_v3.py +552 -0
- package/scripts/build_esl_artifacts.py +1921 -0
- package/scripts/build_evidence_library.py +307 -0
- package/scripts/build_killer_demo.py +295 -0
- package/scripts/build_result.py +311 -0
- package/scripts/check_version_consistency.py +96 -0
- package/scripts/citation_check.py +123 -0
- package/scripts/claim_audit.py +157 -0
- package/scripts/complexity_gate.py +180 -0
- package/scripts/compute_confidence.py +176 -0
- package/scripts/dashboard_server.py +536 -0
- package/scripts/did_regression.py +315 -0
- package/scripts/effect_calculator.py +99 -0
- package/scripts/enrich_projects_human_and_lieflat.py +315 -0
- package/scripts/evidence_matrix.py +129 -0
- package/scripts/evidence_score.py +234 -0
- package/scripts/evidence_semantics.py +87 -0
- package/scripts/fetch_benchmark.py +177 -0
- package/scripts/generate_metrics.py +99 -0
- package/scripts/generate_new_projects.py +686 -0
- package/scripts/generate_promo.py +192 -0
- package/scripts/lint_report_layout.py +182 -0
- package/scripts/orchestrator.py +1456 -0
- package/scripts/pre_verdict_gate.py +513 -0
- package/scripts/quickstart.py +121 -0
- package/scripts/rebake_all_5themes.py +88 -0
- package/scripts/recompute_demo_quality.py +205 -0
- package/scripts/render_report.py +270 -0
- package/scripts/render_report_html.py +356 -0
- package/scripts/retraction_watch.py +110 -0
- package/scripts/run_workspace.py +337 -0
- package/scripts/serve_web.py +54 -0
- package/scripts/skill_lint.py +150 -0
- package/scripts/startup_probe.py +265 -0
- package/scripts/sync_killer_demo_report.py +270 -0
- package/scripts/test_adversarial_empirical.py +541 -0
- package/scripts/validate_schema.py +256 -0
- package/skill/agents/education-planner.md +80 -0
- package/skill/agents/evaluation-designer.md +74 -0
- package/skill/agents/evidence-analyst.md +106 -0
- package/skill/agents/evidence-judge.md +111 -0
- package/skill/agents/evidence-retriever.md +80 -0
- package/skill/agents/intervention-designer.md +82 -0
- package/skill/agents/method-reviewer.md +104 -0
- package/skill/agents/skeptic.md +89 -0
- package/skill/sub-skills/aihot-trend-analysis/SKILL.md +31 -0
- package/skill/sub-skills/contradiction-analysis/SKILL.md +17 -0
- package/skill/sub-skills/data-analysis/SKILL.md +23 -0
- package/skill/sub-skills/ethics-review/SKILL.md +25 -0
- package/skill/sub-skills/evidence-extraction/SKILL.md +19 -0
- package/skill/sub-skills/evidence-review/SKILL.md +18 -0
- package/skill/sub-skills/gap-analysis/SKILL.md +25 -0
- package/skill/sub-skills/literature-review/SKILL.md +21 -0
- package/skill/sub-skills/methodology-audit/SKILL.md +20 -0
- package/skill/sub-skills/report-generation/SKILL.md +51 -0
- package/skill/sub-skills/research-planning/SKILL.md +21 -0
- package/skill/sub-skills/study-design/SKILL.md +16 -0
- package/skill/task-briefs/adjudicate.md +17 -0
- package/skill/task-briefs/audit.md +15 -0
- package/skill/task-briefs/challenge.md +15 -0
- package/skill/task-briefs/evaluate.md +13 -0
- package/skill/task-briefs/extract.md +16 -0
- package/skill/task-briefs/frame.md +17 -0
- package/skill/task-briefs/intervene.md +14 -0
- package/skill/task-briefs/present.md +16 -0
- package/skill/task-briefs/retrieve.md +15 -0
- package/visualization/eduevidence-report/assets/base.css +337 -0
- package/visualization/eduevidence-report/motion/motion.css +157 -0
- package/visualization/eduevidence-report/motion/motion.js +107 -0
- package/visualization/eduevidence-report/references/bilingual-style.md +7 -0
- package/visualization/eduevidence-report/references/component-catalog.md +145 -0
- package/visualization/eduevidence-report/references/evidence-expansion.md +65 -0
- package/visualization/eduevidence-report/references/full-report-outline.md +86 -0
- package/visualization/eduevidence-report/references/layout-constraints.md +63 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +79 -0
- package/visualization/eduevidence-report/references/motion-system.md +31 -0
- package/visualization/eduevidence-report/schemas/adapter-envelope.schema.json +22 -0
- package/visualization/eduevidence-report/schemas/visual-layout.schema.json +87 -0
- package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/adapter_contract.py +72 -0
- package/visualization/eduevidence-report/scripts/build_artifact_manifest.py +70 -0
- package/visualization/eduevidence-report/scripts/build_charts.py +283 -0
- package/visualization/eduevidence-report/scripts/build_figures.py +515 -0
- package/visualization/eduevidence-report/scripts/build_infographics.py +268 -0
- package/visualization/eduevidence-report/scripts/build_report.py +3211 -0
- package/visualization/eduevidence-report/scripts/charts_data.py +617 -0
- package/visualization/eduevidence-report/scripts/check_mobile_layout.js +220 -0
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +852 -0
- package/visualization/eduevidence-report/scripts/zh_labels.py +245 -0
- package/visualization/eduevidence-report/themes/academic.css +94 -0
- package/visualization/eduevidence-report/themes/claude.css +96 -0
- package/visualization/eduevidence-report/themes/datalab-dark.css +147 -0
- package/visualization/eduevidence-report/themes/datalab.css +151 -0
- package/visualization/eduevidence-report/themes/presentation.css +140 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* check_mobile_layout.js — Browser-level layout gate for the five baked report
|
|
3
|
+
themes. Zero dependencies (Node ≥21: global WebSocket + fetch).
|
|
4
|
+
|
|
5
|
+
Measures each report at the given viewports in BOTH Visual Brief and Full
|
|
6
|
+
Report views. Flags TRUE layout problems only:
|
|
7
|
+
- page horizontal overflow (documentElement.scrollWidth > innerWidth)
|
|
8
|
+
- visible .report-shell whose scrollWidth exceeds clientWidth
|
|
9
|
+
- elements escaping the viewport that are NOT inside a scrollable
|
|
10
|
+
ancestor and NOT inside an <svg> (those are legit: tables/actions scroll
|
|
11
|
+
inside their own overflow-x:auto containers)
|
|
12
|
+
- non-scrollable elements whose content is clipped (scrollW > clientW)
|
|
13
|
+
- gallery reveal contract: every [data-lieflat] card in the visible shell
|
|
14
|
+
must gain is-live once scrolled into view (scroll-reveal works).
|
|
15
|
+
LocalStorage is cleared per URL so view state never leaks between themes.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
node check_mobile_layout.js --port 9230 [--widths 390,768,1280] <url>...
|
|
19
|
+
Exit 0 = clean; 1 = violations; 42 = runtime failure.
|
|
20
|
+
*/
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const { spawn } = require('child_process');
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const os = require('os');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
const CHROME = (() => {
|
|
29
|
+
for (const p of [
|
|
30
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
31
|
+
'/usr/bin/google-chrome', '/usr/bin/google-chrome-stable',
|
|
32
|
+
'/usr/bin/chromium', '/usr/bin/chromium-browser',
|
|
33
|
+
]) if (fs.existsSync(p)) return p;
|
|
34
|
+
return null;
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
const args = process.argv.slice(2);
|
|
38
|
+
const PORT = Number((args.find(a => a.startsWith('--port=')) || '--port=9230').split('=')[1]);
|
|
39
|
+
const WIDTHS = (args.find(a => a.startsWith('--widths=')) || '--widths=390,768,1280')
|
|
40
|
+
.split('=')[1].split(',').map(Number);
|
|
41
|
+
const HEIGHT = Number((args.find(a => a.startsWith('--height=')) || '--height=1200').split('=')[1]);
|
|
42
|
+
const urls = args.filter(a => a.startsWith('http') || a.startsWith('file://'));
|
|
43
|
+
if (!CHROME) { console.log('SKIP no chrome binary'); process.exit(0); }
|
|
44
|
+
if (!urls.length) { console.error('usage: node check_mobile_layout.js --port 9230 <url>...'); process.exit(2); }
|
|
45
|
+
|
|
46
|
+
let chrome = null, seq = 0;
|
|
47
|
+
const pending = new Map();
|
|
48
|
+
|
|
49
|
+
function cdpSend(ws, method, params = {}) {
|
|
50
|
+
const id = ++seq;
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
pending.set(id, { resolve, reject });
|
|
53
|
+
ws.send(JSON.stringify({ id, method, params }));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
57
|
+
|
|
58
|
+
const MEASURE = `(() => {
|
|
59
|
+
const vw = window.innerWidth;
|
|
60
|
+
const chain = el => { const c = []; let n = el;
|
|
61
|
+
while (n && n.tagName !== 'BODY' && c.length < 5) {
|
|
62
|
+
c.push(n.tagName.toLowerCase() + (n.className ? '.' + String(typeof n.className==='object' ? n.className.baseVal||'' : n.className||'').trim().split(/\\s+/)[0] : ''));
|
|
63
|
+
n = n.parentElement; }
|
|
64
|
+
return c.join(' < '); };
|
|
65
|
+
const inScroll = el => { let n = el.parentElement;
|
|
66
|
+
while (n) { const s = getComputedStyle(n);
|
|
67
|
+
if (s.overflowX === 'auto' || s.overflowX === 'scroll' || s.overflowY === 'auto' || s.overflowY === 'scroll') return true;
|
|
68
|
+
if (n.tagName === 'BODY' || n.tagName === 'HTML') break;
|
|
69
|
+
n = n.parentElement; }
|
|
70
|
+
return false; };
|
|
71
|
+
const inSvg = el => { let n = el.parentElement;
|
|
72
|
+
while (n) { if (n.tagName && n.tagName.toLowerCase() === 'svg') return true; const p = n.parentElement; if (!p) break; n = p; }
|
|
73
|
+
return false; };
|
|
74
|
+
const visible = el => { const s = getComputedStyle(el);
|
|
75
|
+
return s.display !== 'none' && s.visibility !== 'hidden' && el.getClientRects().length > 0; };
|
|
76
|
+
const dsw = document.documentElement.scrollWidth;
|
|
77
|
+
const escapes = [...document.querySelectorAll('body *')]
|
|
78
|
+
.filter(visible)
|
|
79
|
+
.filter(el => !inSvg(el))
|
|
80
|
+
.filter(el => !inScroll(el))
|
|
81
|
+
.map(el => { const r = el.getBoundingClientRect();
|
|
82
|
+
return { chain: chain(el), right: Math.round(r.right), left: Math.round(r.left), w: Math.round(r.width) }; })
|
|
83
|
+
.filter(o => o.right > vw + 2 || o.left < -2)
|
|
84
|
+
.slice(0, 6);
|
|
85
|
+
let shell = null;
|
|
86
|
+
const shells = [...document.querySelectorAll('.report-shell')].filter(visible);
|
|
87
|
+
if (shells.length) { const s = shells[0]; shell = { sw: s.scrollWidth, cw: s.clientWidth, full: getComputedStyle(s).overflowX }; }
|
|
88
|
+
const clipped = [...document.querySelectorAll('body *')]
|
|
89
|
+
.filter(visible).filter(el => !inSvg(el)).filter(el => !inScroll(el))
|
|
90
|
+
.filter(el => el.scrollWidth > el.clientWidth + 3)
|
|
91
|
+
.map(el => ({ chain: chain(el), sw: el.scrollWidth, cw: el.clientWidth }))
|
|
92
|
+
.slice(0, 4);
|
|
93
|
+
// gallery reveal contract: zh+en shells; only the visible shell matters
|
|
94
|
+
const okReveal = (() => {
|
|
95
|
+
const cards = [...document.querySelectorAll('[data-lieflat]')].filter(visible);
|
|
96
|
+
return { cards: cards.length, live: cards.filter(c => c.classList.contains('is-live')).length };
|
|
97
|
+
})();
|
|
98
|
+
// Elements whose BOX extends past the shell's right edge — that is what
|
|
99
|
+
// actually creates horizontal scroll overflow (scrollWidth can mislead).
|
|
100
|
+
const shellCw = shells.length ? shells[0].clientWidth : vw;
|
|
101
|
+
const shellLeft = shells.length ? shells[0].getBoundingClientRect().left : 0;
|
|
102
|
+
const widest = shells.length
|
|
103
|
+
? [...shells[0].querySelectorAll('*')]
|
|
104
|
+
.filter(visible)
|
|
105
|
+
.filter(el => !inSvg(el)) // SVG 内部随 viewBox 缩放,不参与布局溢出
|
|
106
|
+
.filter(el => !inScroll(el)) // 内部滚动容器已裁剪,不会撑破 shell
|
|
107
|
+
.map(el => {
|
|
108
|
+
const r = el.getBoundingClientRect();
|
|
109
|
+
return { chain: chain(el), over: Math.round(r.right - shellLeft - shellCw),
|
|
110
|
+
w: Math.round(r.width),
|
|
111
|
+
hint: (el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 70) };
|
|
112
|
+
})
|
|
113
|
+
.filter(o => o.over > 4)
|
|
114
|
+
.sort((a, b) => b.over - a.over)
|
|
115
|
+
.slice(0, 8)
|
|
116
|
+
: [];
|
|
117
|
+
return { vw, dsw, overflow: dsw > vw + 4, shell, escapes, clipped, okReveal, widest };
|
|
118
|
+
})()`;
|
|
119
|
+
|
|
120
|
+
async function measure(ws, width, height, view) {
|
|
121
|
+
await cdpSend(ws, 'Emulation.setDeviceMetricsOverride',
|
|
122
|
+
{ width, height, deviceScaleFactor: 1, mobile: width <= 768 });
|
|
123
|
+
await cdpSend(ws, 'Page.enable').catch(() => {});
|
|
124
|
+
// clear persisted view/lang state, force reload in the requested view
|
|
125
|
+
await cdpSend(ws, 'Runtime.evaluate', {
|
|
126
|
+
expression: `try{localStorage.removeItem('eduevidence-report-view');localStorage.setItem('eduevidence-report-view','${view}')}catch(e){}`,
|
|
127
|
+
});
|
|
128
|
+
await cdpSend(ws, 'Page.reload', { ignoreCache: true }).catch(() => {});
|
|
129
|
+
await sleep(1200);
|
|
130
|
+
// let the language switcher settle then scroll through the page so
|
|
131
|
+
// IntersectionObserver reveal can fire for below-fold cards
|
|
132
|
+
await cdpSend(ws, 'Runtime.evaluate', {
|
|
133
|
+
expression: `(() => { const max = document.body.scrollHeight; let y = 0;
|
|
134
|
+
const step = () => { window.scrollTo(0, y); y += Math.max(200, window.innerHeight * 0.7);
|
|
135
|
+
if (y < max) { setTimeout(step, 90); } };
|
|
136
|
+
step(); })()`,
|
|
137
|
+
});
|
|
138
|
+
await sleep(2400);
|
|
139
|
+
await cdpSend(ws, 'Runtime.evaluate', { expression: 'window.scrollTo(0, 0)' });
|
|
140
|
+
await sleep(300);
|
|
141
|
+
const r = await cdpSend(ws, 'Runtime.evaluate', { expression: MEASURE, returnByValue: true });
|
|
142
|
+
return r.result.value;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function main() {
|
|
146
|
+
chrome = spawn(CHROME, [
|
|
147
|
+
'--headless=new', '--disable-gpu', '--no-first-run', '--no-default-browser-check',
|
|
148
|
+
`--remote-debugging-port=${PORT}`,
|
|
149
|
+
`--user-data-dir=${fs.mkdtempSync(path.join(os.tmpdir(), 'edue-layout-'))}`,
|
|
150
|
+
'--no-report-upload', 'about:blank',
|
|
151
|
+
], { stdio: ['ignore', 'ignore', 'ignore'] });
|
|
152
|
+
let wsInfo = null;
|
|
153
|
+
for (let i = 0; i < 60; i++) {
|
|
154
|
+
try {
|
|
155
|
+
const v = await (await fetch(`http://127.0.0.1:${PORT}/json/version`)).json();
|
|
156
|
+
if (v.webSocketDebuggerUrl) { wsInfo = v; break; }
|
|
157
|
+
} catch (e) { /* retry */ }
|
|
158
|
+
await sleep(400);
|
|
159
|
+
}
|
|
160
|
+
if (!wsInfo) { console.error('FAIL chrome CDP not reachable'); process.exit(42); }
|
|
161
|
+
|
|
162
|
+
const browserWs = new WebSocket(wsInfo.webSocketDebuggerUrl);
|
|
163
|
+
await new Promise((res, rej) => { browserWs.onopen = res; browserWs.onerror = rej; });
|
|
164
|
+
browserWs.onmessage = ev => {
|
|
165
|
+
const m = JSON.parse(ev.data);
|
|
166
|
+
if (m.id && pending.has(m.id)) {
|
|
167
|
+
const p = pending.get(m.id); pending.delete(m.id);
|
|
168
|
+
m.error ? p.reject(new Error(m.error.message)) : p.resolve(m.result);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
let violations = 0;
|
|
173
|
+
for (const url of urls) {
|
|
174
|
+
const target = await (await fetch(`http://127.0.0.1:${PORT}/json/new?about:blank`, { method: 'PUT' })).json();
|
|
175
|
+
const pageWs = new WebSocket(target.webSocketDebuggerUrl);
|
|
176
|
+
await new Promise((res, rej) => { pageWs.onopen = res; pageWs.onerror = rej; });
|
|
177
|
+
pageWs.onmessage = ev => {
|
|
178
|
+
const m = JSON.parse(ev.data);
|
|
179
|
+
if (m.id && pending.has(m.id)) {
|
|
180
|
+
const p = pending.get(m.id); pending.delete(m.id);
|
|
181
|
+
m.error ? p.reject(new Error(m.error.message)) : p.resolve(m.result);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
await cdpSend(pageWs, 'Page.navigate', { url });
|
|
185
|
+
await sleep(1000);
|
|
186
|
+
for (const w of WIDTHS) {
|
|
187
|
+
for (const view of ['brief', 'full']) {
|
|
188
|
+
let m;
|
|
189
|
+
try { m = await measure(pageWs, w, HEIGHT, view); }
|
|
190
|
+
catch (e) { console.log(`err ${path.basename(new URL(url).pathname)} @${w} [${view}]: ${e.message}`); violations++; continue; }
|
|
191
|
+
const label = `${path.basename(new URL(url).pathname)} @${w}px [${view}]`;
|
|
192
|
+
const problems = [];
|
|
193
|
+
if (m.overflow) problems.push(`page overflow scrollWidth=${m.dsw}>innerWidth=${m.vw}`);
|
|
194
|
+
if (m.shell && m.shell.sw > m.shell.cw + 4) problems.push(`shell scrollW=${m.shell.sw}>clientW=${m.shell.cw}`);
|
|
195
|
+
if (m.escapes.length) problems.push(`escapes(${m.escapes.length})`);
|
|
196
|
+
if (m.clipped.length) problems.push(`clipped(${m.clipped.length})`);
|
|
197
|
+
if (m.okReveal.cards > 0 && m.okReveal.live < m.okReveal.cards) {
|
|
198
|
+
problems.push(`reveal ${m.okReveal.live}/${m.okReveal.cards} cards got is-live`);
|
|
199
|
+
}
|
|
200
|
+
if (problems.length) {
|
|
201
|
+
violations++;
|
|
202
|
+
console.log(`VIOLATION ${label}: ${problems.join(' | ')}`);
|
|
203
|
+
for (const e of m.escapes.slice(0, 3)) console.log(` esc <${e.chain}> right=${e.right} w=${e.w}`);
|
|
204
|
+
for (const c of m.clipped.slice(0, 2)) console.log(` clip <${c.chain}> sw=${c.sw} cw=${c.cw}`);
|
|
205
|
+
for (const wd of m.widest || []) console.log(` wide <${wd.chain}> over=${wd.over}px w=${wd.w} "${wd.hint}"`);
|
|
206
|
+
} else {
|
|
207
|
+
console.log(`ok ${label}: scrollW=${m.dsw} shell=${m.shell ? m.shell.sw + '/' + m.shell.cw : 'n/a'} reveal=${m.okReveal.live}/${m.okReveal.cards}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
pageWs.close();
|
|
212
|
+
}
|
|
213
|
+
browserWs.close();
|
|
214
|
+
try { chrome.kill(); } catch (e) {}
|
|
215
|
+
if (violations) { console.error(`FAIL ${violations} layout violations`); process.exit(1); }
|
|
216
|
+
console.log('ALL CLEAN');
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
main().catch(e => { console.error('FATAL', e.message); try { chrome && chrome.kill(); } catch (_) {} process.exit(42); });
|