@svelte-vitals/core 0.38.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 +12 -4
- package/dist/index.js +171 -26
- 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
|
package/dist/index.js
CHANGED
|
@@ -826,7 +826,7 @@ function countProps(program) {
|
|
|
826
826
|
if (n.type !== "VariableDeclarator" || !n.init || !isPropsCall(unwrapTs(n.init))) return;
|
|
827
827
|
seen++;
|
|
828
828
|
const props = n.id?.type === "ObjectPattern" ? n.id.properties : void 0;
|
|
829
|
-
if (!Array.isArray(props)
|
|
829
|
+
if (!Array.isArray(props)) {
|
|
830
830
|
uncountable = true;
|
|
831
831
|
return;
|
|
832
832
|
}
|
|
@@ -2737,6 +2737,13 @@ function imageRule(opts) {
|
|
|
2737
2737
|
severity: opts.severity,
|
|
2738
2738
|
detection: { presence: "own", value: "static" },
|
|
2739
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,
|
|
2740
2747
|
message: opts.label,
|
|
2741
2748
|
recommendation: opts.recommendation,
|
|
2742
2749
|
docsUrl: docsUrl12
|
|
@@ -2836,6 +2843,13 @@ function linkRule(opts) {
|
|
|
2836
2843
|
severity: opts.severity,
|
|
2837
2844
|
detection: { presence: "own", value: "static" },
|
|
2838
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,
|
|
2839
2853
|
message: opts.label,
|
|
2840
2854
|
recommendation: opts.recommendation,
|
|
2841
2855
|
docsUrl: docsUrl12
|
|
@@ -2938,6 +2952,11 @@ var performanceLcpImage = {
|
|
|
2938
2952
|
severity: "warning",
|
|
2939
2953
|
detection: { presence: "own", value: "static" },
|
|
2940
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,
|
|
2941
2960
|
message: "LCP image eager loading",
|
|
2942
2961
|
recommendation,
|
|
2943
2962
|
docsUrl
|
|
@@ -2992,6 +3011,10 @@ var performanceRenderBlockingScript = {
|
|
|
2992
3011
|
severity: "warning",
|
|
2993
3012
|
detection: { presence: "own", value: "static" },
|
|
2994
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,
|
|
2995
3018
|
message: "No render-blocking scripts",
|
|
2996
3019
|
recommendation: recommendation2,
|
|
2997
3020
|
docsUrl: docsUrl2
|
|
@@ -3247,6 +3270,10 @@ var performancePreconnect = {
|
|
|
3247
3270
|
severity: "info",
|
|
3248
3271
|
detection: { presence: "own", value: "static" },
|
|
3249
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,
|
|
3250
3277
|
message: "Third-party origins are preconnected",
|
|
3251
3278
|
recommendation: recommendation3,
|
|
3252
3279
|
docsUrl: docsUrl3
|
|
@@ -3588,6 +3615,9 @@ function jsonldRule(opts) {
|
|
|
3588
3615
|
severity: opts.severity,
|
|
3589
3616
|
detection: PASS,
|
|
3590
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,
|
|
3591
3621
|
message: opts.label,
|
|
3592
3622
|
recommendation: opts.recommendation,
|
|
3593
3623
|
docsUrl: docsUrl12
|
|
@@ -3641,6 +3671,9 @@ var seoJsonLdValidity = {
|
|
|
3641
3671
|
severity: "warning",
|
|
3642
3672
|
detection: PASS,
|
|
3643
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,
|
|
3644
3677
|
message: "JSON-LD validity",
|
|
3645
3678
|
recommendation: "Make the JSON-LD valid JSON with both @context and @type.",
|
|
3646
3679
|
docsUrl: docsUrl12
|
|
@@ -3797,6 +3830,11 @@ function lengthRule(opts) {
|
|
|
3797
3830
|
severity: "info",
|
|
3798
3831
|
detection: PASS,
|
|
3799
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,
|
|
3800
3838
|
message: opts.label,
|
|
3801
3839
|
recommendation: recommendation12,
|
|
3802
3840
|
docsUrl: docsUrl12
|
|
@@ -3920,6 +3958,9 @@ var seoHreflang = {
|
|
|
3920
3958
|
severity: "warning",
|
|
3921
3959
|
detection: PASS,
|
|
3922
3960
|
route: head.route,
|
|
3961
|
+
// Same `location` the penalized branch above uses (design
|
|
3962
|
+
// 2026-08-08-pass-result-location-design.md).
|
|
3963
|
+
location,
|
|
3923
3964
|
message: "hreflang",
|
|
3924
3965
|
recommendation: recommendation4,
|
|
3925
3966
|
docsUrl: docsUrl4
|
|
@@ -3972,6 +4013,10 @@ var seoSingleH1 = {
|
|
|
3972
4013
|
severity: "warning",
|
|
3973
4014
|
detection: PASS,
|
|
3974
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,
|
|
3975
4020
|
message: "Heading hierarchy",
|
|
3976
4021
|
recommendation: recommendation5,
|
|
3977
4022
|
docsUrl: docsUrl5
|
|
@@ -4021,6 +4066,9 @@ function uniquenessRule(opts) {
|
|
|
4021
4066
|
severity: "warning",
|
|
4022
4067
|
detection: PASS,
|
|
4023
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,
|
|
4024
4072
|
message: opts.label,
|
|
4025
4073
|
recommendation: opts.recommendation,
|
|
4026
4074
|
docsUrl: docsUrl12
|
|
@@ -4094,6 +4142,11 @@ var seoHeadingLevelSkip = {
|
|
|
4094
4142
|
severity: "info",
|
|
4095
4143
|
detection: PASS,
|
|
4096
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,
|
|
4097
4150
|
message: "Heading order",
|
|
4098
4151
|
recommendation: recommendation6,
|
|
4099
4152
|
docsUrl: docsUrl6
|
|
@@ -4133,6 +4186,9 @@ function kitModuleRule(opts) {
|
|
|
4133
4186
|
severity,
|
|
4134
4187
|
detection: PASS2,
|
|
4135
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,
|
|
4136
4192
|
message: opts.label,
|
|
4137
4193
|
recommendation: opts.recommendation,
|
|
4138
4194
|
docsUrl: docsUrl12
|
|
@@ -4212,6 +4268,9 @@ function componentRule(opts) {
|
|
|
4212
4268
|
severity,
|
|
4213
4269
|
detection: PASS3,
|
|
4214
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,
|
|
4215
4274
|
message: opts.label,
|
|
4216
4275
|
recommendation: recommendation12,
|
|
4217
4276
|
docsUrl: docsUrl12
|
|
@@ -4417,6 +4476,9 @@ function emitFile(out, file, issues, suppressions) {
|
|
|
4417
4476
|
severity: "critical",
|
|
4418
4477
|
detection: PASS4,
|
|
4419
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,
|
|
4420
4482
|
message: LABEL,
|
|
4421
4483
|
recommendation: RECOMMENDATION,
|
|
4422
4484
|
docsUrl: DOCS_URL
|
|
@@ -4508,6 +4570,9 @@ function emitFile2(out, file, links, suppressions) {
|
|
|
4508
4570
|
severity: "warning",
|
|
4509
4571
|
detection: PASS5,
|
|
4510
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,
|
|
4511
4576
|
message: LABEL2,
|
|
4512
4577
|
recommendation: RECOMMENDATION2,
|
|
4513
4578
|
docsUrl: DOCS_URL2
|
|
@@ -4575,6 +4640,9 @@ function emitFile3(out, file, issues, suppressions) {
|
|
|
4575
4640
|
severity: "critical",
|
|
4576
4641
|
detection: PASS6,
|
|
4577
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,
|
|
4578
4646
|
message: LABEL3,
|
|
4579
4647
|
recommendation: RECOMMENDATION3,
|
|
4580
4648
|
docsUrl: DOCS_URL3
|
|
@@ -4853,6 +4921,11 @@ var architecturePrivateScopeImport = {
|
|
|
4853
4921
|
severity: "info",
|
|
4854
4922
|
detection: { presence: "own", value: "static" },
|
|
4855
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,
|
|
4856
4929
|
message: "No private-scope imports",
|
|
4857
4930
|
recommendation: recommendation7,
|
|
4858
4931
|
docsUrl: docsUrl7
|
|
@@ -5031,6 +5104,8 @@ var architectureUnitEntryFile = {
|
|
|
5031
5104
|
...Object.keys(mapOption(globalOptions, "pascalCaseUnits"))
|
|
5032
5105
|
]);
|
|
5033
5106
|
const usedKeys = /* @__PURE__ */ new Set();
|
|
5107
|
+
const examinedCounts = {};
|
|
5108
|
+
for (const key of globalKeys) examinedCounts[key] = 0;
|
|
5034
5109
|
const excludedDirs = [];
|
|
5035
5110
|
const matchedSurviving = /* @__PURE__ */ new Set();
|
|
5036
5111
|
for (const dir of [...dirs].sort()) {
|
|
@@ -5058,6 +5133,10 @@ var architectureUnitEntryFile = {
|
|
|
5058
5133
|
ext = byCasing.best === void 0 ? void 0 : pascalUnits[byCasing.best];
|
|
5059
5134
|
}
|
|
5060
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
|
+
}
|
|
5061
5140
|
const expected = `${dir}/${baseName(dir)}${ext}`;
|
|
5062
5141
|
if (fileSet.has(expected)) {
|
|
5063
5142
|
out.push({
|
|
@@ -5107,6 +5186,7 @@ var architectureUnitEntryFile = {
|
|
|
5107
5186
|
docsUrl: docsUrl8
|
|
5108
5187
|
});
|
|
5109
5188
|
}
|
|
5189
|
+
ctx.recordExamined?.(examinedCounts);
|
|
5110
5190
|
return out;
|
|
5111
5191
|
}
|
|
5112
5192
|
};
|
|
@@ -5180,6 +5260,8 @@ var architectureDirectoryNaming = {
|
|
|
5180
5260
|
const globalOptions = resolveRuleOptions(ID5, OPTIONS4, ctx.config);
|
|
5181
5261
|
const globalMap = mapOption(globalOptions, "directories");
|
|
5182
5262
|
const globalKeys = new Set(Object.keys(globalMap));
|
|
5263
|
+
const examinedCounts = {};
|
|
5264
|
+
for (const key of globalKeys) examinedCounts[key] = 0;
|
|
5183
5265
|
const usedKeys = /* @__PURE__ */ new Set();
|
|
5184
5266
|
const excludedDirs = [];
|
|
5185
5267
|
for (const dir of [...dirs].sort()) {
|
|
@@ -5197,6 +5279,7 @@ var architectureDirectoryNaming = {
|
|
|
5197
5279
|
if (m.best === void 0) continue;
|
|
5198
5280
|
const decoded = decodeSegment(baseName(dir));
|
|
5199
5281
|
if (decoded === void 0) continue;
|
|
5282
|
+
if (globalKeys.has(m.best)) examinedCounts[m.best] = (examinedCounts[m.best] ?? 0) + 1;
|
|
5200
5283
|
const allowed = casingsOf(declared[m.best]).known;
|
|
5201
5284
|
if (satisfiesCasing(decoded, allowed)) continue;
|
|
5202
5285
|
const at = reportAt(dir, files);
|
|
@@ -5244,6 +5327,7 @@ var architectureDirectoryNaming = {
|
|
|
5244
5327
|
docsUrl: docsUrl9
|
|
5245
5328
|
});
|
|
5246
5329
|
}
|
|
5330
|
+
ctx.recordExamined?.(examinedCounts);
|
|
5247
5331
|
return out;
|
|
5248
5332
|
}
|
|
5249
5333
|
};
|
|
@@ -5255,6 +5339,7 @@ var recommendation10 = "Use one of the names this location declares, or add the
|
|
|
5255
5339
|
var OPTIONS5 = {
|
|
5256
5340
|
scopes: { kind: "string-map", default: {} },
|
|
5257
5341
|
unitScopes: { kind: "string-map", default: {} },
|
|
5342
|
+
anyCaseUnitScopes: { kind: "string-map", default: {} },
|
|
5258
5343
|
exclude: { kind: "string-list", default: [] }
|
|
5259
5344
|
};
|
|
5260
5345
|
function stem(file) {
|
|
@@ -5270,6 +5355,7 @@ function isAnyCaseUnitDir(dir, filesIn) {
|
|
|
5270
5355
|
const own = filesIn.get(dir);
|
|
5271
5356
|
return own !== void 0 && own.some((f) => stem(f) === name);
|
|
5272
5357
|
}
|
|
5358
|
+
var PRIORITY = { scopes: 0, unitScopes: 1, anyCaseUnitScopes: 2 };
|
|
5273
5359
|
var architectureReservedDirectoryNames = {
|
|
5274
5360
|
id: ID6,
|
|
5275
5361
|
title: "Reserved directory names",
|
|
@@ -5301,26 +5387,45 @@ var architectureReservedDirectoryNames = {
|
|
|
5301
5387
|
const globalOptions = resolveRuleOptions(ID6, OPTIONS5, ctx.config);
|
|
5302
5388
|
const globalScopes = mapOption(globalOptions, "scopes");
|
|
5303
5389
|
const globalUnits = mapOption(globalOptions, "unitScopes");
|
|
5304
|
-
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
|
+
]);
|
|
5305
5396
|
const usedKeys = /* @__PURE__ */ new Set();
|
|
5306
5397
|
const excludedDirs = [];
|
|
5307
5398
|
const nonUnitDirs = [];
|
|
5308
|
-
const
|
|
5309
|
-
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) => {
|
|
5310
5409
|
for (const key of Object.keys(scopesMap)) {
|
|
5311
|
-
if (!Object.hasOwn(unitMap, key)) continue;
|
|
5312
5410
|
if (namesOf(scopesMap[key]).length === 0) continue;
|
|
5313
|
-
|
|
5314
|
-
|
|
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));
|
|
5315
5417
|
}
|
|
5316
5418
|
};
|
|
5317
|
-
noteCollisions(globalScopes, globalUnits);
|
|
5419
|
+
noteCollisions(globalScopes, globalUnits, globalAnyUnits);
|
|
5318
5420
|
for (const dir of [...dirs].sort()) {
|
|
5319
5421
|
const o = resolveRuleOptions(ID6, OPTIONS5, ctx.config, { route: dir, file: dir }, compiledOverrides);
|
|
5320
5422
|
const scopes = mapOption(o, "scopes");
|
|
5321
5423
|
const unitScopes = mapOption(o, "unitScopes");
|
|
5322
|
-
|
|
5323
|
-
|
|
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);
|
|
5324
5429
|
const excluded = compile(listOption(o, "exclude"));
|
|
5325
5430
|
if (isExcluded(dir, ancestorDirs2(dir), excluded)) {
|
|
5326
5431
|
excludedDirs.push(dir);
|
|
@@ -5328,22 +5433,46 @@ var architectureReservedDirectoryNames = {
|
|
|
5328
5433
|
}
|
|
5329
5434
|
const liveScopes = Object.keys(scopes).filter((k) => namesOf(scopes[k]).length > 0);
|
|
5330
5435
|
const isUnit = isUnitDir(dir, filesIn);
|
|
5436
|
+
const isAnyUnit = isAnyCaseUnitDir(dir, filesIn);
|
|
5331
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) : [];
|
|
5332
5439
|
if (!isUnit) nonUnitDirs.push(dir);
|
|
5440
|
+
if (!isAnyUnit) nonAnyUnitDirs.push(dir);
|
|
5333
5441
|
const byPosition = matchKeys(dir, compile(liveScopes, true));
|
|
5334
5442
|
const byUnit = matchKeys(dir, compile(liveUnits, true));
|
|
5443
|
+
const byAnyUnit = matchKeys(dir, compile(liveAnyUnits, true));
|
|
5335
5444
|
for (const k of byPosition.matched) if (globalKeys.has(k)) usedKeys.add(k);
|
|
5336
5445
|
for (const k of byUnit.matched) if (globalKeys.has(k)) usedKeys.add(k);
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
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
|
+
}
|
|
5344
5472
|
}
|
|
5345
|
-
if (
|
|
5346
|
-
|
|
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);
|
|
5347
5476
|
for (const child of kids.get(dir) ?? []) {
|
|
5348
5477
|
if (allowed.has(baseName(child))) continue;
|
|
5349
5478
|
if (isExcluded(child, ancestorDirs2(child), excluded)) continue;
|
|
@@ -5364,7 +5493,7 @@ var architectureReservedDirectoryNames = {
|
|
|
5364
5493
|
detection: { presence: "none", value: "absent" },
|
|
5365
5494
|
route: child,
|
|
5366
5495
|
location: at,
|
|
5367
|
-
message: `${child} is not one of the names declared here: ${
|
|
5496
|
+
message: `${child} is not one of the names declared here: ${winner.names.join(", ")}.`,
|
|
5368
5497
|
recommendation: recommendation10,
|
|
5369
5498
|
docsUrl: docsUrl10,
|
|
5370
5499
|
fix: {
|
|
@@ -5374,19 +5503,24 @@ var architectureReservedDirectoryNames = {
|
|
|
5374
5503
|
}
|
|
5375
5504
|
}
|
|
5376
5505
|
const notes = /* @__PURE__ */ new Map();
|
|
5377
|
-
for (const key of collisions)
|
|
5378
|
-
notes.set(key, "declared in both scopes and unitScopes, so the scopes entry wins wherever both apply");
|
|
5379
|
-
}
|
|
5506
|
+
for (const [key, message] of collisions) notes.set(key, message);
|
|
5380
5507
|
for (const key of globalKeys) {
|
|
5381
5508
|
if (notes.has(key)) continue;
|
|
5382
5509
|
const scopesEmpty = Object.hasOwn(globalScopes, key) && namesOf(globalScopes[key]).length === 0;
|
|
5383
5510
|
const unitsEmpty = Object.hasOwn(globalUnits, key) && namesOf(globalUnits[key]).length === 0;
|
|
5384
|
-
|
|
5511
|
+
const anyUnitsEmpty = Object.hasOwn(globalAnyUnits, key) && namesOf(globalAnyUnits[key]).length === 0;
|
|
5512
|
+
if (scopesEmpty || unitsEmpty || anyUnitsEmpty) {
|
|
5385
5513
|
notes.set(key, "names no directory name at all");
|
|
5386
5514
|
}
|
|
5387
5515
|
}
|
|
5388
5516
|
const unused = [...globalKeys].filter((k) => !notes.has(k) && !usedKeys.has(k));
|
|
5389
|
-
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
|
+
);
|
|
5390
5524
|
for (const key of keysMatchingAny(unitOnly, nonUnitDirs, compile)) {
|
|
5391
5525
|
notes.set(key, "matched directories but never a unit");
|
|
5392
5526
|
}
|
|
@@ -5410,6 +5544,7 @@ var architectureReservedDirectoryNames = {
|
|
|
5410
5544
|
docsUrl: docsUrl10
|
|
5411
5545
|
});
|
|
5412
5546
|
}
|
|
5547
|
+
ctx.recordExamined?.(examinedCounts);
|
|
5413
5548
|
return out;
|
|
5414
5549
|
}
|
|
5415
5550
|
};
|
|
@@ -6612,6 +6747,7 @@ body{background:var(--ground);color:var(--ink);font-family:var(--sans);line-heig
|
|
|
6612
6747
|
.dv-cat-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px}
|
|
6613
6748
|
.dv-bar{height:7px;border-radius:999px;background:var(--line);overflow:hidden}
|
|
6614
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}
|
|
6615
6751
|
.dv-filters{display:flex;gap:8px;flex-wrap:wrap;margin:16px 0}
|
|
6616
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}
|
|
6617
6753
|
.dv-chip[aria-pressed="true"]{background:var(--active-bg);border-color:var(--active-bg);color:var(--active-ink)}
|
|
@@ -7084,12 +7220,21 @@ var APP_SCRIPT = `
|
|
|
7084
7220
|
var band = scoreBand(c.score);
|
|
7085
7221
|
var weight = s.report.weights[cat];
|
|
7086
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;
|
|
7087
7231
|
return h('div', { class: 'dv-cat' }, [
|
|
7088
7232
|
h('div', { class: 'dv-cat-top' }, [
|
|
7089
7233
|
h('span', { text: name + (weight !== undefined ? ' (weight ' + weight + ')' : '') }, []),
|
|
7090
7234
|
h('span', { style: 'color:' + BAND_COLOR[band], text: String(c.score) }, [])
|
|
7091
7235
|
]),
|
|
7092
|
-
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
|
|
7093
7238
|
]);
|
|
7094
7239
|
});
|
|
7095
7240
|
var chips = renderFilterChips(s.report.categories);
|