@svelte-vitals/core 0.37.0 → 0.39.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/dist/index.d.ts +13 -7
- package/dist/index.js +211 -75
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -971,7 +971,7 @@ interface Rule {
|
|
|
971
971
|
category: Category;
|
|
972
972
|
/** Default severity (overridable by config in later slices). */
|
|
973
973
|
severity: Severity;
|
|
974
|
-
/** 'route' = evaluated per route, 'project' = site-wide (design §10, §12). */
|
|
974
|
+
/** 'route' = evaluated per route, 'project' = site-wide, 'component' = evaluated per source file (design §10, §12). */
|
|
975
975
|
scope: Scope;
|
|
976
976
|
/** Why this rule matters — one or two sentences, surfaced by `svelte-vitals explain` (issue #24). */
|
|
977
977
|
rationale: string;
|
|
@@ -1268,11 +1268,19 @@ declare const architectureDirectoryNaming: Rule;
|
|
|
1268
1268
|
|
|
1269
1269
|
/**
|
|
1270
1270
|
* architecture/reserved-directory-names — a directory's immediate subdirectories may only take names
|
|
1271
|
-
* the project declared for that position (design 2026-07-29
|
|
1271
|
+
* the project declared for that position (design 2026-07-29, extended 2026-08-08 for lowercase units —
|
|
1272
|
+
* issue #386).
|
|
1272
1273
|
*
|
|
1273
|
-
*
|
|
1274
|
+
* The option maps differ in what their keys name. A `scopes` key names the parent directly. A
|
|
1274
1275
|
* `unitScopes` key names a root, and the rule governs the children of whichever directories beneath
|
|
1275
|
-
* it are units — the shape a glob cannot reach, because units nest to arbitrary
|
|
1276
|
+
* it are units whose name begins A–Z — the shape a glob cannot reach, because units nest to arbitrary
|
|
1277
|
+
* depth. An `anyCaseUnitScopes` key names a root the same way, but governs units of *either* case:
|
|
1278
|
+
* `isUnitDir`'s letter test — A–Z plus a same-stemmed entry file, whatever its extension — excludes a
|
|
1279
|
+
* lowercase unit, so without this map no generic unit-map declaration governed one's children (a
|
|
1280
|
+
* `scopes` key naming the parent directly could still reach one) — measured at 129 of 299 units (43%)
|
|
1281
|
+
* on a real tree. Neither map is named with the bare word "unit": the
|
|
1282
|
+
* sibling rule `architecture/reserved-name-placement` records why that word alone is ambiguous between
|
|
1283
|
+
* the two predicates once both exist.
|
|
1276
1284
|
*
|
|
1277
1285
|
* There are no pass results. `computeScore` seeds every distinct `route` at 100 and averages, and the
|
|
1278
1286
|
* subject here is a directory with no pre-existing score key, so a pass per directory would add
|
|
@@ -1643,13 +1651,11 @@ declare const APP_SCRIPT: string;
|
|
|
1643
1651
|
declare function renderAppShell(snapshot: AppSnapshot): string;
|
|
1644
1652
|
/**
|
|
1645
1653
|
* Static (non-live) document over a prebuilt JsonReport — kept as the public name the
|
|
1646
|
-
* html reporter has always exported.
|
|
1654
|
+
* html reporter has always exported.
|
|
1647
1655
|
*/
|
|
1648
1656
|
declare function buildHtmlDocument(report: JsonReport, meta: {
|
|
1649
1657
|
version: string;
|
|
1650
1658
|
coreVersion?: string;
|
|
1651
|
-
}, opts?: {
|
|
1652
|
-
routeBadges?: Record<string, RouteBadge>;
|
|
1653
1659
|
}): string;
|
|
1654
1660
|
/** Render results as the self-contained HTML report (the CLI's `--reporter html`). */
|
|
1655
1661
|
declare function formatHtmlReport(results: Result[], config: Config, meta: {
|
package/dist/index.js
CHANGED
|
@@ -167,17 +167,9 @@ function collectEachBlocks(node, source, acc) {
|
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
var WALK_IGNORED_KEYS = /* @__PURE__ */ new Set(["type", "start", "end", "loc", "range"]);
|
|
170
|
+
var NO_BOUNDARIES = /* @__PURE__ */ new Set();
|
|
170
171
|
function walkEstree(node, visit) {
|
|
171
|
-
|
|
172
|
-
for (const child of node) walkEstree(child, visit);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
if (!node || typeof node !== "object" || typeof node.type !== "string") return;
|
|
176
|
-
visit(node);
|
|
177
|
-
for (const key of Object.keys(node)) {
|
|
178
|
-
if (WALK_IGNORED_KEYS.has(key)) continue;
|
|
179
|
-
walkEstree(node[key], visit);
|
|
180
|
-
}
|
|
172
|
+
walkEvalScope(node, (n) => void visit(n), /* @__PURE__ */ new Set(), NO_BOUNDARIES);
|
|
181
173
|
}
|
|
182
174
|
function isEffectCall(node) {
|
|
183
175
|
const c = node?.callee;
|
|
@@ -280,18 +272,7 @@ function scopeIntroducedNames(node) {
|
|
|
280
272
|
return introduced;
|
|
281
273
|
}
|
|
282
274
|
function walkScoped(node, visit, shadowed = /* @__PURE__ */ new Set()) {
|
|
283
|
-
|
|
284
|
-
for (const child of node) walkScoped(child, visit, shadowed);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (!node || typeof node !== "object" || typeof node.type !== "string") return;
|
|
288
|
-
const introduced = scopeIntroducedNames(node);
|
|
289
|
-
const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
|
|
290
|
-
visit(node, scope);
|
|
291
|
-
for (const key of Object.keys(node)) {
|
|
292
|
-
if (WALK_IGNORED_KEYS.has(key)) continue;
|
|
293
|
-
walkScoped(node[key], visit, scope);
|
|
294
|
-
}
|
|
275
|
+
walkEvalScope(node, (n, scope) => void visit(n, scope), shadowed, NO_BOUNDARIES);
|
|
295
276
|
}
|
|
296
277
|
function collectStateWrites(root, stateNames, acc, kinds) {
|
|
297
278
|
const record = (name, kind) => {
|
|
@@ -769,7 +750,7 @@ function collectPropNames(program, includeBindable) {
|
|
|
769
750
|
let seen = 0;
|
|
770
751
|
let ambiguous = false;
|
|
771
752
|
walkEstree(program, (n) => {
|
|
772
|
-
if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(n.init)) return;
|
|
753
|
+
if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(unwrapTs(n.init))) return;
|
|
773
754
|
seen++;
|
|
774
755
|
if (n.id?.type === "Identifier") {
|
|
775
756
|
names.add(n.id.name);
|
|
@@ -842,10 +823,10 @@ function countProps(program) {
|
|
|
842
823
|
let seen = 0;
|
|
843
824
|
let uncountable = false;
|
|
844
825
|
walkEstree(program, (n) => {
|
|
845
|
-
if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(n.init)) return;
|
|
826
|
+
if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(unwrapTs(n.init))) return;
|
|
846
827
|
seen++;
|
|
847
828
|
const props = n.id?.type === "ObjectPattern" ? n.id.properties : void 0;
|
|
848
|
-
if (!Array.isArray(props)
|
|
829
|
+
if (!Array.isArray(props)) {
|
|
849
830
|
uncountable = true;
|
|
850
831
|
return;
|
|
851
832
|
}
|
|
@@ -958,19 +939,19 @@ var EVAL_SCOPE_BOUNDARIES = /* @__PURE__ */ new Set([
|
|
|
958
939
|
"ClassDeclaration",
|
|
959
940
|
"ClassExpression"
|
|
960
941
|
]);
|
|
961
|
-
function walkEvalScope(node, visit, shadowed = /* @__PURE__ */ new Set()) {
|
|
942
|
+
function walkEvalScope(node, visit, shadowed = /* @__PURE__ */ new Set(), boundaries = EVAL_SCOPE_BOUNDARIES) {
|
|
962
943
|
if (Array.isArray(node)) {
|
|
963
|
-
for (const child of node) walkEvalScope(child, visit, shadowed);
|
|
944
|
+
for (const child of node) walkEvalScope(child, visit, shadowed, boundaries);
|
|
964
945
|
return;
|
|
965
946
|
}
|
|
966
947
|
if (!node || typeof node !== "object" || typeof node.type !== "string") return;
|
|
967
948
|
const introduced = scopeIntroducedNames(node);
|
|
968
949
|
const scope = introduced.size > 0 ? /* @__PURE__ */ new Set([...shadowed, ...introduced]) : shadowed;
|
|
969
950
|
if (visit(node, scope)) return;
|
|
970
|
-
if (
|
|
951
|
+
if (boundaries.has(node.type)) return;
|
|
971
952
|
for (const key of Object.keys(node)) {
|
|
972
953
|
if (WALK_IGNORED_KEYS.has(key)) continue;
|
|
973
|
-
walkEvalScope(node[key], visit, scope);
|
|
954
|
+
walkEvalScope(node[key], visit, scope, boundaries);
|
|
974
955
|
}
|
|
975
956
|
}
|
|
976
957
|
function collectEvalScopeCalls(root, source, matcher, skipSubtree, initialShadowed) {
|
|
@@ -1261,13 +1242,13 @@ function collectModuleStateDecls(program, source) {
|
|
|
1261
1242
|
const decl = unwrapExport(stmt);
|
|
1262
1243
|
if (decl?.type === "VariableDeclaration") {
|
|
1263
1244
|
for (const d of decl.declarations ?? []) {
|
|
1264
|
-
if (d?.id?.type === "Identifier" && d.init && isStateDeclaration(d.init)) {
|
|
1245
|
+
if (d?.id?.type === "Identifier" && d.init && isStateDeclaration(unwrapTs(d.init))) {
|
|
1265
1246
|
out.push({ name: d.id.name, line: lineOf(source, d.start) });
|
|
1266
1247
|
}
|
|
1267
1248
|
}
|
|
1268
1249
|
} else if (decl?.type === "ClassDeclaration" && decl.id?.type === "Identifier") {
|
|
1269
1250
|
const hasStateField = (decl.body?.body ?? []).some(
|
|
1270
|
-
(m) => m?.type === "PropertyDefinition" && m.value && isStateDeclaration(m.value)
|
|
1251
|
+
(m) => m?.type === "PropertyDefinition" && m.value && isStateDeclaration(unwrapTs(m.value))
|
|
1271
1252
|
);
|
|
1272
1253
|
if (hasStateField) statefulClasses.add(decl.id.name);
|
|
1273
1254
|
}
|
|
@@ -1303,6 +1284,17 @@ function parseModuleFacts(source, filename) {
|
|
|
1303
1284
|
for (const l of raw) basePathLinks.push({ ...l, line: shift(l.line) });
|
|
1304
1285
|
basePathLinks.sort((a, b) => a.line - b.line);
|
|
1305
1286
|
}
|
|
1287
|
+
const importSpans = [];
|
|
1288
|
+
const namespaceImports = [];
|
|
1289
|
+
if (program) {
|
|
1290
|
+
const rawImportSpans = [];
|
|
1291
|
+
collectImportSources(program, wrapped, rawImportSpans);
|
|
1292
|
+
for (const s of rawImportSpans) importSpans.push({ ...s, line: shift(s.line) });
|
|
1293
|
+
const rawNamespaceImports = [];
|
|
1294
|
+
collectNamespaceImports(program, wrapped, rawNamespaceImports);
|
|
1295
|
+
for (const n of rawNamespaceImports) namespaceImports.push({ ...n, line: shift(n.line) });
|
|
1296
|
+
}
|
|
1297
|
+
const imports = importSpans.map((s) => s.source);
|
|
1306
1298
|
return {
|
|
1307
1299
|
eachBlocks: [],
|
|
1308
1300
|
effects: [],
|
|
@@ -1310,9 +1302,9 @@ function parseModuleFacts(source, filename) {
|
|
|
1310
1302
|
javascriptUrls: [],
|
|
1311
1303
|
loc: 0,
|
|
1312
1304
|
propCount: 0,
|
|
1313
|
-
imports
|
|
1314
|
-
importSpans
|
|
1315
|
-
namespaceImports
|
|
1305
|
+
imports,
|
|
1306
|
+
importSpans,
|
|
1307
|
+
namespaceImports,
|
|
1316
1308
|
constableStates: [],
|
|
1317
1309
|
mutatedProps: [],
|
|
1318
1310
|
stalePropDerivations: [],
|
|
@@ -1409,11 +1401,12 @@ function parseComponentFacts(source, filename) {
|
|
|
1409
1401
|
const stateDecls = [];
|
|
1410
1402
|
walkEstree(program, (n) => {
|
|
1411
1403
|
if (n.type !== "VariableDeclarator" || !n.init) return;
|
|
1412
|
-
|
|
1404
|
+
const init = unwrapTs(n.init);
|
|
1405
|
+
if (isStateDeclaration(init) && n.id?.type === "Identifier") {
|
|
1413
1406
|
stateNames.add(n.id.name);
|
|
1414
1407
|
stateDecls.push({ name: n.id.name, line: lineOf(source, n.start) });
|
|
1415
1408
|
}
|
|
1416
|
-
if (isStateDeclaration(
|
|
1409
|
+
if (isStateDeclaration(init) || isDerivedDeclaration(init) || isPropsCall(init))
|
|
1417
1410
|
addBoundNames(n.id, reactiveNames);
|
|
1418
1411
|
});
|
|
1419
1412
|
walkEstree(program, (n) => {
|
|
@@ -1440,8 +1433,10 @@ function parseComponentFacts(source, filename) {
|
|
|
1440
1433
|
for (const stmt of program.body ?? []) {
|
|
1441
1434
|
if (stmt?.type !== "VariableDeclaration") continue;
|
|
1442
1435
|
for (const d of stmt.declarations ?? []) {
|
|
1443
|
-
if (d?.id?.type !== "Identifier" || !d.init
|
|
1444
|
-
const
|
|
1436
|
+
if (d?.id?.type !== "Identifier" || !d.init) continue;
|
|
1437
|
+
const init = unwrapTs(d.init);
|
|
1438
|
+
if (!isPlainStateCall(init)) continue;
|
|
1439
|
+
const arg = unwrapTs(init.arguments?.[0]);
|
|
1445
1440
|
if (arg?.type === "ObjectExpression" || arg?.type === "ArrayExpression") {
|
|
1446
1441
|
rawableCandidates.push({ name: d.id.name, line: lineOf(source, d.start) });
|
|
1447
1442
|
}
|
|
@@ -1475,8 +1470,10 @@ function parseComponentFacts(source, filename) {
|
|
|
1475
1470
|
for (const stmt of program.body ?? []) {
|
|
1476
1471
|
if (stmt?.type !== "VariableDeclaration") continue;
|
|
1477
1472
|
for (const d of stmt.declarations ?? []) {
|
|
1478
|
-
if (d?.id?.type !== "Identifier" || !d.init
|
|
1479
|
-
const
|
|
1473
|
+
if (d?.id?.type !== "Identifier" || !d.init) continue;
|
|
1474
|
+
const init = unwrapTs(d.init);
|
|
1475
|
+
if (!isPlainStateCall(init)) continue;
|
|
1476
|
+
const arg = unwrapTs(init.arguments?.[0]);
|
|
1480
1477
|
if (arg?.type === "NewExpression" && arg.callee?.type === "Identifier" && BUILTIN_STATE_TYPES.has(arg.callee.name)) {
|
|
1481
1478
|
builtinCandidates.set(d.id.name, { type: arg.callee.name, line: lineOf(source, d.start) });
|
|
1482
1479
|
}
|
|
@@ -2740,6 +2737,13 @@ function imageRule(opts) {
|
|
|
2740
2737
|
severity: opts.severity,
|
|
2741
2738
|
detection: { presence: "own", value: "static" },
|
|
2742
2739
|
route: route.route,
|
|
2740
|
+
// No single route-level file exists here (unlike ResolvedHead.file) — the
|
|
2741
|
+
// route's first image stands in as its attributed file (design
|
|
2742
|
+
// 2026-08-08-pass-result-location-design.md; this uncaught inline PASS literal
|
|
2743
|
+
// was missed by the design spike's grep and added to its blast-radius table
|
|
2744
|
+
// afterward, maintainer ruling, same date). `route.images.length === 0` already
|
|
2745
|
+
// continued above, so `[0]` is always defined here.
|
|
2746
|
+
location: route.images[0].file,
|
|
2743
2747
|
message: opts.label,
|
|
2744
2748
|
recommendation: opts.recommendation,
|
|
2745
2749
|
docsUrl: docsUrl12
|
|
@@ -2839,6 +2843,13 @@ function linkRule(opts) {
|
|
|
2839
2843
|
severity: opts.severity,
|
|
2840
2844
|
detection: { presence: "own", value: "static" },
|
|
2841
2845
|
route: head.route,
|
|
2846
|
+
// The route's own attributed file (design 2026-08-08-pass-result-location-design.md)
|
|
2847
|
+
// — this uncaught inline PASS literal was missed by the design spike's grep and
|
|
2848
|
+
// added to its blast-radius table afterward (maintainer ruling, same date). No
|
|
2849
|
+
// single per-tag location applies here (many links can back one pass), so the
|
|
2850
|
+
// route's own head file is the uniform attribution; per-tag penalized locations
|
|
2851
|
+
// above remain per-tag.
|
|
2852
|
+
location: head.file,
|
|
2842
2853
|
message: opts.label,
|
|
2843
2854
|
recommendation: opts.recommendation,
|
|
2844
2855
|
docsUrl: docsUrl12
|
|
@@ -2941,6 +2952,11 @@ var performanceLcpImage = {
|
|
|
2941
2952
|
severity: "warning",
|
|
2942
2953
|
detection: { presence: "own", value: "static" },
|
|
2943
2954
|
route: route.route,
|
|
2955
|
+
// Same `location` the penalized branch above uses (design
|
|
2956
|
+
// 2026-08-08-pass-result-location-design.md) — this uncaught inline PASS literal
|
|
2957
|
+
// was missed by the design spike's grep and added to its blast-radius table
|
|
2958
|
+
// afterward (maintainer ruling, same date).
|
|
2959
|
+
location: first.file,
|
|
2944
2960
|
message: "LCP image eager loading",
|
|
2945
2961
|
recommendation,
|
|
2946
2962
|
docsUrl
|
|
@@ -2995,6 +3011,10 @@ var performanceRenderBlockingScript = {
|
|
|
2995
3011
|
severity: "warning",
|
|
2996
3012
|
detection: { presence: "own", value: "static" },
|
|
2997
3013
|
route: head.route,
|
|
3014
|
+
// The route's own attributed file (design 2026-08-08-pass-result-location-design.md)
|
|
3015
|
+
// — this uncaught inline PASS literal was missed by the design spike's grep and
|
|
3016
|
+
// added to its blast-radius table afterward (maintainer ruling, same date).
|
|
3017
|
+
location: head.file,
|
|
2998
3018
|
message: "No render-blocking scripts",
|
|
2999
3019
|
recommendation: recommendation2,
|
|
3000
3020
|
docsUrl: docsUrl2
|
|
@@ -3250,6 +3270,10 @@ var performancePreconnect = {
|
|
|
3250
3270
|
severity: "info",
|
|
3251
3271
|
detection: { presence: "own", value: "static" },
|
|
3252
3272
|
route: head.route,
|
|
3273
|
+
// head.file — the same target used to resolve options above (design
|
|
3274
|
+
// 2026-08-08-pass-result-location-design.md) — so a `files:`-scoped override can
|
|
3275
|
+
// also match this passing seed via `severity: 'off'`.
|
|
3276
|
+
location: head.file,
|
|
3253
3277
|
message: "Third-party origins are preconnected",
|
|
3254
3278
|
recommendation: recommendation3,
|
|
3255
3279
|
docsUrl: docsUrl3
|
|
@@ -3514,7 +3538,6 @@ var PLACEHOLDER_RES = [
|
|
|
3514
3538
|
/yourcompany/i,
|
|
3515
3539
|
/your name here/i
|
|
3516
3540
|
];
|
|
3517
|
-
var PLACEHOLDERS = PLACEHOLDER_RES.map((r) => r.source);
|
|
3518
3541
|
function hasPlaceholder(s) {
|
|
3519
3542
|
return PLACEHOLDER_RES.some((re) => re.test(s));
|
|
3520
3543
|
}
|
|
@@ -3592,6 +3615,9 @@ function jsonldRule(opts) {
|
|
|
3592
3615
|
severity: opts.severity,
|
|
3593
3616
|
detection: PASS,
|
|
3594
3617
|
route: head.route,
|
|
3618
|
+
// Same `location` the penalized branch above uses (design
|
|
3619
|
+
// 2026-08-08-pass-result-location-design.md).
|
|
3620
|
+
location: head.file,
|
|
3595
3621
|
message: opts.label,
|
|
3596
3622
|
recommendation: opts.recommendation,
|
|
3597
3623
|
docsUrl: docsUrl12
|
|
@@ -3645,6 +3671,9 @@ var seoJsonLdValidity = {
|
|
|
3645
3671
|
severity: "warning",
|
|
3646
3672
|
detection: PASS,
|
|
3647
3673
|
route: head.route,
|
|
3674
|
+
// Same `location` the penalized branch above uses (design
|
|
3675
|
+
// 2026-08-08-pass-result-location-design.md).
|
|
3676
|
+
location: head.file,
|
|
3648
3677
|
message: "JSON-LD validity",
|
|
3649
3678
|
recommendation: "Make the JSON-LD valid JSON with both @context and @type.",
|
|
3650
3679
|
docsUrl: docsUrl12
|
|
@@ -3746,14 +3775,12 @@ var seoJsonLdRequiredProps = jsonldRule({
|
|
|
3746
3775
|
});
|
|
3747
3776
|
|
|
3748
3777
|
// src/rules/seo/text-metrics.ts
|
|
3749
|
-
var segmenter =
|
|
3778
|
+
var segmenter = new Intl.Segmenter();
|
|
3750
3779
|
function collapseWhitespace(s) {
|
|
3751
3780
|
return s.trim().replace(/\s+/g, " ");
|
|
3752
3781
|
}
|
|
3753
3782
|
function visibleLength(s) {
|
|
3754
|
-
|
|
3755
|
-
if (!segmenter) return [...collapsed].length;
|
|
3756
|
-
return [...segmenter.segment(collapsed)].length;
|
|
3783
|
+
return [...segmenter.segment(collapseWhitespace(s))].length;
|
|
3757
3784
|
}
|
|
3758
3785
|
|
|
3759
3786
|
// src/rules/seo/length-rule.ts
|
|
@@ -3803,6 +3830,11 @@ function lengthRule(opts) {
|
|
|
3803
3830
|
severity: "info",
|
|
3804
3831
|
detection: PASS,
|
|
3805
3832
|
route: head.route,
|
|
3833
|
+
// Same `location` the penalized branch above uses (design
|
|
3834
|
+
// 2026-08-08-pass-result-location-design.md) — without it, a `files:`-scoped
|
|
3835
|
+
// override can flip this result to PASS via its `options` but can never match
|
|
3836
|
+
// it to also apply `severity: 'off'`.
|
|
3837
|
+
location,
|
|
3806
3838
|
message: opts.label,
|
|
3807
3839
|
recommendation: recommendation12,
|
|
3808
3840
|
docsUrl: docsUrl12
|
|
@@ -3926,6 +3958,9 @@ var seoHreflang = {
|
|
|
3926
3958
|
severity: "warning",
|
|
3927
3959
|
detection: PASS,
|
|
3928
3960
|
route: head.route,
|
|
3961
|
+
// Same `location` the penalized branch above uses (design
|
|
3962
|
+
// 2026-08-08-pass-result-location-design.md).
|
|
3963
|
+
location,
|
|
3929
3964
|
message: "hreflang",
|
|
3930
3965
|
recommendation: recommendation4,
|
|
3931
3966
|
docsUrl: docsUrl4
|
|
@@ -3978,6 +4013,10 @@ var seoSingleH1 = {
|
|
|
3978
4013
|
severity: "warning",
|
|
3979
4014
|
detection: PASS,
|
|
3980
4015
|
route: route.route,
|
|
4016
|
+
// No single route-level file exists here (unlike ResolvedHead.file) — the
|
|
4017
|
+
// passing route's own <h1> stands in as its attributed file (design
|
|
4018
|
+
// 2026-08-08-pass-result-location-design.md). Only reached when h1.length === 1.
|
|
4019
|
+
location: h1[0].file,
|
|
3981
4020
|
message: "Heading hierarchy",
|
|
3982
4021
|
recommendation: recommendation5,
|
|
3983
4022
|
docsUrl: docsUrl5
|
|
@@ -4027,6 +4066,9 @@ function uniquenessRule(opts) {
|
|
|
4027
4066
|
severity: "warning",
|
|
4028
4067
|
detection: PASS,
|
|
4029
4068
|
route: e.route,
|
|
4069
|
+
// Same `location` the penalized branch above uses (design
|
|
4070
|
+
// 2026-08-08-pass-result-location-design.md).
|
|
4071
|
+
location: e.file,
|
|
4030
4072
|
message: opts.label,
|
|
4031
4073
|
recommendation: opts.recommendation,
|
|
4032
4074
|
docsUrl: docsUrl12
|
|
@@ -4100,6 +4142,11 @@ var seoHeadingLevelSkip = {
|
|
|
4100
4142
|
severity: "info",
|
|
4101
4143
|
detection: PASS,
|
|
4102
4144
|
route: route.route,
|
|
4145
|
+
// No single route-level file exists here (unlike ResolvedHead.file) — the
|
|
4146
|
+
// route's first heading stands in as its attributed file (design
|
|
4147
|
+
// 2026-08-08-pass-result-location-design.md). `route.headings.length === 0`
|
|
4148
|
+
// already continued above, so `[0]` is always defined here.
|
|
4149
|
+
location: route.headings[0].file,
|
|
4103
4150
|
message: "Heading order",
|
|
4104
4151
|
recommendation: recommendation6,
|
|
4105
4152
|
docsUrl: docsUrl6
|
|
@@ -4139,6 +4186,9 @@ function kitModuleRule(opts) {
|
|
|
4139
4186
|
severity,
|
|
4140
4187
|
detection: PASS2,
|
|
4141
4188
|
route: m.file,
|
|
4189
|
+
// Uniform PASS-result attribution (design 2026-08-08-pass-result-location-design.md):
|
|
4190
|
+
// same location a penalized result for this file would carry.
|
|
4191
|
+
location: m.file,
|
|
4142
4192
|
message: opts.label,
|
|
4143
4193
|
recommendation: opts.recommendation,
|
|
4144
4194
|
docsUrl: docsUrl12
|
|
@@ -4218,6 +4268,9 @@ function componentRule(opts) {
|
|
|
4218
4268
|
severity,
|
|
4219
4269
|
detection: PASS3,
|
|
4220
4270
|
route: c.file,
|
|
4271
|
+
// Uniform PASS-result attribution (design 2026-08-08-pass-result-location-design.md):
|
|
4272
|
+
// same location a penalized result for this file would carry.
|
|
4273
|
+
location: c.file,
|
|
4221
4274
|
message: opts.label,
|
|
4222
4275
|
recommendation: recommendation12,
|
|
4223
4276
|
docsUrl: docsUrl12
|
|
@@ -4423,6 +4476,9 @@ function emitFile(out, file, issues, suppressions) {
|
|
|
4423
4476
|
severity: "critical",
|
|
4424
4477
|
detection: PASS4,
|
|
4425
4478
|
route: file,
|
|
4479
|
+
// Uniform PASS-result attribution (design 2026-08-08-pass-result-location-design.md):
|
|
4480
|
+
// same location a penalized result for this file would carry.
|
|
4481
|
+
location: file,
|
|
4426
4482
|
message: LABEL,
|
|
4427
4483
|
recommendation: RECOMMENDATION,
|
|
4428
4484
|
docsUrl: DOCS_URL
|
|
@@ -4514,6 +4570,9 @@ function emitFile2(out, file, links, suppressions) {
|
|
|
4514
4570
|
severity: "warning",
|
|
4515
4571
|
detection: PASS5,
|
|
4516
4572
|
route: file,
|
|
4573
|
+
// Uniform PASS-result attribution (design 2026-08-08-pass-result-location-design.md):
|
|
4574
|
+
// same location a penalized result for this file would carry.
|
|
4575
|
+
location: file,
|
|
4517
4576
|
message: LABEL2,
|
|
4518
4577
|
recommendation: RECOMMENDATION2,
|
|
4519
4578
|
docsUrl: DOCS_URL2
|
|
@@ -4581,6 +4640,9 @@ function emitFile3(out, file, issues, suppressions) {
|
|
|
4581
4640
|
severity: "critical",
|
|
4582
4641
|
detection: PASS6,
|
|
4583
4642
|
route: file,
|
|
4643
|
+
// Uniform PASS-result attribution (design 2026-08-08-pass-result-location-design.md):
|
|
4644
|
+
// same location a penalized result for this file would carry.
|
|
4645
|
+
location: file,
|
|
4584
4646
|
message: LABEL3,
|
|
4585
4647
|
recommendation: RECOMMENDATION3,
|
|
4586
4648
|
docsUrl: DOCS_URL3
|
|
@@ -4859,6 +4921,11 @@ var architecturePrivateScopeImport = {
|
|
|
4859
4921
|
severity: "info",
|
|
4860
4922
|
detection: { presence: "own", value: "static" },
|
|
4861
4923
|
route: c.file,
|
|
4924
|
+
// Same location the penalized branch below uses (design
|
|
4925
|
+
// 2026-08-08-pass-result-location-design.md) — this uncaught inline PASS literal
|
|
4926
|
+
// was missed by the design spike's grep and added to its blast-radius table
|
|
4927
|
+
// afterward (maintainer ruling, same date).
|
|
4928
|
+
location: c.file,
|
|
4862
4929
|
message: "No private-scope imports",
|
|
4863
4930
|
recommendation: recommendation7,
|
|
4864
4931
|
docsUrl: docsUrl7
|
|
@@ -5037,6 +5104,8 @@ var architectureUnitEntryFile = {
|
|
|
5037
5104
|
...Object.keys(mapOption(globalOptions, "pascalCaseUnits"))
|
|
5038
5105
|
]);
|
|
5039
5106
|
const usedKeys = /* @__PURE__ */ new Set();
|
|
5107
|
+
const examinedCounts = {};
|
|
5108
|
+
for (const key of globalKeys) examinedCounts[key] = 0;
|
|
5040
5109
|
const excludedDirs = [];
|
|
5041
5110
|
const matchedSurviving = /* @__PURE__ */ new Set();
|
|
5042
5111
|
for (const dir of [...dirs].sort()) {
|
|
@@ -5064,6 +5133,10 @@ var architectureUnitEntryFile = {
|
|
|
5064
5133
|
ext = byCasing.best === void 0 ? void 0 : pascalUnits[byCasing.best];
|
|
5065
5134
|
}
|
|
5066
5135
|
if (ext === void 0) continue;
|
|
5136
|
+
const winningKey = viaUnits ? byPath.best : byCasing.best;
|
|
5137
|
+
if (winningKey !== void 0 && globalKeys.has(winningKey)) {
|
|
5138
|
+
examinedCounts[winningKey] = (examinedCounts[winningKey] ?? 0) + 1;
|
|
5139
|
+
}
|
|
5067
5140
|
const expected = `${dir}/${baseName(dir)}${ext}`;
|
|
5068
5141
|
if (fileSet.has(expected)) {
|
|
5069
5142
|
out.push({
|
|
@@ -5113,6 +5186,7 @@ var architectureUnitEntryFile = {
|
|
|
5113
5186
|
docsUrl: docsUrl8
|
|
5114
5187
|
});
|
|
5115
5188
|
}
|
|
5189
|
+
ctx.recordExamined?.(examinedCounts);
|
|
5116
5190
|
return out;
|
|
5117
5191
|
}
|
|
5118
5192
|
};
|
|
@@ -5186,6 +5260,8 @@ var architectureDirectoryNaming = {
|
|
|
5186
5260
|
const globalOptions = resolveRuleOptions(ID5, OPTIONS4, ctx.config);
|
|
5187
5261
|
const globalMap = mapOption(globalOptions, "directories");
|
|
5188
5262
|
const globalKeys = new Set(Object.keys(globalMap));
|
|
5263
|
+
const examinedCounts = {};
|
|
5264
|
+
for (const key of globalKeys) examinedCounts[key] = 0;
|
|
5189
5265
|
const usedKeys = /* @__PURE__ */ new Set();
|
|
5190
5266
|
const excludedDirs = [];
|
|
5191
5267
|
for (const dir of [...dirs].sort()) {
|
|
@@ -5203,6 +5279,7 @@ var architectureDirectoryNaming = {
|
|
|
5203
5279
|
if (m.best === void 0) continue;
|
|
5204
5280
|
const decoded = decodeSegment(baseName(dir));
|
|
5205
5281
|
if (decoded === void 0) continue;
|
|
5282
|
+
if (globalKeys.has(m.best)) examinedCounts[m.best] = (examinedCounts[m.best] ?? 0) + 1;
|
|
5206
5283
|
const allowed = casingsOf(declared[m.best]).known;
|
|
5207
5284
|
if (satisfiesCasing(decoded, allowed)) continue;
|
|
5208
5285
|
const at = reportAt(dir, files);
|
|
@@ -5250,6 +5327,7 @@ var architectureDirectoryNaming = {
|
|
|
5250
5327
|
docsUrl: docsUrl9
|
|
5251
5328
|
});
|
|
5252
5329
|
}
|
|
5330
|
+
ctx.recordExamined?.(examinedCounts);
|
|
5253
5331
|
return out;
|
|
5254
5332
|
}
|
|
5255
5333
|
};
|
|
@@ -5261,6 +5339,7 @@ var recommendation10 = "Use one of the names this location declares, or add the
|
|
|
5261
5339
|
var OPTIONS5 = {
|
|
5262
5340
|
scopes: { kind: "string-map", default: {} },
|
|
5263
5341
|
unitScopes: { kind: "string-map", default: {} },
|
|
5342
|
+
anyCaseUnitScopes: { kind: "string-map", default: {} },
|
|
5264
5343
|
exclude: { kind: "string-list", default: [] }
|
|
5265
5344
|
};
|
|
5266
5345
|
function stem(file) {
|
|
@@ -5276,6 +5355,7 @@ function isAnyCaseUnitDir(dir, filesIn) {
|
|
|
5276
5355
|
const own = filesIn.get(dir);
|
|
5277
5356
|
return own !== void 0 && own.some((f) => stem(f) === name);
|
|
5278
5357
|
}
|
|
5358
|
+
var PRIORITY = { scopes: 0, unitScopes: 1, anyCaseUnitScopes: 2 };
|
|
5279
5359
|
var architectureReservedDirectoryNames = {
|
|
5280
5360
|
id: ID6,
|
|
5281
5361
|
title: "Reserved directory names",
|
|
@@ -5307,26 +5387,45 @@ var architectureReservedDirectoryNames = {
|
|
|
5307
5387
|
const globalOptions = resolveRuleOptions(ID6, OPTIONS5, ctx.config);
|
|
5308
5388
|
const globalScopes = mapOption(globalOptions, "scopes");
|
|
5309
5389
|
const globalUnits = mapOption(globalOptions, "unitScopes");
|
|
5310
|
-
const
|
|
5390
|
+
const globalAnyUnits = mapOption(globalOptions, "anyCaseUnitScopes");
|
|
5391
|
+
const globalKeys = /* @__PURE__ */ new Set([
|
|
5392
|
+
...Object.keys(globalScopes),
|
|
5393
|
+
...Object.keys(globalUnits),
|
|
5394
|
+
...Object.keys(globalAnyUnits)
|
|
5395
|
+
]);
|
|
5311
5396
|
const usedKeys = /* @__PURE__ */ new Set();
|
|
5312
5397
|
const excludedDirs = [];
|
|
5313
5398
|
const nonUnitDirs = [];
|
|
5314
|
-
const
|
|
5315
|
-
const
|
|
5399
|
+
const nonAnyUnitDirs = [];
|
|
5400
|
+
const collisions = /* @__PURE__ */ new Map();
|
|
5401
|
+
const collisionMessage = (losers) => {
|
|
5402
|
+
const maps = ["scopes", ...losers];
|
|
5403
|
+
const list = maps.length === 2 ? `both ${maps[0]} and ${maps[1]}` : maps.join(", ");
|
|
5404
|
+
return `declared in ${list}, so the scopes entry wins wherever ${losers.length > 1 ? "they" : "both"} apply`;
|
|
5405
|
+
};
|
|
5406
|
+
const examinedCounts = {};
|
|
5407
|
+
for (const key of globalKeys) examinedCounts[key] = 0;
|
|
5408
|
+
const noteCollisions = (scopesMap, unitMap, anyUnitMap) => {
|
|
5316
5409
|
for (const key of Object.keys(scopesMap)) {
|
|
5317
|
-
if (!Object.hasOwn(unitMap, key)) continue;
|
|
5318
5410
|
if (namesOf(scopesMap[key]).length === 0) continue;
|
|
5319
|
-
|
|
5320
|
-
|
|
5411
|
+
const losers = [];
|
|
5412
|
+
if (Object.hasOwn(unitMap, key) && namesOf(unitMap[key]).length > 0) losers.push("unitScopes");
|
|
5413
|
+
if (Object.hasOwn(anyUnitMap, key) && namesOf(anyUnitMap[key]).length > 0) {
|
|
5414
|
+
losers.push("anyCaseUnitScopes");
|
|
5415
|
+
}
|
|
5416
|
+
if (losers.length > 0) collisions.set(key, collisionMessage(losers));
|
|
5321
5417
|
}
|
|
5322
5418
|
};
|
|
5323
|
-
noteCollisions(globalScopes, globalUnits);
|
|
5419
|
+
noteCollisions(globalScopes, globalUnits, globalAnyUnits);
|
|
5324
5420
|
for (const dir of [...dirs].sort()) {
|
|
5325
5421
|
const o = resolveRuleOptions(ID6, OPTIONS5, ctx.config, { route: dir, file: dir }, compiledOverrides);
|
|
5326
5422
|
const scopes = mapOption(o, "scopes");
|
|
5327
5423
|
const unitScopes = mapOption(o, "unitScopes");
|
|
5328
|
-
|
|
5329
|
-
|
|
5424
|
+
const anyCaseUnitScopes = mapOption(o, "anyCaseUnitScopes");
|
|
5425
|
+
if (Object.keys(scopes).length === 0 && Object.keys(unitScopes).length === 0 && Object.keys(anyCaseUnitScopes).length === 0) {
|
|
5426
|
+
continue;
|
|
5427
|
+
}
|
|
5428
|
+
noteCollisions(scopes, unitScopes, anyCaseUnitScopes);
|
|
5330
5429
|
const excluded = compile(listOption(o, "exclude"));
|
|
5331
5430
|
if (isExcluded(dir, ancestorDirs2(dir), excluded)) {
|
|
5332
5431
|
excludedDirs.push(dir);
|
|
@@ -5334,22 +5433,46 @@ var architectureReservedDirectoryNames = {
|
|
|
5334
5433
|
}
|
|
5335
5434
|
const liveScopes = Object.keys(scopes).filter((k) => namesOf(scopes[k]).length > 0);
|
|
5336
5435
|
const isUnit = isUnitDir(dir, filesIn);
|
|
5436
|
+
const isAnyUnit = isAnyCaseUnitDir(dir, filesIn);
|
|
5337
5437
|
const liveUnits = isUnit ? Object.keys(unitScopes).filter((k) => namesOf(unitScopes[k]).length > 0) : [];
|
|
5438
|
+
const liveAnyUnits = isAnyUnit ? Object.keys(anyCaseUnitScopes).filter((k) => namesOf(anyCaseUnitScopes[k]).length > 0) : [];
|
|
5338
5439
|
if (!isUnit) nonUnitDirs.push(dir);
|
|
5440
|
+
if (!isAnyUnit) nonAnyUnitDirs.push(dir);
|
|
5339
5441
|
const byPosition = matchKeys(dir, compile(liveScopes, true));
|
|
5340
5442
|
const byUnit = matchKeys(dir, compile(liveUnits, true));
|
|
5443
|
+
const byAnyUnit = matchKeys(dir, compile(liveAnyUnits, true));
|
|
5341
5444
|
for (const k of byPosition.matched) if (globalKeys.has(k)) usedKeys.add(k);
|
|
5342
5445
|
for (const k of byUnit.matched) if (globalKeys.has(k)) usedKeys.add(k);
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5446
|
+
for (const k of byAnyUnit.matched) if (globalKeys.has(k)) usedKeys.add(k);
|
|
5447
|
+
const candidates = [];
|
|
5448
|
+
if (byPosition.best !== void 0) {
|
|
5449
|
+
candidates.push({ kind: "scopes", best: byPosition.best, names: namesOf(scopes[byPosition.best]) });
|
|
5450
|
+
}
|
|
5451
|
+
if (byUnit.best !== void 0) {
|
|
5452
|
+
candidates.push({
|
|
5453
|
+
kind: "unitScopes",
|
|
5454
|
+
best: byUnit.best,
|
|
5455
|
+
names: namesOf(unitScopes[byUnit.best])
|
|
5456
|
+
});
|
|
5457
|
+
}
|
|
5458
|
+
if (byAnyUnit.best !== void 0) {
|
|
5459
|
+
candidates.push({
|
|
5460
|
+
kind: "anyCaseUnitScopes",
|
|
5461
|
+
best: byAnyUnit.best,
|
|
5462
|
+
names: namesOf(anyCaseUnitScopes[byAnyUnit.best])
|
|
5463
|
+
});
|
|
5464
|
+
}
|
|
5465
|
+
let winner;
|
|
5466
|
+
for (const c of candidates) {
|
|
5467
|
+
if (winner === void 0 || moreSpecificGlob(c.best, winner.best)) {
|
|
5468
|
+
winner = c;
|
|
5469
|
+
} else if (!moreSpecificGlob(winner.best, c.best) && PRIORITY[c.kind] < PRIORITY[winner.kind]) {
|
|
5470
|
+
winner = c;
|
|
5471
|
+
}
|
|
5350
5472
|
}
|
|
5351
|
-
if (
|
|
5352
|
-
|
|
5473
|
+
if (winner === void 0) continue;
|
|
5474
|
+
if (globalKeys.has(winner.best)) examinedCounts[winner.best] = (examinedCounts[winner.best] ?? 0) + 1;
|
|
5475
|
+
const allowed = new Set(winner.names);
|
|
5353
5476
|
for (const child of kids.get(dir) ?? []) {
|
|
5354
5477
|
if (allowed.has(baseName(child))) continue;
|
|
5355
5478
|
if (isExcluded(child, ancestorDirs2(child), excluded)) continue;
|
|
@@ -5370,7 +5493,7 @@ var architectureReservedDirectoryNames = {
|
|
|
5370
5493
|
detection: { presence: "none", value: "absent" },
|
|
5371
5494
|
route: child,
|
|
5372
5495
|
location: at,
|
|
5373
|
-
message: `${child} is not one of the names declared here: ${
|
|
5496
|
+
message: `${child} is not one of the names declared here: ${winner.names.join(", ")}.`,
|
|
5374
5497
|
recommendation: recommendation10,
|
|
5375
5498
|
docsUrl: docsUrl10,
|
|
5376
5499
|
fix: {
|
|
@@ -5380,19 +5503,24 @@ var architectureReservedDirectoryNames = {
|
|
|
5380
5503
|
}
|
|
5381
5504
|
}
|
|
5382
5505
|
const notes = /* @__PURE__ */ new Map();
|
|
5383
|
-
for (const key of collisions)
|
|
5384
|
-
notes.set(key, "declared in both scopes and unitScopes, so the scopes entry wins wherever both apply");
|
|
5385
|
-
}
|
|
5506
|
+
for (const [key, message] of collisions) notes.set(key, message);
|
|
5386
5507
|
for (const key of globalKeys) {
|
|
5387
5508
|
if (notes.has(key)) continue;
|
|
5388
5509
|
const scopesEmpty = Object.hasOwn(globalScopes, key) && namesOf(globalScopes[key]).length === 0;
|
|
5389
5510
|
const unitsEmpty = Object.hasOwn(globalUnits, key) && namesOf(globalUnits[key]).length === 0;
|
|
5390
|
-
|
|
5511
|
+
const anyUnitsEmpty = Object.hasOwn(globalAnyUnits, key) && namesOf(globalAnyUnits[key]).length === 0;
|
|
5512
|
+
if (scopesEmpty || unitsEmpty || anyUnitsEmpty) {
|
|
5391
5513
|
notes.set(key, "names no directory name at all");
|
|
5392
5514
|
}
|
|
5393
5515
|
}
|
|
5394
5516
|
const unused = [...globalKeys].filter((k) => !notes.has(k) && !usedKeys.has(k));
|
|
5395
|
-
const
|
|
5517
|
+
const anyUnitOnly = unused.filter((k) => Object.hasOwn(globalAnyUnits, k) && !Object.hasOwn(globalScopes, k));
|
|
5518
|
+
for (const key of keysMatchingAny(anyUnitOnly, nonAnyUnitDirs, compile)) {
|
|
5519
|
+
notes.set(key, "matched directories but never a unit of either case");
|
|
5520
|
+
}
|
|
5521
|
+
const unitOnly = unused.filter(
|
|
5522
|
+
(k) => Object.hasOwn(globalUnits, k) && !Object.hasOwn(globalScopes, k) && !notes.has(k)
|
|
5523
|
+
);
|
|
5396
5524
|
for (const key of keysMatchingAny(unitOnly, nonUnitDirs, compile)) {
|
|
5397
5525
|
notes.set(key, "matched directories but never a unit");
|
|
5398
5526
|
}
|
|
@@ -5416,6 +5544,7 @@ var architectureReservedDirectoryNames = {
|
|
|
5416
5544
|
docsUrl: docsUrl10
|
|
5417
5545
|
});
|
|
5418
5546
|
}
|
|
5547
|
+
ctx.recordExamined?.(examinedCounts);
|
|
5419
5548
|
return out;
|
|
5420
5549
|
}
|
|
5421
5550
|
};
|
|
@@ -6618,6 +6747,7 @@ body{background:var(--ground);color:var(--ink);font-family:var(--sans);line-heig
|
|
|
6618
6747
|
.dv-cat-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px}
|
|
6619
6748
|
.dv-bar{height:7px;border-radius:999px;background:var(--line);overflow:hidden}
|
|
6620
6749
|
.dv-bar>i{display:block;height:100%;border-radius:999px}
|
|
6750
|
+
.dv-cat-reach{font-size:11.5px;color:var(--muted);margin-top:5px}
|
|
6621
6751
|
.dv-filters{display:flex;gap:8px;flex-wrap:wrap;margin:16px 0}
|
|
6622
6752
|
.dv-chip{font:inherit;font-size:12.5px;font-weight:600;cursor:pointer;background:var(--panel);border:1px solid var(--line-strong);color:var(--muted);padding:5px 12px;border-radius:999px}
|
|
6623
6753
|
.dv-chip[aria-pressed="true"]{background:var(--active-bg);border-color:var(--active-bg);color:var(--active-ink)}
|
|
@@ -7090,12 +7220,21 @@ var APP_SCRIPT = `
|
|
|
7090
7220
|
var band = scoreBand(c.score);
|
|
7091
7221
|
var weight = s.report.weights[cat];
|
|
7092
7222
|
var name = cat === 'seo' ? 'SEO' : cat.charAt(0).toUpperCase() + cat.slice(1);
|
|
7223
|
+
// keys/affectedKeys are absent on hand-built snapshots (older fixtures, tests) \u2014
|
|
7224
|
+
// render nothing rather than "undefined of undefined". 0 affected of N keys is still
|
|
7225
|
+
// rendered: on a real project that's the signal a thin score can't give, that the
|
|
7226
|
+
// category is clean project-wide and not just on the one key it happened to look at
|
|
7227
|
+
// (design: 2026-08-05-score-floor-and-reach-design.md).
|
|
7228
|
+
var reach = typeof c.keys === 'number' && c.keys > 0
|
|
7229
|
+
? h('div', { class: 'dv-cat-reach', text: c.affectedKeys + ' of ' + c.keys + ' keys affected' }, [])
|
|
7230
|
+
: null;
|
|
7093
7231
|
return h('div', { class: 'dv-cat' }, [
|
|
7094
7232
|
h('div', { class: 'dv-cat-top' }, [
|
|
7095
7233
|
h('span', { text: name + (weight !== undefined ? ' (weight ' + weight + ')' : '') }, []),
|
|
7096
7234
|
h('span', { style: 'color:' + BAND_COLOR[band], text: String(c.score) }, [])
|
|
7097
7235
|
]),
|
|
7098
|
-
h('div', { class: 'dv-bar' }, [h('i', { style: 'width:' + c.score + '%;background:' + BAND_COLOR[band] }, [])])
|
|
7236
|
+
h('div', { class: 'dv-bar' }, [h('i', { style: 'width:' + c.score + '%;background:' + BAND_COLOR[band] }, [])]),
|
|
7237
|
+
reach
|
|
7099
7238
|
]);
|
|
7100
7239
|
});
|
|
7101
7240
|
var chips = renderFilterChips(s.report.categories);
|
|
@@ -7202,13 +7341,10 @@ function renderAppShell(snapshot) {
|
|
|
7202
7341
|
const title = snapshot.live ? "svelte-vitals dashboard" : "svelte-vitals report";
|
|
7203
7342
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${title}</title><style>${APP_STYLE}</style></head><body><div class="dv-app" id="dv-app"><header class="dv-topbar" id="dv-topbar"></header><nav class="dv-sidebar" id="dv-sidebar"></nav><main class="dv-detail" id="dv-detail"></main></div><script type="application/json" id="svelte-vitals-data">${embedJson(safe)}</script><script>${APP_SCRIPT}</script></body></html>`;
|
|
7204
7343
|
}
|
|
7205
|
-
function buildHtmlDocument(report, meta
|
|
7206
|
-
const badges = Object.fromEntries(
|
|
7207
|
-
Object.entries(opts?.routeBadges ?? {}).filter(([, b]) => b === "measured" || b === "static")
|
|
7208
|
-
);
|
|
7344
|
+
function buildHtmlDocument(report, meta) {
|
|
7209
7345
|
return renderAppShell({
|
|
7210
7346
|
report,
|
|
7211
|
-
badges,
|
|
7347
|
+
badges: {},
|
|
7212
7348
|
analyzing: false,
|
|
7213
7349
|
sequence: 0,
|
|
7214
7350
|
live: false,
|