@domandigital/craft 0.20.1 → 0.20.3
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/CHANGELOG.md +15 -0
- package/CHARACTER.md +309 -0
- package/PRINCIPLES.md +30 -6
- package/README.md +25 -1
- package/STANDARD.md +5 -1
- package/art-direction.schema.json +82 -0
- package/design-review.schema.json +421 -0
- package/dist/{chunk-GOPHZI6O.js → chunk-2276FJVN.js} +2 -2
- package/dist/{chunk-FQYKKKHI.js → chunk-GBDPNNC2.js} +3 -3
- package/dist/{chunk-IZ6GTU54.js → chunk-KQF44RI3.js} +1 -1
- package/dist/{chunk-6NXPDCUO.js → chunk-NQAYRK2K.js} +12 -10
- package/dist/{chunk-2OBDCIVW.js → chunk-S3L7TEJF.js} +193 -2
- package/dist/cli-6AZVLNWL.js +654 -0
- package/dist/{cli-LSJI7QRD.js → cli-6INEFJYN.js} +1 -1
- package/dist/{cli-VFKKFRB4.js → cli-7HKVVL3P.js} +5 -5
- package/dist/{cli-NTNUZUFP.js → cli-AFKXDGUH.js} +2 -2
- package/dist/{cli-DVYLDCEA.js → cli-AK4EQHUF.js} +2 -2
- package/dist/{cli-IREKGVSM.js → cli-OYVZOHHV.js} +3 -3
- package/dist/{cli-HUN5ZTH5.js → cli-QGJB6OUB.js} +4 -4
- package/dist/cli.js +1 -1
- package/dist/index.cjs +480 -5
- package/dist/index.d.cts +286 -1
- package/dist/index.d.ts +286 -1
- package/dist/index.js +464 -5
- package/dist/phone.cjs +20 -1
- package/dist/phone.d.cts +30 -1
- package/dist/phone.d.ts +30 -1
- package/dist/phone.js +18 -1
- package/dist/{viewports-cli-C4VXMPKK.js → viewports-cli-WWWWEBKI.js} +2 -1
- package/package.json +4 -2
- package/dist/cli-MKZJMZUJ.js +0 -337
package/dist/index.js
CHANGED
|
@@ -6555,14 +6555,14 @@ function cv(xs, min) {
|
|
|
6555
6555
|
}
|
|
6556
6556
|
function shapeOf(text) {
|
|
6557
6557
|
const ss = splitSentences(text);
|
|
6558
|
-
const
|
|
6559
|
-
const per100 = (n) =>
|
|
6558
|
+
const wordCount2 = words(text).length;
|
|
6559
|
+
const per100 = (n) => wordCount2 === 0 ? 0 : 100 * n / wordCount2;
|
|
6560
6560
|
const share2 = (re) => ss.length === 0 ? 0 : ss.filter((s) => re.test(s)).length / ss.length;
|
|
6561
6561
|
const tri = ss.flatMap(tricolons);
|
|
6562
6562
|
const commas = (text.match(/,/g) ?? []).length;
|
|
6563
6563
|
return {
|
|
6564
6564
|
sentences: ss.length,
|
|
6565
|
-
words:
|
|
6565
|
+
words: wordCount2,
|
|
6566
6566
|
tricolons: tri.length,
|
|
6567
6567
|
parallelTriads: tri.filter((t) => t.parallel).length,
|
|
6568
6568
|
stackedConditionals: ss.filter(stackedConditional).length,
|
|
@@ -8265,7 +8265,18 @@ var SOURCE_KINDS = [
|
|
|
8265
8265
|
var CHOICE_KEYS = ["accent", "ground", "display", "body", "shape", "motif", "signature"];
|
|
8266
8266
|
var CONSIDERATION = ["low", "considered", "high"];
|
|
8267
8267
|
var PAGE_TYPES = ["home", "service", "about", "pricing", "booking", "contact", "article", "landing"];
|
|
8268
|
+
var PROOF_KINDS = ["work", "people", "credentials", "reviews", "experience", "outcomes"];
|
|
8269
|
+
var PROOF_DEPTHS = ["first-look", "on-request"];
|
|
8270
|
+
var VIEWPORTS = ["mobile", "tablet", "desktop"];
|
|
8268
8271
|
var HIERARCHY_FIELDS = ["primaryAction", "order", "lead", "journey"];
|
|
8272
|
+
var CONFIDENCE_LENSES = ["relevance", "hierarchy", "evidence", "identity", "action", "disclosure", "personality", "differentiation", "mobile"];
|
|
8273
|
+
var REVIEW_BASES = ["observed", "inferred", "preference", "unknown"];
|
|
8274
|
+
var INSPECTED_KINDS = ["screenshot", "rendered", "source", "measurement", "content"];
|
|
8275
|
+
var REPORTED_KINDS = ["audit", "stakeholder", "customer"];
|
|
8276
|
+
var EVIDENCE_KINDS = [...INSPECTED_KINDS, ...REPORTED_KINDS];
|
|
8277
|
+
var CHANGE_CLASSES = ["confirmed-correction", "design-hypothesis", "optional-refinement", "no-change"];
|
|
8278
|
+
var CHANGE_STEPS = ["copy", "relocate", "group", "imagery", "trust-placement", "adjust-component", "remove-redundancy", "restructure", "new-component"];
|
|
8279
|
+
var ADDITIONS = ["carousel", "javascript", "dependency", "animation", "testimonial", "cta", "duplicate-content"];
|
|
8269
8280
|
|
|
8270
8281
|
// src/direction/reason.ts
|
|
8271
8282
|
var PROPOSED = "PROPOSED:";
|
|
@@ -8561,6 +8572,429 @@ function checkOrder(declared, page, snapshot, strict = false) {
|
|
|
8561
8572
|
return [{ severity: strict ? "error" : "warn", at: `hierarchy.${page}.order`, message: `the page runs ${rendered.join(", ")}; the file says ${said.join(", ")}` }];
|
|
8562
8573
|
}
|
|
8563
8574
|
|
|
8575
|
+
// src/direction/review.ts
|
|
8576
|
+
var isObject = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
8577
|
+
var wordCount = (x) => typeof x === "string" ? x.trim().split(/\s+/).filter(Boolean).length : 0;
|
|
8578
|
+
var includes = (list, x) => list.includes(x);
|
|
8579
|
+
var STEP_RANK = new Map(CHANGE_STEPS.map((s, i) => [s, i]));
|
|
8580
|
+
var CLASS_RANK = { "no-change": 0, "optional-refinement": 1, "design-hypothesis": 2, "confirmed-correction": 3 };
|
|
8581
|
+
var RENDERED_KINDS = ["screenshot", "rendered", "measurement"];
|
|
8582
|
+
var isStructural = (step) => step === "restructure" || step === "new-component";
|
|
8583
|
+
var GALLERY = /\b(?:gallery|galleries|carousel|slideshow|slider|swipe(?:able)?|rotating)\b/i;
|
|
8584
|
+
function plain2(at, text) {
|
|
8585
|
+
if (wordCount(text) < 8) return [{ severity: "error", at, message: "a sentence at least: say why, in this visitor's terms" }];
|
|
8586
|
+
const t = text;
|
|
8587
|
+
if (PREFERENCE.test(t)) return [{ severity: "error", at, message: "that is a preference, not a reason. Say what it does for this visitor." }];
|
|
8588
|
+
const c = t.match(CONVENTION);
|
|
8589
|
+
if (c) return [{ severity: "error", at, message: `"${c[0]}" is the category's default speaking, not a reason. Say what in this visitor's job it answers.` }];
|
|
8590
|
+
return [];
|
|
8591
|
+
}
|
|
8592
|
+
function checkProposalShape(at, p, required) {
|
|
8593
|
+
const problems = [];
|
|
8594
|
+
const err = (rest, message) => problems.push({ severity: "error", at: `${at}${rest}`, message });
|
|
8595
|
+
if (!isObject(p)) {
|
|
8596
|
+
if (required) err("", "a proposal is an object: change, step, and optionally adds and touches");
|
|
8597
|
+
return problems;
|
|
8598
|
+
}
|
|
8599
|
+
if (wordCount(p.change) < 3) err(".change", "say what changes, in a few words");
|
|
8600
|
+
if (!includes(CHANGE_STEPS, p.step)) err(".step", `one of ${CHANGE_STEPS.join(", ")}: the smallest step that does it`);
|
|
8601
|
+
if (p.adds !== void 0) {
|
|
8602
|
+
if (!Array.isArray(p.adds) || p.adds.some((a) => !includes(ADDITIONS, a))) err(".adds", `a list drawn from ${ADDITIONS.join(", ")}`);
|
|
8603
|
+
}
|
|
8604
|
+
if (p.comparisons !== void 0) {
|
|
8605
|
+
if (!Array.isArray(p.comparisons)) err(".comparisons", "a list comparing each relevant existing component");
|
|
8606
|
+
else p.comparisons.forEach((c, i) => {
|
|
8607
|
+
if (!isObject(c) || typeof c.existing !== "string" || !c.existing.trim() || typeof c.samePurpose !== "boolean") {
|
|
8608
|
+
err(`.comparisons[${i}]`, "existing, samePurpose and because are required");
|
|
8609
|
+
} else problems.push(...plain2(`${at}.comparisons[${i}].because`, c.because));
|
|
8610
|
+
});
|
|
8611
|
+
}
|
|
8612
|
+
if (p.touches !== void 0 && !includes(SECTION_ROLES, p.touches)) err(".touches", `a section role: ${SECTION_ROLES.join(", ")}`);
|
|
8613
|
+
return problems;
|
|
8614
|
+
}
|
|
8615
|
+
function checkEvidenceShape(at, e) {
|
|
8616
|
+
const problems = [];
|
|
8617
|
+
const err = (rest, message) => problems.push({ severity: "error", at: `${at}${rest}`, message });
|
|
8618
|
+
if (!isObject(e)) return [{ severity: "error", at, message: "evidence is an object: kind, ref, and a viewport for anything rendered" }];
|
|
8619
|
+
if (!includes(EVIDENCE_KINDS, e.kind)) err(".kind", `one of ${EVIDENCE_KINDS.join(", ")}`);
|
|
8620
|
+
if (typeof e.ref !== "string" || !e.ref.trim()) err(".ref", "where it can be found: a screenshot path, a selector, a quotation, a report");
|
|
8621
|
+
if (e.viewport !== void 0 && !includes(VIEWPORTS, e.viewport)) err(".viewport", `one of ${VIEWPORTS.join(", ")}`);
|
|
8622
|
+
if (includes(RENDERED_KINDS, e.kind) && e.viewport === void 0) err(".viewport", "say which width this was seen at: what is seen at one width is not evidence about another");
|
|
8623
|
+
return problems;
|
|
8624
|
+
}
|
|
8625
|
+
function checkNoteShape(i, n) {
|
|
8626
|
+
const base = `notes[${i}]`;
|
|
8627
|
+
const problems = [];
|
|
8628
|
+
const err = (rest, message) => problems.push({ severity: "error", at: `${base}${rest}`, message });
|
|
8629
|
+
if (typeof n.id !== "string" || !n.id.trim()) err(".id", "a short name for this note");
|
|
8630
|
+
if (n.page !== void 0 && !includes(PAGE_TYPES, n.page)) err(".page", `one of ${PAGE_TYPES.join(", ")}`);
|
|
8631
|
+
if (!includes(CONFIDENCE_LENSES, n.lens)) err(".lens", `one of ${CONFIDENCE_LENSES.join(", ")}`);
|
|
8632
|
+
if (wordCount(n.observation) < 3) err(".observation", "what was said or seen, as it was said");
|
|
8633
|
+
if (typeof n.raisedBy !== "string" || !n.raisedBy.trim()) err(".raisedBy", "who said it, or what found it");
|
|
8634
|
+
if (!includes(REVIEW_BASES, n.basis)) err(".basis", `one of ${REVIEW_BASES.join(", ")}`);
|
|
8635
|
+
if (!Array.isArray(n.evidence)) err(".evidence", "a list, possibly empty: what supports the note");
|
|
8636
|
+
else n.evidence.forEach((e, j) => problems.push(...checkEvidenceShape(`${base}.evidence[${j}]`, e)));
|
|
8637
|
+
if (n.appliesTo !== void 0 && (!Array.isArray(n.appliesTo) || n.appliesTo.some((v) => !includes(VIEWPORTS, v)))) err(".appliesTo", `a list drawn from ${VIEWPORTS.join(", ")}`);
|
|
8638
|
+
if (n.existing !== void 0 && (!Array.isArray(n.existing) || n.existing.some((x) => typeof x !== "string"))) err(".existing", "a list of what the page already has");
|
|
8639
|
+
if (n.suggested !== void 0) problems.push(...checkProposalShape(`${base}.suggested`, n.suggested, false));
|
|
8640
|
+
const r = n.recommend;
|
|
8641
|
+
if (!isObject(r)) {
|
|
8642
|
+
err(".recommend", "the recommendation: a class, and for anything but no-change a change, a step and a reason");
|
|
8643
|
+
return problems;
|
|
8644
|
+
}
|
|
8645
|
+
if (!includes(CHANGE_CLASSES, r.class)) {
|
|
8646
|
+
err(".recommend.class", `one of ${CHANGE_CLASSES.join(", ")}. no-change is a result, not a failure to answer.`);
|
|
8647
|
+
return problems;
|
|
8648
|
+
}
|
|
8649
|
+
if (r.class === "no-change") {
|
|
8650
|
+
if (r.because !== void 0) problems.push(...plain2(`${base}.recommend.because`, r.because));
|
|
8651
|
+
return problems;
|
|
8652
|
+
}
|
|
8653
|
+
problems.push(...checkProposalShape(`${base}.recommend`, r, true));
|
|
8654
|
+
problems.push(...plain2(`${base}.recommend.because`, r.because));
|
|
8655
|
+
const bigger = isStructural(r.step) || Array.isArray(r.adds) && r.adds.length > 0;
|
|
8656
|
+
if (bigger) {
|
|
8657
|
+
const smaller = Array.isArray(r.smaller) ? r.smaller : [];
|
|
8658
|
+
if (smaller.length === 0) err(".recommend.smaller", "a structural change, or one that adds a carousel, script, dependency, animation, testimonial, call to action or repeated content, says which smaller steps were considered and why none was enough");
|
|
8659
|
+
const limit = STEP_RANK.get(r.step) ?? 0;
|
|
8660
|
+
smaller.forEach((s, j) => {
|
|
8661
|
+
const ok = isObject(s) && includes(CHANGE_STEPS, s.step) && (limit === 0 || (STEP_RANK.get(s.step) ?? 99) < limit);
|
|
8662
|
+
if (!ok) err(`.recommend.smaller[${j}].step`, "a step on the ladder below the one recommended");
|
|
8663
|
+
if (isObject(s)) problems.push(...plain2(`${base}.recommend.smaller[${j}].whyNot`, s.whyNot));
|
|
8664
|
+
});
|
|
8665
|
+
} else {
|
|
8666
|
+
problems.push(...plain2(`${base}.recommend.whyEnough`, r.whyEnough));
|
|
8667
|
+
}
|
|
8668
|
+
if (r.class === "design-hypothesis" && wordCount(r.validation) < 4) err(".recommend.validation", "a hypothesis says how it would be checked: a phone test, five visitors, a read of the enquiries");
|
|
8669
|
+
if (r.requires !== void 0) {
|
|
8670
|
+
if (!Array.isArray(r.requires) || r.requires.some((q) => !isObject(q) || wordCount(q.what) < 2)) err(".recommend.requires", "a list of {what, source?}: facts or assets the change depends on");
|
|
8671
|
+
}
|
|
8672
|
+
return problems;
|
|
8673
|
+
}
|
|
8674
|
+
function whyNot(p, ctx) {
|
|
8675
|
+
const reasons = [];
|
|
8676
|
+
const adds = Array.isArray(p.adds) ? p.adds : [];
|
|
8677
|
+
if (adds.includes("carousel")) {
|
|
8678
|
+
for (const have of new Set(ctx.existing.filter((e) => GALLERY.test(e)))) {
|
|
8679
|
+
const compared = p.comparisons?.filter((c) => c.existing === have) ?? [];
|
|
8680
|
+
if (compared.length !== 1) reasons.push(`the page already has "${have}". Compare its purpose, content and placement before adding another carousel; duplication is not established by its name.`);
|
|
8681
|
+
else if (compared[0].samePurpose) reasons.push(`the comparison identifies the same purpose as "${have}": ${compared[0].because}. Reuse or adjust that component.`);
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8684
|
+
const defensible = (ctx.basis === "observed" || ctx.basis === "inferred") && ctx.impact;
|
|
8685
|
+
if ((isStructural(p.step) || adds.length > 0) && !defensible) {
|
|
8686
|
+
reasons.push("it is a structural change or adds something that costs, and the problem under it is neither observed nor inferred with a stated visitor impact. A preference does not justify it.");
|
|
8687
|
+
}
|
|
8688
|
+
if (p.step === "remove-redundancy" && !(ctx.basis === "observed" && ctx.impact)) {
|
|
8689
|
+
reasons.push("it removes information, and nothing observed shows it is redundant or what it costs the visitor. Useful detail stays unless it is shown not to be.");
|
|
8690
|
+
}
|
|
8691
|
+
if (p.touches && ctx.placed.has(p.touches) && ctx.basis !== "observed") {
|
|
8692
|
+
reasons.push(`the declared order already places "${p.touches}" with a reason. Only something observed overrides a reasoned decision.`);
|
|
8693
|
+
}
|
|
8694
|
+
return reasons;
|
|
8695
|
+
}
|
|
8696
|
+
function widthsOf(evidence) {
|
|
8697
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8698
|
+
for (const e of evidence) {
|
|
8699
|
+
if (e.viewport) seen.add(e.viewport);
|
|
8700
|
+
else if (e.kind === "content") VIEWPORTS.forEach((v) => seen.add(v));
|
|
8701
|
+
}
|
|
8702
|
+
return VIEWPORTS.filter((v) => seen.has(v));
|
|
8703
|
+
}
|
|
8704
|
+
function assessReview(input, direction) {
|
|
8705
|
+
const problems = [];
|
|
8706
|
+
const results = [];
|
|
8707
|
+
const fail = (at, message) => problems.push({ severity: "error", at, message });
|
|
8708
|
+
const report2 = () => {
|
|
8709
|
+
const n = (o) => results.filter((r) => r.outcome === o).length;
|
|
8710
|
+
const summary = { change: n("change"), hold: n("hold"), noChange: n("no-change"), gatherEvidence: n("gather-evidence"), verdict: "" };
|
|
8711
|
+
summary.verdict = results.length === 0 ? "The review has no notes to assess." : summary.change + summary.hold + summary.gatherEvidence === 0 ? "The existing design is already appropriate for what was reviewed. No change is recommended." : `${summary.change} change${summary.change === 1 ? "" : "s"} recommended, ${summary.hold} held for something nobody has supplied, ${summary.gatherEvidence} to establish first, ${summary.noChange} needing no change.`;
|
|
8712
|
+
return { valid: !problems.some((p) => p.severity === "error"), problems, results, summary };
|
|
8713
|
+
};
|
|
8714
|
+
if (!isObject(input)) {
|
|
8715
|
+
fail("", "a review is an object: version, subject and notes");
|
|
8716
|
+
return report2();
|
|
8717
|
+
}
|
|
8718
|
+
if (input.context !== void 0 && !["publication", "protected-staging"].includes(input.context)) fail("context", "publication or protected-staging");
|
|
8719
|
+
if (input.version !== 1) fail("version", "version is 1");
|
|
8720
|
+
if (typeof input.subject !== "string" || !input.subject.trim()) fail("subject", "who or what was reviewed");
|
|
8721
|
+
if (!Array.isArray(input.notes)) {
|
|
8722
|
+
fail("notes", "a list of notes");
|
|
8723
|
+
return report2();
|
|
8724
|
+
}
|
|
8725
|
+
const job = direction.job;
|
|
8726
|
+
if (!job) {
|
|
8727
|
+
fail("", "a review is read against the visitor's job, and the art direction has no job map yet. Record it first (craft direction research).");
|
|
8728
|
+
return report2();
|
|
8729
|
+
}
|
|
8730
|
+
const ids = /* @__PURE__ */ new Set();
|
|
8731
|
+
input.notes.forEach((raw, i) => {
|
|
8732
|
+
if (!isObject(raw)) return fail(`notes[${i}]`, "a note is an object");
|
|
8733
|
+
const shape = checkNoteShape(i, raw);
|
|
8734
|
+
if (typeof raw.id === "string") {
|
|
8735
|
+
if (ids.has(raw.id)) shape.push({ severity: "error", at: `notes[${i}].id`, message: `"${raw.id}" is used twice` });
|
|
8736
|
+
ids.add(raw.id);
|
|
8737
|
+
}
|
|
8738
|
+
problems.push(...shape);
|
|
8739
|
+
if (shape.length > 0) return;
|
|
8740
|
+
const note = raw;
|
|
8741
|
+
const page = note.page ?? "home";
|
|
8742
|
+
const adjustments = [];
|
|
8743
|
+
const hierarchy = direction.hierarchy?.[page];
|
|
8744
|
+
const sources = Array.isArray(direction.sources) ? direction.sources : [];
|
|
8745
|
+
const inspected = note.evidence.filter((e) => includes(INSPECTED_KINDS, e.kind));
|
|
8746
|
+
let basis = note.basis;
|
|
8747
|
+
if (basis === "observed" && inspected.length === 0) {
|
|
8748
|
+
basis = note.evidence.length > 0 ? "inferred" : "unknown";
|
|
8749
|
+
adjustments.push(
|
|
8750
|
+
note.evidence.length > 0 ? "declared observed, but everything cited is reported (an audit, a stakeholder, a customer) and nothing was inspected. Read as inferred until someone looks." : "declared observed, but nothing is cited. Read as unknown."
|
|
8751
|
+
);
|
|
8752
|
+
} else if (basis === "inferred" && note.evidence.length === 0) {
|
|
8753
|
+
basis = "unknown";
|
|
8754
|
+
adjustments.push("declared inferred, but nothing supports it. Read as unknown.");
|
|
8755
|
+
}
|
|
8756
|
+
const impactProblems = typeof note.impact === "string" ? checkReason({ at: `notes[${i}].impact`, because: note.impact, sources, tie: "job", job }) : [];
|
|
8757
|
+
const impact = typeof note.impact === "string" && impactProblems.length === 0;
|
|
8758
|
+
const wantsChange = note.recommend.class !== "no-change";
|
|
8759
|
+
if (!impact && (wantsChange || note.suggested)) {
|
|
8760
|
+
adjustments.push(
|
|
8761
|
+
typeof note.impact === "string" ? `no visitor impact tied to this client's job (${impactProblems[0]?.message ?? "none given"}).` : "no visitor impact stated. Something that costs the visitor nothing they can name is a fact or a preference, not an issue, and is not read as one."
|
|
8762
|
+
);
|
|
8763
|
+
if (basis === "inferred") basis = "preference";
|
|
8764
|
+
}
|
|
8765
|
+
const ctx = {
|
|
8766
|
+
note,
|
|
8767
|
+
basis,
|
|
8768
|
+
impact,
|
|
8769
|
+
existing: [...note.existing ?? [], ...(hierarchy?.confidence?.proof ?? []).map((p) => p.what).filter((w) => typeof w === "string")],
|
|
8770
|
+
placed: new Set((hierarchy?.order ?? []).map((o) => o.role))
|
|
8771
|
+
};
|
|
8772
|
+
const examined = widthsOf(note.evidence);
|
|
8773
|
+
const wanted = note.appliesTo ?? (examined.length > 0 ? examined : [...VIEWPORTS]);
|
|
8774
|
+
const claimed = wanted.filter((v) => examined.includes(v));
|
|
8775
|
+
const unknown = VIEWPORTS.filter((v) => wanted.includes(v) && !examined.includes(v));
|
|
8776
|
+
if (unknown.length > 0) adjustments.push(`nothing here was looked at on ${unknown.join(" or ")}, so the note says nothing about it. That stays unknown until it is examined there.`);
|
|
8777
|
+
const rec = note.recommend;
|
|
8778
|
+
const recReasons = wantsChange ? whyNot(rec, ctx) : [];
|
|
8779
|
+
const sugReasons = note.suggested ? whyNot(note.suggested, ctx) : [];
|
|
8780
|
+
const ceiling = basis === "observed" ? impact ? "confirmed-correction" : "optional-refinement" : basis === "inferred" ? "design-hypothesis" : basis === "preference" ? "optional-refinement" : "no-change";
|
|
8781
|
+
let effective = wantsChange ? rec.class : "no-change";
|
|
8782
|
+
if (basis !== "unknown" && CLASS_RANK[effective] > CLASS_RANK[ceiling]) {
|
|
8783
|
+
adjustments.push(`declared ${rec.class}, but a problem that is ${basis}${basis === "observed" && !impact ? " with no stated visitor impact" : ""} supports at most ${ceiling}.`);
|
|
8784
|
+
effective = ceiling;
|
|
8785
|
+
}
|
|
8786
|
+
let withdrawn;
|
|
8787
|
+
if (wantsChange && recReasons.length > 0) {
|
|
8788
|
+
withdrawn = recReasons;
|
|
8789
|
+
effective = "no-change";
|
|
8790
|
+
adjustments.push("the recommended change is withdrawn: " + recReasons.join(" "));
|
|
8791
|
+
}
|
|
8792
|
+
const missing = (rec.requires ?? []).filter((q) => !q.source || !sources.some((s) => s.id === q.source));
|
|
8793
|
+
const outstanding = missing.map((q) => q.what);
|
|
8794
|
+
const blocking = missing.filter((q) => !(input.context === "protected-staging" && q.kind === "asset" && q.phase === "publication" && q.placeholder));
|
|
8795
|
+
if (missing.length > blocking.length) adjustments.push("Protected staging may reserve the explicitly marked placeholder. Required assets remain outstanding for publication; no fact or photograph has been supplied by this review.");
|
|
8796
|
+
let outcome;
|
|
8797
|
+
if (basis === "unknown") {
|
|
8798
|
+
outcome = "gather-evidence";
|
|
8799
|
+
effective = "no-change";
|
|
8800
|
+
if (wantsChange) adjustments.push(`nothing is recommended until the problem is established${rec.validation ? `: ${rec.validation}` : ". Ask the client, or look, first."}`);
|
|
8801
|
+
} else if (effective === "no-change") outcome = "no-change";
|
|
8802
|
+
else if (blocking.length > 0) outcome = "hold";
|
|
8803
|
+
else outcome = "change";
|
|
8804
|
+
if (outcome === "hold") adjustments.push(`held: it depends on ${blocking.map((o) => `"${o.what}"`).join(", ")}, and no source supplies it. Do not write it in. Ask the client.`);
|
|
8805
|
+
let suggested;
|
|
8806
|
+
if (note.suggested) {
|
|
8807
|
+
const recRank = STEP_RANK.get(rec.step ?? "") ?? -1;
|
|
8808
|
+
const sugRank = STEP_RANK.get(note.suggested.step) ?? 0;
|
|
8809
|
+
const sugAdds = (note.suggested.adds ?? []).length;
|
|
8810
|
+
let standing;
|
|
8811
|
+
const reasons = [...sugReasons];
|
|
8812
|
+
if (sugReasons.length > 0) standing = "not-supported";
|
|
8813
|
+
else if (effective === "no-change") {
|
|
8814
|
+
standing = "not-needed";
|
|
8815
|
+
reasons.push("the existing design already answers the problem, or nothing is yet established to answer.");
|
|
8816
|
+
} else if (sugRank > recRank || sugAdds > 0 && (rec.adds ?? []).length === 0) {
|
|
8817
|
+
standing = "comparison-needed";
|
|
8818
|
+
reasons.push("Compare the proposals before selecting one. A smaller implementation is not evidence of a better visitor experience; compare their adequacy against the stated problem and rendered result.");
|
|
8819
|
+
} else standing = "supported";
|
|
8820
|
+
suggested = { standing, reasons };
|
|
8821
|
+
}
|
|
8822
|
+
results.push({
|
|
8823
|
+
id: note.id,
|
|
8824
|
+
page,
|
|
8825
|
+
lens: note.lens,
|
|
8826
|
+
basis: { declared: note.basis, effective: basis },
|
|
8827
|
+
...suggested ? { suggested } : {},
|
|
8828
|
+
recommend: { declared: rec.class, effective, ...wantsChange ? { step: rec.step } : {}, ...withdrawn ? { withdrawn } : {} },
|
|
8829
|
+
outcome,
|
|
8830
|
+
widths: { examined, claimed, unknown },
|
|
8831
|
+
adjustments,
|
|
8832
|
+
outstanding,
|
|
8833
|
+
publicationReady: outstanding.length === 0
|
|
8834
|
+
});
|
|
8835
|
+
});
|
|
8836
|
+
return report2();
|
|
8837
|
+
}
|
|
8838
|
+
|
|
8839
|
+
// src/direction/confidence.ts
|
|
8840
|
+
function viewportOf(width) {
|
|
8841
|
+
return width < 768 ? "mobile" : width < 1024 ? "tablet" : "desktop";
|
|
8842
|
+
}
|
|
8843
|
+
var isObject2 = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
8844
|
+
function validateConfidence(page, confidence, job, sources) {
|
|
8845
|
+
const problems = [];
|
|
8846
|
+
const base = `hierarchy.${page}.confidence`;
|
|
8847
|
+
const err = (at, message) => problems.push({ severity: "error", at: `${base}${at}`, message });
|
|
8848
|
+
if (!isObject2(confidence)) {
|
|
8849
|
+
err("", "confidence is an object: `examined` (the widths looked at) and `proof` (what the page offers)");
|
|
8850
|
+
return problems;
|
|
8851
|
+
}
|
|
8852
|
+
if (confidence.examined !== void 0) {
|
|
8853
|
+
if (!Array.isArray(confidence.examined)) err(".examined", `a list of widths: ${VIEWPORTS.join(", ")}`);
|
|
8854
|
+
else for (const v of confidence.examined) if (!VIEWPORTS.includes(v)) err(".examined", `"${String(v)}" is not a width craft knows. Widths are: ${VIEWPORTS.join(", ")}`);
|
|
8855
|
+
}
|
|
8856
|
+
if (confidence.proof !== void 0 && !Array.isArray(confidence.proof)) err(".proof", "a list of evidence items");
|
|
8857
|
+
const proof = Array.isArray(confidence.proof) ? confidence.proof : [];
|
|
8858
|
+
proof.forEach((raw, i) => {
|
|
8859
|
+
const at = `.proof[${i}]`;
|
|
8860
|
+
if (!isObject2(raw)) return err(at, "an evidence item is an object: kind, what, position, depth, because");
|
|
8861
|
+
if (!PROOF_KINDS.includes(raw.kind)) err(`${at}.kind`, `one of ${PROOF_KINDS.join(", ")}`);
|
|
8862
|
+
if (typeof raw.what !== "string" || contentWords(raw.what).size < 2) err(`${at}.what`, 'what it is, in a few words: "photographs of three finished bathrooms", not "proof"');
|
|
8863
|
+
if (typeof raw.position !== "string" || !POSITIONS.has(raw.position)) err(`${at}.position`, 'the section it sits in, or "header" or "sticky"');
|
|
8864
|
+
if (!PROOF_DEPTHS.includes(raw.depth)) err(`${at}.depth`, `${PROOF_DEPTHS.join(" or ")}: met without being sought, or detail a visitor opens`);
|
|
8865
|
+
problems.push(...checkReason({ at: `${base}${at}`, because: raw.because, evidence: raw.evidence, sources, tie: "job", job }));
|
|
8866
|
+
});
|
|
8867
|
+
return problems;
|
|
8868
|
+
}
|
|
8869
|
+
var IDENTITY_WORRY = /\b(?:strangers?|who(?:'s| is| would| will) (?:coming|doing|treating|seeing|dealing|there)|(?:someone|somebody) (?:i|they|we) (?:have not|haven't|do not|don't) (?:met|know)|have not met|haven't met|not met|in (?:my|their|the) (?:home|house)|into (?:my|their|the) (?:home|house)|accountab\w+)\b/i;
|
|
8870
|
+
var LATE_SECTION = 3;
|
|
8871
|
+
var identityText = (job) => [job.emotional?.value, job.social?.value, ...Array.isArray(job.objections) ? job.objections : [], ...Array.isArray(job.language) ? job.language : []].filter((x) => typeof x === "string").join(" ");
|
|
8872
|
+
var lensOf = (kind) => kind === "people" ? "identity" : "evidence";
|
|
8873
|
+
function decisionText(h) {
|
|
8874
|
+
const proof = Array.isArray(h.confidence?.proof) ? h.confidence.proof : [];
|
|
8875
|
+
return [
|
|
8876
|
+
h.primaryAction?.value,
|
|
8877
|
+
h.primaryAction?.because,
|
|
8878
|
+
h.lead?.value,
|
|
8879
|
+
h.lead?.because,
|
|
8880
|
+
...Array.isArray(h.order) ? h.order.map((o) => o?.because) : [],
|
|
8881
|
+
...Array.isArray(h.journey) ? h.journey.map((s) => s?.because) : [],
|
|
8882
|
+
...proof.flatMap((p) => isObject2(p) ? [p.what, p.because] : [])
|
|
8883
|
+
].filter((x) => typeof x === "string").join(" ");
|
|
8884
|
+
}
|
|
8885
|
+
function assessConfidence(hierarchy, job, ctx = {}) {
|
|
8886
|
+
const findings = [];
|
|
8887
|
+
if (!isObject2(hierarchy)) return findings;
|
|
8888
|
+
for (const [key, raw] of Object.entries(hierarchy)) {
|
|
8889
|
+
const h = raw;
|
|
8890
|
+
if (!PAGE_TYPES.includes(key) || !isObject2(h) || !isObject2(h.confidence)) continue;
|
|
8891
|
+
const page = key;
|
|
8892
|
+
const at = (rest) => `hierarchy.${page}.confidence${rest}`;
|
|
8893
|
+
const add = (f4) => findings.push({ page, ...f4 });
|
|
8894
|
+
const c = h.confidence;
|
|
8895
|
+
const proof = (Array.isArray(c.proof) ? c.proof : []).filter(isObject2);
|
|
8896
|
+
const order = new Set((Array.isArray(h.order) ? h.order : []).map((o) => o?.role));
|
|
8897
|
+
const bands = new Set((Array.isArray(c.examined) ? c.examined : []).filter((v) => VIEWPORTS.includes(v)));
|
|
8898
|
+
if (ctx.snapshot && (ctx.snapshotPage ?? "home") === page) bands.add(viewportOf(ctx.snapshot.viewport.width));
|
|
8899
|
+
for (const pos of Array.isArray(h.primaryAction?.positions) ? h.primaryAction.positions : []) {
|
|
8900
|
+
if (POSITIONS.has(pos) && pos !== "header" && pos !== "sticky" && !order.has(pos)) {
|
|
8901
|
+
add({
|
|
8902
|
+
lens: "action",
|
|
8903
|
+
basis: "observed",
|
|
8904
|
+
at: `hierarchy.${page}.primaryAction.positions`,
|
|
8905
|
+
message: `the ask "${h.primaryAction.value}" sits in "${pos}", and the declared order has no such section`,
|
|
8906
|
+
why: "A visitor can only take the next step where the page offers it. Either the order or the position is out of date."
|
|
8907
|
+
});
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8910
|
+
proof.forEach((p, i) => {
|
|
8911
|
+
const where = at(`.proof[${i}]`);
|
|
8912
|
+
if (POSITIONS.has(p.position) && p.position !== "header" && p.position !== "sticky" && !order.has(p.position)) {
|
|
8913
|
+
add({
|
|
8914
|
+
lens: "hierarchy",
|
|
8915
|
+
basis: "observed",
|
|
8916
|
+
at: `${where}.position`,
|
|
8917
|
+
message: `${p.kind} evidence "${p.what}" sits in "${p.position}", and the declared order has no such section`,
|
|
8918
|
+
why: "Evidence a visitor is meant to meet has to be on the page, in a section the order names."
|
|
8919
|
+
});
|
|
8920
|
+
}
|
|
8921
|
+
const cited = Array.isArray(p.evidence) ? p.evidence : [];
|
|
8922
|
+
if (cited.length === 0) {
|
|
8923
|
+
add({
|
|
8924
|
+
lens: lensOf(p.kind),
|
|
8925
|
+
basis: "unknown",
|
|
8926
|
+
at: `${where}.evidence`,
|
|
8927
|
+
message: `no source in the file shows that "${p.what}" exists`,
|
|
8928
|
+
why: "Confidence built on evidence that is only planned or imagined is the opposite of confidence. Confirm it with the client, and cite what shows it, before the page claims it."
|
|
8929
|
+
});
|
|
8930
|
+
}
|
|
8931
|
+
});
|
|
8932
|
+
const firstLook = new Set(proof.filter((p) => p.depth === "first-look").map((p) => p.kind));
|
|
8933
|
+
proof.forEach((p, i) => {
|
|
8934
|
+
if (p.depth === "on-request" && !firstLook.has(p.kind)) {
|
|
8935
|
+
add({
|
|
8936
|
+
lens: "disclosure",
|
|
8937
|
+
basis: "inferred",
|
|
8938
|
+
at: at(`.proof[${i}].depth`),
|
|
8939
|
+
message: `${p.kind} evidence "${p.what}" is held back for visitors who seek it, and nothing of the same kind is met first`,
|
|
8940
|
+
why: "Detail can wait only if the visitor can tell it is there. Nothing at first look means the evidence may never be found. A line in the header or a short sample in the hero would do; this is a hypothesis, not a rule."
|
|
8941
|
+
});
|
|
8942
|
+
}
|
|
8943
|
+
});
|
|
8944
|
+
if (job && IDENTITY_WORRY.test(identityText(job))) {
|
|
8945
|
+
const sections = Array.isArray(h.order) ? h.order.map((o) => o?.role) : [];
|
|
8946
|
+
const before = (pos) => pos === "header" || pos === "sticky" ? 0 : sections.indexOf(pos);
|
|
8947
|
+
const met = proof.filter((p) => p.kind === "people" && before(p.position) >= 0).map((p) => ({ position: p.position, before: before(p.position) }));
|
|
8948
|
+
const earliest = met.length > 0 ? met.reduce((a, b) => b.before < a.before ? b : a) : void 0;
|
|
8949
|
+
if (earliest && earliest.before >= LATE_SECTION) {
|
|
8950
|
+
add({
|
|
8951
|
+
lens: "identity",
|
|
8952
|
+
basis: "inferred",
|
|
8953
|
+
at: at(".proof"),
|
|
8954
|
+
message: `the job worries about who the visitor would deal with, and the first person is met in "${earliest.position}", after ${earliest.before} other sections`,
|
|
8955
|
+
why: `A visitor who wants to know who is coming may leave or ask before getting there. The smallest change is a name and a line where the ask is first made, not the whole biography moved up: it can stay where it is. This is a hypothesis about the order as declared; check it on the width where it matters.${bands.has("mobile") ? "" : " It has not been examined on a phone."}`
|
|
8956
|
+
});
|
|
8957
|
+
}
|
|
8958
|
+
}
|
|
8959
|
+
if (job && Array.isArray(job.objections)) {
|
|
8960
|
+
const said = contentWords(decisionText(h));
|
|
8961
|
+
job.objections.forEach((o, i) => {
|
|
8962
|
+
if (typeof o !== "string") return;
|
|
8963
|
+
if (shares(contentWords(o), said).length === 0) {
|
|
8964
|
+
add({
|
|
8965
|
+
lens: "relevance",
|
|
8966
|
+
basis: "unknown",
|
|
8967
|
+
at: `job.objections[${i}]`,
|
|
8968
|
+
message: `no decision recorded for this page names the worry "${o}"`,
|
|
8969
|
+
why: "A visitor who arrives with this worry needs something that meets it. Either a decision here does, and its reason should say so, or this page does not have to answer it. This reads wording only: it can miss an answer that uses other words."
|
|
8970
|
+
});
|
|
8971
|
+
}
|
|
8972
|
+
});
|
|
8973
|
+
}
|
|
8974
|
+
if (bands.size === 0) {
|
|
8975
|
+
add({
|
|
8976
|
+
lens: "mobile",
|
|
8977
|
+
basis: "unknown",
|
|
8978
|
+
at: at(".examined"),
|
|
8979
|
+
message: "no width recorded: say which widths these decisions were examined at",
|
|
8980
|
+
why: "The phone is the base layout (STANDARD.md section 10). What was seen at one width says nothing about another until it has been looked at there."
|
|
8981
|
+
});
|
|
8982
|
+
} else {
|
|
8983
|
+
for (const band of VIEWPORTS) {
|
|
8984
|
+
if (bands.has(band)) continue;
|
|
8985
|
+
add({
|
|
8986
|
+
lens: "mobile",
|
|
8987
|
+
basis: "unknown",
|
|
8988
|
+
at: at(".examined"),
|
|
8989
|
+
message: band === "mobile" ? "not examined on a phone" : `not examined at ${band} width; what was seen at ${[...bands].join(" and ")} is not carried to it`,
|
|
8990
|
+
why: band === "mobile" ? "The phone is the base layout every wider one is derived from (STANDARD.md section 10). A page not looked at there has not been looked at where it is designed first." : "Order, prominence and what fits can differ at another width, and nothing recorded shows they do not. Examine it there before treating the finding as true of it."
|
|
8991
|
+
});
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8995
|
+
return findings;
|
|
8996
|
+
}
|
|
8997
|
+
|
|
8564
8998
|
// src/direction/research.ts
|
|
8565
8999
|
function tradeOf(brief) {
|
|
8566
9000
|
const text = brief.toLowerCase();
|
|
@@ -8591,14 +9025,17 @@ function researchPrompt(input) {
|
|
|
8591
9025
|
"4. **Their words.** The phrases they actually use for the problem and for what they want. Quote them exactly.",
|
|
8592
9026
|
"5. **Who else is involved.** Who do they have to explain or justify the choice to (a partner, a landlord, a tenant, a manager), and what would make them look good or bad to that person?",
|
|
8593
9027
|
"6. **Consideration.** Do they decide in minutes, days or months? What do they compare before deciding?",
|
|
8594
|
-
"7. **Where they look.** What do they search for, and where do they ask?",
|
|
9028
|
+
"7. **Where they look.** What do they search for, and where do they ask? On a phone or a computer, and at what moment: standing in a kitchen, on a commute, at a desk?",
|
|
9029
|
+
"8. **What would settle it.** Before they would get in touch, what do they check or want to see: work that was actually done, who they would deal with, a registration they can look up, what other customers said, how long the person has done this? Say which of these the people you found actually mention, and do not assume all of them matter.",
|
|
9030
|
+
"9. **The next step.** How do they usually take it: ring, book, send an enquiry, ask for a quotation, look further first? What stops them taking it, and what do they want to know before they do?",
|
|
8595
9031
|
"",
|
|
8596
9032
|
"## Do not",
|
|
8597
9033
|
"",
|
|
8598
9034
|
`- Do not describe, list, rank or summarise what ${category} websites look like, or how they are laid out.`,
|
|
8599
9035
|
'- Do not suggest colours, fonts, layouts, sections or visual "inspiration" from this category.',
|
|
8600
9036
|
"- Do not summarise competitors' marketing. Their claims are the category average; the customer's words are the point.",
|
|
8601
|
-
"- Do not invent quotes. If you cannot find a source for something, say so."
|
|
9037
|
+
"- Do not invent quotes. If you cannot find a source for something, say so.",
|
|
9038
|
+
"- Do not assume the next step is a phone call, or that a business needs a founder's photograph, reviews, credentials or a gallery. Report what these customers say they need, including that they need none of it."
|
|
8602
9039
|
];
|
|
8603
9040
|
if (input.visual) {
|
|
8604
9041
|
lines.push(
|
|
@@ -8929,6 +9366,7 @@ function validateDirection(input, ctx = {}) {
|
|
|
8929
9366
|
}
|
|
8930
9367
|
let job = false;
|
|
8931
9368
|
let hierarchy = [];
|
|
9369
|
+
let confidence;
|
|
8932
9370
|
if (d.version === 2) {
|
|
8933
9371
|
const j = validateJob(d.job, sources, { brief: typeof d.brief === "string" ? d.brief : "", readSource: ctx.readSource });
|
|
8934
9372
|
problems.push(...j.problems);
|
|
@@ -8936,6 +9374,10 @@ function validateDirection(input, ctx = {}) {
|
|
|
8936
9374
|
const h = validateHierarchy(d.hierarchy, d.job, sources);
|
|
8937
9375
|
problems.push(...h.problems);
|
|
8938
9376
|
hierarchy = h.pages;
|
|
9377
|
+
const pages = d.hierarchy && typeof d.hierarchy === "object" && !Array.isArray(d.hierarchy) ? d.hierarchy : {};
|
|
9378
|
+
const recorded = Object.entries(pages).filter(([k, p]) => PAGE_TYPES.includes(k) && p && typeof p === "object" && p.confidence !== void 0);
|
|
9379
|
+
for (const [page, p] of recorded) problems.push(...validateConfidence(page, p.confidence, d.job, sources));
|
|
9380
|
+
if (recorded.length) confidence = assessConfidence(d.hierarchy, d.job, { snapshot: ctx.snapshot, snapshotPage: ctx.page });
|
|
8939
9381
|
if (ctx.snapshot) problems.push(...checkOrder(d.hierarchy?.[ctx.page ?? "home"], ctx.page ?? "home", ctx.snapshot, ctx.strict));
|
|
8940
9382
|
}
|
|
8941
9383
|
if (ctx.estate?.length) {
|
|
@@ -8952,6 +9394,7 @@ function validateDirection(input, ctx = {}) {
|
|
|
8952
9394
|
valid: problems.every((p) => p.severity !== "error"),
|
|
8953
9395
|
problems,
|
|
8954
9396
|
decided,
|
|
9397
|
+
...confidence ? { confidence } : {},
|
|
8955
9398
|
layers: { job, hierarchy, tokens: { decided, total: CHOICE_KEYS.length }, complete: complete && problems.every((p) => p.severity !== "error") }
|
|
8956
9399
|
};
|
|
8957
9400
|
}
|
|
@@ -9509,6 +9952,7 @@ function retrofitPlan(report2, direction) {
|
|
|
9509
9952
|
export {
|
|
9510
9953
|
ACCENT_SCALE,
|
|
9511
9954
|
ACCENT_SHARED,
|
|
9955
|
+
ADDITIONS,
|
|
9512
9956
|
AI_PHRASES,
|
|
9513
9957
|
AI_SOURCE_TYPES,
|
|
9514
9958
|
AI_VIOLET,
|
|
@@ -9517,8 +9961,11 @@ export {
|
|
|
9517
9961
|
BUZZWORDS,
|
|
9518
9962
|
CATALOGUE,
|
|
9519
9963
|
CATALOGUE_VERSION,
|
|
9964
|
+
CHANGE_CLASSES,
|
|
9965
|
+
CHANGE_STEPS,
|
|
9520
9966
|
CHOICE_KEYS,
|
|
9521
9967
|
COMPONENT_ROLES,
|
|
9968
|
+
CONFIDENCE_LENSES,
|
|
9522
9969
|
CONSIDERATION,
|
|
9523
9970
|
CONTENT_FEATURES,
|
|
9524
9971
|
CONVENTION,
|
|
@@ -9535,6 +9982,7 @@ export {
|
|
|
9535
9982
|
EASE_TUPLE,
|
|
9536
9983
|
EDGE_STEP,
|
|
9537
9984
|
ESTATE_VERSION,
|
|
9985
|
+
EVIDENCE_KINDS,
|
|
9538
9986
|
EXIT_RATIO,
|
|
9539
9987
|
FINGERPRINT_VERSION,
|
|
9540
9988
|
FLAGGED_TARGET,
|
|
@@ -9550,6 +9998,7 @@ export {
|
|
|
9550
9998
|
HOUSE_TYPE,
|
|
9551
9999
|
HUMAN_TYPICAL_TARGET,
|
|
9552
10000
|
IMAGERY_TELLS,
|
|
10001
|
+
INSPECTED_KINDS,
|
|
9553
10002
|
JSON_SCHEMA_VERSION,
|
|
9554
10003
|
LABELS2 as LABELS,
|
|
9555
10004
|
LEXICAL_FEATURES,
|
|
@@ -9570,6 +10019,8 @@ export {
|
|
|
9570
10019
|
PROFILE_MEASURED,
|
|
9571
10020
|
PROFILE_PARAGRAPHS,
|
|
9572
10021
|
PROMO_BADGE,
|
|
10022
|
+
PROOF_DEPTHS,
|
|
10023
|
+
PROOF_KINDS,
|
|
9573
10024
|
PROOF_NAMES,
|
|
9574
10025
|
PROPOSED,
|
|
9575
10026
|
PROVENANCE_SCAN_BYTES,
|
|
@@ -9583,7 +10034,9 @@ export {
|
|
|
9583
10034
|
REGISTER_IDS,
|
|
9584
10035
|
RENDERED_PATHS,
|
|
9585
10036
|
RENDERED_TELLS,
|
|
10037
|
+
REPORTED_KINDS,
|
|
9586
10038
|
REVEAL_LIMIT,
|
|
10039
|
+
REVIEW_BASES,
|
|
9587
10040
|
REVIEW_PHRASES,
|
|
9588
10041
|
SAMPLES_PER_SEGMENT,
|
|
9589
10042
|
SCALE,
|
|
@@ -9612,12 +10065,15 @@ export {
|
|
|
9612
10065
|
TYPE_STEPS,
|
|
9613
10066
|
TYPICAL_AT,
|
|
9614
10067
|
VAGUE_WORDS,
|
|
10068
|
+
VIEWPORTS,
|
|
9615
10069
|
accentFork,
|
|
9616
10070
|
addSite,
|
|
9617
10071
|
apcaContrast,
|
|
9618
10072
|
applyBaseline,
|
|
9619
10073
|
applyHouseGate,
|
|
9620
10074
|
applySeverity,
|
|
10075
|
+
assessConfidence,
|
|
10076
|
+
assessReview,
|
|
9621
10077
|
auditSnapshot,
|
|
9622
10078
|
bandOf,
|
|
9623
10079
|
briefTerms,
|
|
@@ -9705,6 +10161,7 @@ export {
|
|
|
9705
10161
|
isCategoryLabel,
|
|
9706
10162
|
isCream,
|
|
9707
10163
|
isFlagged,
|
|
10164
|
+
isStructural,
|
|
9708
10165
|
jobWords,
|
|
9709
10166
|
layoutDistance,
|
|
9710
10167
|
lexicalOf,
|
|
@@ -9775,9 +10232,11 @@ export {
|
|
|
9775
10232
|
typeFeatureTokens,
|
|
9776
10233
|
typicality,
|
|
9777
10234
|
unprovenClaims,
|
|
10235
|
+
validateConfidence,
|
|
9778
10236
|
validateDirection,
|
|
9779
10237
|
validateHierarchy,
|
|
9780
10238
|
validateJob,
|
|
10239
|
+
viewportOf,
|
|
9781
10240
|
visibleText,
|
|
9782
10241
|
visualMeasures,
|
|
9783
10242
|
vocabulary,
|
package/dist/phone.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(phone_exports, {
|
|
|
24
24
|
DEFAULT_MEASURED_DIR: () => DEFAULT_MEASURED_DIR,
|
|
25
25
|
DEFAULT_SWEEP: () => DEFAULT_SWEEP,
|
|
26
26
|
DEFAULT_THRESHOLDS: () => DEFAULT_THRESHOLDS,
|
|
27
|
+
DESKTOP: () => DESKTOP,
|
|
27
28
|
EMPTY_BASELINE: () => EMPTY_BASELINE,
|
|
28
29
|
LADDER: () => LADDER,
|
|
29
30
|
PHONE: () => PHONE,
|
|
@@ -38,6 +39,7 @@ __export(phone_exports, {
|
|
|
38
39
|
mainActionFor: () => mainActionFor,
|
|
39
40
|
measurePhone: () => measurePhone,
|
|
40
41
|
measurePhoneInPage: () => measurePhoneInPage,
|
|
42
|
+
measureRoute: () => measureRoute,
|
|
41
43
|
measureSite: () => measureSite,
|
|
42
44
|
measureViewportInPage: () => measureViewportInPage,
|
|
43
45
|
measureViewports: () => measureViewports,
|
|
@@ -724,6 +726,21 @@ async function measureViewports(page, options) {
|
|
|
724
726
|
await page.evaluate(() => document.fonts?.ready.then(() => void 0), void 0);
|
|
725
727
|
return finish(options.route, await collect(page, options), options.out);
|
|
726
728
|
}
|
|
729
|
+
var DESKTOP = { viewport: { width: 1024, height: 768 }, deviceScaleFactor: 1 };
|
|
730
|
+
async function measureRoute(open, options) {
|
|
731
|
+
if (!options.route.startsWith("/")) throw new Error(`measureRoute: route "${options.route}" should start with /`);
|
|
732
|
+
const collected = [];
|
|
733
|
+
for (const scope of ["phone", "wide"]) {
|
|
734
|
+
const { page, close } = await open(scope);
|
|
735
|
+
try {
|
|
736
|
+
await page.evaluate(() => document.fonts?.ready.then(() => void 0), void 0);
|
|
737
|
+
collected.push(await collect(page, { ...options, scope }));
|
|
738
|
+
} finally {
|
|
739
|
+
await close?.();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return finish(options.route, { perViewport: collected.flatMap((c) => c.perViewport), tested: collected.flatMap((c) => c.tested) }, options.out);
|
|
743
|
+
}
|
|
727
744
|
async function measureSite(browser, options) {
|
|
728
745
|
const result = { routes: [], errors: [] };
|
|
729
746
|
for (const route of options.routes) {
|
|
@@ -731,7 +748,7 @@ async function measureSite(browser, options) {
|
|
|
731
748
|
let failed = null;
|
|
732
749
|
for (const [scope, context] of [
|
|
733
750
|
["phone", PHONE],
|
|
734
|
-
["wide",
|
|
751
|
+
["wide", DESKTOP]
|
|
735
752
|
]) {
|
|
736
753
|
const ctx = await browser.newContext({ ...context });
|
|
737
754
|
try {
|
|
@@ -775,6 +792,7 @@ async function measurePhone(page, options) {
|
|
|
775
792
|
DEFAULT_MEASURED_DIR,
|
|
776
793
|
DEFAULT_SWEEP,
|
|
777
794
|
DEFAULT_THRESHOLDS,
|
|
795
|
+
DESKTOP,
|
|
778
796
|
EMPTY_BASELINE,
|
|
779
797
|
LADDER,
|
|
780
798
|
PHONE,
|
|
@@ -789,6 +807,7 @@ async function measurePhone(page, options) {
|
|
|
789
807
|
mainActionFor,
|
|
790
808
|
measurePhone,
|
|
791
809
|
measurePhoneInPage,
|
|
810
|
+
measureRoute,
|
|
792
811
|
measureSite,
|
|
793
812
|
measureViewportInPage,
|
|
794
813
|
measureViewports,
|