@domandigital/craft 0.20.2 → 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 +9 -0
- package/CHARACTER.md +309 -0
- package/PRINCIPLES.md +30 -6
- package/README.md +21 -0
- package/STANDARD.md +5 -1
- package/art-direction.schema.json +82 -0
- package/design-review.schema.json +421 -0
- package/dist/{chunk-TCMM3PTK.js → chunk-2276FJVN.js} +2 -2
- package/dist/{chunk-IJXDBYHH.js → chunk-GBDPNNC2.js} +3 -3
- package/dist/{chunk-YRXMPI3T.js → chunk-KQF44RI3.js} +1 -1
- package/dist/{chunk-JLLULDVW.js → chunk-NQAYRK2K.js} +11 -9
- package/dist/{chunk-2OBDCIVW.js → chunk-S3L7TEJF.js} +193 -2
- package/dist/cli-6AZVLNWL.js +654 -0
- package/dist/{cli-FF2YDHA4.js → cli-6INEFJYN.js} +1 -1
- package/dist/{cli-A6SNEMRL.js → cli-7HKVVL3P.js} +5 -5
- package/dist/{cli-KJRHO3YO.js → cli-AFKXDGUH.js} +2 -2
- package/dist/{cli-LVYTLT2A.js → cli-AK4EQHUF.js} +2 -2
- package/dist/{cli-U7PJQKZT.js → cli-OYVZOHHV.js} +3 -3
- package/dist/{cli-DZGQOIBX.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/package.json +4 -2
- package/dist/cli-YQEVPRL5.js +0 -337
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ACCENT_SCALE: () => ACCENT_SCALE,
|
|
24
24
|
ACCENT_SHARED: () => ACCENT_SHARED,
|
|
25
|
+
ADDITIONS: () => ADDITIONS,
|
|
25
26
|
AI_PHRASES: () => AI_PHRASES,
|
|
26
27
|
AI_SOURCE_TYPES: () => AI_SOURCE_TYPES,
|
|
27
28
|
AI_VIOLET: () => AI_VIOLET,
|
|
@@ -30,8 +31,11 @@ __export(index_exports, {
|
|
|
30
31
|
BUZZWORDS: () => BUZZWORDS,
|
|
31
32
|
CATALOGUE: () => CATALOGUE,
|
|
32
33
|
CATALOGUE_VERSION: () => CATALOGUE_VERSION,
|
|
34
|
+
CHANGE_CLASSES: () => CHANGE_CLASSES,
|
|
35
|
+
CHANGE_STEPS: () => CHANGE_STEPS,
|
|
33
36
|
CHOICE_KEYS: () => CHOICE_KEYS,
|
|
34
37
|
COMPONENT_ROLES: () => COMPONENT_ROLES,
|
|
38
|
+
CONFIDENCE_LENSES: () => CONFIDENCE_LENSES,
|
|
35
39
|
CONSIDERATION: () => CONSIDERATION,
|
|
36
40
|
CONTENT_FEATURES: () => CONTENT_FEATURES,
|
|
37
41
|
CONVENTION: () => CONVENTION,
|
|
@@ -48,6 +52,7 @@ __export(index_exports, {
|
|
|
48
52
|
EASE_TUPLE: () => EASE_TUPLE,
|
|
49
53
|
EDGE_STEP: () => EDGE_STEP,
|
|
50
54
|
ESTATE_VERSION: () => ESTATE_VERSION,
|
|
55
|
+
EVIDENCE_KINDS: () => EVIDENCE_KINDS,
|
|
51
56
|
EXIT_RATIO: () => EXIT_RATIO,
|
|
52
57
|
FINGERPRINT_VERSION: () => FINGERPRINT_VERSION,
|
|
53
58
|
FLAGGED_TARGET: () => FLAGGED_TARGET,
|
|
@@ -63,6 +68,7 @@ __export(index_exports, {
|
|
|
63
68
|
HOUSE_TYPE: () => HOUSE_TYPE,
|
|
64
69
|
HUMAN_TYPICAL_TARGET: () => HUMAN_TYPICAL_TARGET,
|
|
65
70
|
IMAGERY_TELLS: () => IMAGERY_TELLS,
|
|
71
|
+
INSPECTED_KINDS: () => INSPECTED_KINDS,
|
|
66
72
|
JSON_SCHEMA_VERSION: () => JSON_SCHEMA_VERSION,
|
|
67
73
|
LABELS: () => LABELS2,
|
|
68
74
|
LEXICAL_FEATURES: () => LEXICAL_FEATURES,
|
|
@@ -83,6 +89,8 @@ __export(index_exports, {
|
|
|
83
89
|
PROFILE_MEASURED: () => PROFILE_MEASURED,
|
|
84
90
|
PROFILE_PARAGRAPHS: () => PROFILE_PARAGRAPHS,
|
|
85
91
|
PROMO_BADGE: () => PROMO_BADGE,
|
|
92
|
+
PROOF_DEPTHS: () => PROOF_DEPTHS,
|
|
93
|
+
PROOF_KINDS: () => PROOF_KINDS,
|
|
86
94
|
PROOF_NAMES: () => PROOF_NAMES,
|
|
87
95
|
PROPOSED: () => PROPOSED,
|
|
88
96
|
PROVENANCE_SCAN_BYTES: () => PROVENANCE_SCAN_BYTES,
|
|
@@ -96,7 +104,9 @@ __export(index_exports, {
|
|
|
96
104
|
REGISTER_IDS: () => REGISTER_IDS,
|
|
97
105
|
RENDERED_PATHS: () => RENDERED_PATHS,
|
|
98
106
|
RENDERED_TELLS: () => RENDERED_TELLS,
|
|
107
|
+
REPORTED_KINDS: () => REPORTED_KINDS,
|
|
99
108
|
REVEAL_LIMIT: () => REVEAL_LIMIT,
|
|
109
|
+
REVIEW_BASES: () => REVIEW_BASES,
|
|
100
110
|
REVIEW_PHRASES: () => REVIEW_PHRASES,
|
|
101
111
|
SAMPLES_PER_SEGMENT: () => SAMPLES_PER_SEGMENT,
|
|
102
112
|
SCALE: () => SCALE,
|
|
@@ -125,12 +135,15 @@ __export(index_exports, {
|
|
|
125
135
|
TYPE_STEPS: () => TYPE_STEPS,
|
|
126
136
|
TYPICAL_AT: () => TYPICAL_AT,
|
|
127
137
|
VAGUE_WORDS: () => VAGUE_WORDS,
|
|
138
|
+
VIEWPORTS: () => VIEWPORTS,
|
|
128
139
|
accentFork: () => accentFork,
|
|
129
140
|
addSite: () => addSite,
|
|
130
141
|
apcaContrast: () => apcaContrast,
|
|
131
142
|
applyBaseline: () => applyBaseline,
|
|
132
143
|
applyHouseGate: () => applyHouseGate,
|
|
133
144
|
applySeverity: () => applySeverity,
|
|
145
|
+
assessConfidence: () => assessConfidence,
|
|
146
|
+
assessReview: () => assessReview,
|
|
134
147
|
auditSnapshot: () => auditSnapshot,
|
|
135
148
|
bandOf: () => bandOf,
|
|
136
149
|
briefTerms: () => briefTerms,
|
|
@@ -218,6 +231,7 @@ __export(index_exports, {
|
|
|
218
231
|
isCategoryLabel: () => isCategoryLabel,
|
|
219
232
|
isCream: () => isCream,
|
|
220
233
|
isFlagged: () => isFlagged,
|
|
234
|
+
isStructural: () => isStructural,
|
|
221
235
|
jobWords: () => jobWords,
|
|
222
236
|
layoutDistance: () => layoutDistance,
|
|
223
237
|
lexicalOf: () => lexicalOf,
|
|
@@ -288,9 +302,11 @@ __export(index_exports, {
|
|
|
288
302
|
typeFeatureTokens: () => typeFeatureTokens,
|
|
289
303
|
typicality: () => typicality,
|
|
290
304
|
unprovenClaims: () => unprovenClaims,
|
|
305
|
+
validateConfidence: () => validateConfidence,
|
|
291
306
|
validateDirection: () => validateDirection,
|
|
292
307
|
validateHierarchy: () => validateHierarchy,
|
|
293
308
|
validateJob: () => validateJob,
|
|
309
|
+
viewportOf: () => viewportOf,
|
|
294
310
|
visibleText: () => visibleText,
|
|
295
311
|
visualMeasures: () => visualMeasures,
|
|
296
312
|
vocabulary: () => vocabulary,
|
|
@@ -6857,14 +6873,14 @@ function cv(xs, min) {
|
|
|
6857
6873
|
}
|
|
6858
6874
|
function shapeOf(text) {
|
|
6859
6875
|
const ss = splitSentences(text);
|
|
6860
|
-
const
|
|
6861
|
-
const per100 = (n) =>
|
|
6876
|
+
const wordCount2 = words(text).length;
|
|
6877
|
+
const per100 = (n) => wordCount2 === 0 ? 0 : 100 * n / wordCount2;
|
|
6862
6878
|
const share2 = (re) => ss.length === 0 ? 0 : ss.filter((s) => re.test(s)).length / ss.length;
|
|
6863
6879
|
const tri = ss.flatMap(tricolons);
|
|
6864
6880
|
const commas = (text.match(/,/g) ?? []).length;
|
|
6865
6881
|
return {
|
|
6866
6882
|
sentences: ss.length,
|
|
6867
|
-
words:
|
|
6883
|
+
words: wordCount2,
|
|
6868
6884
|
tricolons: tri.length,
|
|
6869
6885
|
parallelTriads: tri.filter((t) => t.parallel).length,
|
|
6870
6886
|
stackedConditionals: ss.filter(stackedConditional).length,
|
|
@@ -8567,7 +8583,18 @@ var SOURCE_KINDS = [
|
|
|
8567
8583
|
var CHOICE_KEYS = ["accent", "ground", "display", "body", "shape", "motif", "signature"];
|
|
8568
8584
|
var CONSIDERATION = ["low", "considered", "high"];
|
|
8569
8585
|
var PAGE_TYPES = ["home", "service", "about", "pricing", "booking", "contact", "article", "landing"];
|
|
8586
|
+
var PROOF_KINDS = ["work", "people", "credentials", "reviews", "experience", "outcomes"];
|
|
8587
|
+
var PROOF_DEPTHS = ["first-look", "on-request"];
|
|
8588
|
+
var VIEWPORTS = ["mobile", "tablet", "desktop"];
|
|
8570
8589
|
var HIERARCHY_FIELDS = ["primaryAction", "order", "lead", "journey"];
|
|
8590
|
+
var CONFIDENCE_LENSES = ["relevance", "hierarchy", "evidence", "identity", "action", "disclosure", "personality", "differentiation", "mobile"];
|
|
8591
|
+
var REVIEW_BASES = ["observed", "inferred", "preference", "unknown"];
|
|
8592
|
+
var INSPECTED_KINDS = ["screenshot", "rendered", "source", "measurement", "content"];
|
|
8593
|
+
var REPORTED_KINDS = ["audit", "stakeholder", "customer"];
|
|
8594
|
+
var EVIDENCE_KINDS = [...INSPECTED_KINDS, ...REPORTED_KINDS];
|
|
8595
|
+
var CHANGE_CLASSES = ["confirmed-correction", "design-hypothesis", "optional-refinement", "no-change"];
|
|
8596
|
+
var CHANGE_STEPS = ["copy", "relocate", "group", "imagery", "trust-placement", "adjust-component", "remove-redundancy", "restructure", "new-component"];
|
|
8597
|
+
var ADDITIONS = ["carousel", "javascript", "dependency", "animation", "testimonial", "cta", "duplicate-content"];
|
|
8571
8598
|
|
|
8572
8599
|
// src/direction/reason.ts
|
|
8573
8600
|
var PROPOSED = "PROPOSED:";
|
|
@@ -8863,6 +8890,429 @@ function checkOrder(declared, page, snapshot, strict = false) {
|
|
|
8863
8890
|
return [{ severity: strict ? "error" : "warn", at: `hierarchy.${page}.order`, message: `the page runs ${rendered.join(", ")}; the file says ${said.join(", ")}` }];
|
|
8864
8891
|
}
|
|
8865
8892
|
|
|
8893
|
+
// src/direction/review.ts
|
|
8894
|
+
var isObject = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
8895
|
+
var wordCount = (x) => typeof x === "string" ? x.trim().split(/\s+/).filter(Boolean).length : 0;
|
|
8896
|
+
var includes = (list, x) => list.includes(x);
|
|
8897
|
+
var STEP_RANK = new Map(CHANGE_STEPS.map((s, i) => [s, i]));
|
|
8898
|
+
var CLASS_RANK = { "no-change": 0, "optional-refinement": 1, "design-hypothesis": 2, "confirmed-correction": 3 };
|
|
8899
|
+
var RENDERED_KINDS = ["screenshot", "rendered", "measurement"];
|
|
8900
|
+
var isStructural = (step) => step === "restructure" || step === "new-component";
|
|
8901
|
+
var GALLERY = /\b(?:gallery|galleries|carousel|slideshow|slider|swipe(?:able)?|rotating)\b/i;
|
|
8902
|
+
function plain2(at, text) {
|
|
8903
|
+
if (wordCount(text) < 8) return [{ severity: "error", at, message: "a sentence at least: say why, in this visitor's terms" }];
|
|
8904
|
+
const t = text;
|
|
8905
|
+
if (PREFERENCE.test(t)) return [{ severity: "error", at, message: "that is a preference, not a reason. Say what it does for this visitor." }];
|
|
8906
|
+
const c = t.match(CONVENTION);
|
|
8907
|
+
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.` }];
|
|
8908
|
+
return [];
|
|
8909
|
+
}
|
|
8910
|
+
function checkProposalShape(at, p, required) {
|
|
8911
|
+
const problems = [];
|
|
8912
|
+
const err = (rest, message) => problems.push({ severity: "error", at: `${at}${rest}`, message });
|
|
8913
|
+
if (!isObject(p)) {
|
|
8914
|
+
if (required) err("", "a proposal is an object: change, step, and optionally adds and touches");
|
|
8915
|
+
return problems;
|
|
8916
|
+
}
|
|
8917
|
+
if (wordCount(p.change) < 3) err(".change", "say what changes, in a few words");
|
|
8918
|
+
if (!includes(CHANGE_STEPS, p.step)) err(".step", `one of ${CHANGE_STEPS.join(", ")}: the smallest step that does it`);
|
|
8919
|
+
if (p.adds !== void 0) {
|
|
8920
|
+
if (!Array.isArray(p.adds) || p.adds.some((a) => !includes(ADDITIONS, a))) err(".adds", `a list drawn from ${ADDITIONS.join(", ")}`);
|
|
8921
|
+
}
|
|
8922
|
+
if (p.comparisons !== void 0) {
|
|
8923
|
+
if (!Array.isArray(p.comparisons)) err(".comparisons", "a list comparing each relevant existing component");
|
|
8924
|
+
else p.comparisons.forEach((c, i) => {
|
|
8925
|
+
if (!isObject(c) || typeof c.existing !== "string" || !c.existing.trim() || typeof c.samePurpose !== "boolean") {
|
|
8926
|
+
err(`.comparisons[${i}]`, "existing, samePurpose and because are required");
|
|
8927
|
+
} else problems.push(...plain2(`${at}.comparisons[${i}].because`, c.because));
|
|
8928
|
+
});
|
|
8929
|
+
}
|
|
8930
|
+
if (p.touches !== void 0 && !includes(SECTION_ROLES, p.touches)) err(".touches", `a section role: ${SECTION_ROLES.join(", ")}`);
|
|
8931
|
+
return problems;
|
|
8932
|
+
}
|
|
8933
|
+
function checkEvidenceShape(at, e) {
|
|
8934
|
+
const problems = [];
|
|
8935
|
+
const err = (rest, message) => problems.push({ severity: "error", at: `${at}${rest}`, message });
|
|
8936
|
+
if (!isObject(e)) return [{ severity: "error", at, message: "evidence is an object: kind, ref, and a viewport for anything rendered" }];
|
|
8937
|
+
if (!includes(EVIDENCE_KINDS, e.kind)) err(".kind", `one of ${EVIDENCE_KINDS.join(", ")}`);
|
|
8938
|
+
if (typeof e.ref !== "string" || !e.ref.trim()) err(".ref", "where it can be found: a screenshot path, a selector, a quotation, a report");
|
|
8939
|
+
if (e.viewport !== void 0 && !includes(VIEWPORTS, e.viewport)) err(".viewport", `one of ${VIEWPORTS.join(", ")}`);
|
|
8940
|
+
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");
|
|
8941
|
+
return problems;
|
|
8942
|
+
}
|
|
8943
|
+
function checkNoteShape(i, n) {
|
|
8944
|
+
const base = `notes[${i}]`;
|
|
8945
|
+
const problems = [];
|
|
8946
|
+
const err = (rest, message) => problems.push({ severity: "error", at: `${base}${rest}`, message });
|
|
8947
|
+
if (typeof n.id !== "string" || !n.id.trim()) err(".id", "a short name for this note");
|
|
8948
|
+
if (n.page !== void 0 && !includes(PAGE_TYPES, n.page)) err(".page", `one of ${PAGE_TYPES.join(", ")}`);
|
|
8949
|
+
if (!includes(CONFIDENCE_LENSES, n.lens)) err(".lens", `one of ${CONFIDENCE_LENSES.join(", ")}`);
|
|
8950
|
+
if (wordCount(n.observation) < 3) err(".observation", "what was said or seen, as it was said");
|
|
8951
|
+
if (typeof n.raisedBy !== "string" || !n.raisedBy.trim()) err(".raisedBy", "who said it, or what found it");
|
|
8952
|
+
if (!includes(REVIEW_BASES, n.basis)) err(".basis", `one of ${REVIEW_BASES.join(", ")}`);
|
|
8953
|
+
if (!Array.isArray(n.evidence)) err(".evidence", "a list, possibly empty: what supports the note");
|
|
8954
|
+
else n.evidence.forEach((e, j) => problems.push(...checkEvidenceShape(`${base}.evidence[${j}]`, e)));
|
|
8955
|
+
if (n.appliesTo !== void 0 && (!Array.isArray(n.appliesTo) || n.appliesTo.some((v) => !includes(VIEWPORTS, v)))) err(".appliesTo", `a list drawn from ${VIEWPORTS.join(", ")}`);
|
|
8956
|
+
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");
|
|
8957
|
+
if (n.suggested !== void 0) problems.push(...checkProposalShape(`${base}.suggested`, n.suggested, false));
|
|
8958
|
+
const r = n.recommend;
|
|
8959
|
+
if (!isObject(r)) {
|
|
8960
|
+
err(".recommend", "the recommendation: a class, and for anything but no-change a change, a step and a reason");
|
|
8961
|
+
return problems;
|
|
8962
|
+
}
|
|
8963
|
+
if (!includes(CHANGE_CLASSES, r.class)) {
|
|
8964
|
+
err(".recommend.class", `one of ${CHANGE_CLASSES.join(", ")}. no-change is a result, not a failure to answer.`);
|
|
8965
|
+
return problems;
|
|
8966
|
+
}
|
|
8967
|
+
if (r.class === "no-change") {
|
|
8968
|
+
if (r.because !== void 0) problems.push(...plain2(`${base}.recommend.because`, r.because));
|
|
8969
|
+
return problems;
|
|
8970
|
+
}
|
|
8971
|
+
problems.push(...checkProposalShape(`${base}.recommend`, r, true));
|
|
8972
|
+
problems.push(...plain2(`${base}.recommend.because`, r.because));
|
|
8973
|
+
const bigger = isStructural(r.step) || Array.isArray(r.adds) && r.adds.length > 0;
|
|
8974
|
+
if (bigger) {
|
|
8975
|
+
const smaller = Array.isArray(r.smaller) ? r.smaller : [];
|
|
8976
|
+
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");
|
|
8977
|
+
const limit = STEP_RANK.get(r.step) ?? 0;
|
|
8978
|
+
smaller.forEach((s, j) => {
|
|
8979
|
+
const ok = isObject(s) && includes(CHANGE_STEPS, s.step) && (limit === 0 || (STEP_RANK.get(s.step) ?? 99) < limit);
|
|
8980
|
+
if (!ok) err(`.recommend.smaller[${j}].step`, "a step on the ladder below the one recommended");
|
|
8981
|
+
if (isObject(s)) problems.push(...plain2(`${base}.recommend.smaller[${j}].whyNot`, s.whyNot));
|
|
8982
|
+
});
|
|
8983
|
+
} else {
|
|
8984
|
+
problems.push(...plain2(`${base}.recommend.whyEnough`, r.whyEnough));
|
|
8985
|
+
}
|
|
8986
|
+
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");
|
|
8987
|
+
if (r.requires !== void 0) {
|
|
8988
|
+
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");
|
|
8989
|
+
}
|
|
8990
|
+
return problems;
|
|
8991
|
+
}
|
|
8992
|
+
function whyNot(p, ctx) {
|
|
8993
|
+
const reasons = [];
|
|
8994
|
+
const adds = Array.isArray(p.adds) ? p.adds : [];
|
|
8995
|
+
if (adds.includes("carousel")) {
|
|
8996
|
+
for (const have of new Set(ctx.existing.filter((e) => GALLERY.test(e)))) {
|
|
8997
|
+
const compared = p.comparisons?.filter((c) => c.existing === have) ?? [];
|
|
8998
|
+
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.`);
|
|
8999
|
+
else if (compared[0].samePurpose) reasons.push(`the comparison identifies the same purpose as "${have}": ${compared[0].because}. Reuse or adjust that component.`);
|
|
9000
|
+
}
|
|
9001
|
+
}
|
|
9002
|
+
const defensible = (ctx.basis === "observed" || ctx.basis === "inferred") && ctx.impact;
|
|
9003
|
+
if ((isStructural(p.step) || adds.length > 0) && !defensible) {
|
|
9004
|
+
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.");
|
|
9005
|
+
}
|
|
9006
|
+
if (p.step === "remove-redundancy" && !(ctx.basis === "observed" && ctx.impact)) {
|
|
9007
|
+
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.");
|
|
9008
|
+
}
|
|
9009
|
+
if (p.touches && ctx.placed.has(p.touches) && ctx.basis !== "observed") {
|
|
9010
|
+
reasons.push(`the declared order already places "${p.touches}" with a reason. Only something observed overrides a reasoned decision.`);
|
|
9011
|
+
}
|
|
9012
|
+
return reasons;
|
|
9013
|
+
}
|
|
9014
|
+
function widthsOf(evidence) {
|
|
9015
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9016
|
+
for (const e of evidence) {
|
|
9017
|
+
if (e.viewport) seen.add(e.viewport);
|
|
9018
|
+
else if (e.kind === "content") VIEWPORTS.forEach((v) => seen.add(v));
|
|
9019
|
+
}
|
|
9020
|
+
return VIEWPORTS.filter((v) => seen.has(v));
|
|
9021
|
+
}
|
|
9022
|
+
function assessReview(input, direction) {
|
|
9023
|
+
const problems = [];
|
|
9024
|
+
const results = [];
|
|
9025
|
+
const fail = (at, message) => problems.push({ severity: "error", at, message });
|
|
9026
|
+
const report2 = () => {
|
|
9027
|
+
const n = (o) => results.filter((r) => r.outcome === o).length;
|
|
9028
|
+
const summary = { change: n("change"), hold: n("hold"), noChange: n("no-change"), gatherEvidence: n("gather-evidence"), verdict: "" };
|
|
9029
|
+
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.`;
|
|
9030
|
+
return { valid: !problems.some((p) => p.severity === "error"), problems, results, summary };
|
|
9031
|
+
};
|
|
9032
|
+
if (!isObject(input)) {
|
|
9033
|
+
fail("", "a review is an object: version, subject and notes");
|
|
9034
|
+
return report2();
|
|
9035
|
+
}
|
|
9036
|
+
if (input.context !== void 0 && !["publication", "protected-staging"].includes(input.context)) fail("context", "publication or protected-staging");
|
|
9037
|
+
if (input.version !== 1) fail("version", "version is 1");
|
|
9038
|
+
if (typeof input.subject !== "string" || !input.subject.trim()) fail("subject", "who or what was reviewed");
|
|
9039
|
+
if (!Array.isArray(input.notes)) {
|
|
9040
|
+
fail("notes", "a list of notes");
|
|
9041
|
+
return report2();
|
|
9042
|
+
}
|
|
9043
|
+
const job = direction.job;
|
|
9044
|
+
if (!job) {
|
|
9045
|
+
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).");
|
|
9046
|
+
return report2();
|
|
9047
|
+
}
|
|
9048
|
+
const ids = /* @__PURE__ */ new Set();
|
|
9049
|
+
input.notes.forEach((raw, i) => {
|
|
9050
|
+
if (!isObject(raw)) return fail(`notes[${i}]`, "a note is an object");
|
|
9051
|
+
const shape = checkNoteShape(i, raw);
|
|
9052
|
+
if (typeof raw.id === "string") {
|
|
9053
|
+
if (ids.has(raw.id)) shape.push({ severity: "error", at: `notes[${i}].id`, message: `"${raw.id}" is used twice` });
|
|
9054
|
+
ids.add(raw.id);
|
|
9055
|
+
}
|
|
9056
|
+
problems.push(...shape);
|
|
9057
|
+
if (shape.length > 0) return;
|
|
9058
|
+
const note = raw;
|
|
9059
|
+
const page = note.page ?? "home";
|
|
9060
|
+
const adjustments = [];
|
|
9061
|
+
const hierarchy = direction.hierarchy?.[page];
|
|
9062
|
+
const sources = Array.isArray(direction.sources) ? direction.sources : [];
|
|
9063
|
+
const inspected = note.evidence.filter((e) => includes(INSPECTED_KINDS, e.kind));
|
|
9064
|
+
let basis = note.basis;
|
|
9065
|
+
if (basis === "observed" && inspected.length === 0) {
|
|
9066
|
+
basis = note.evidence.length > 0 ? "inferred" : "unknown";
|
|
9067
|
+
adjustments.push(
|
|
9068
|
+
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."
|
|
9069
|
+
);
|
|
9070
|
+
} else if (basis === "inferred" && note.evidence.length === 0) {
|
|
9071
|
+
basis = "unknown";
|
|
9072
|
+
adjustments.push("declared inferred, but nothing supports it. Read as unknown.");
|
|
9073
|
+
}
|
|
9074
|
+
const impactProblems = typeof note.impact === "string" ? checkReason({ at: `notes[${i}].impact`, because: note.impact, sources, tie: "job", job }) : [];
|
|
9075
|
+
const impact = typeof note.impact === "string" && impactProblems.length === 0;
|
|
9076
|
+
const wantsChange = note.recommend.class !== "no-change";
|
|
9077
|
+
if (!impact && (wantsChange || note.suggested)) {
|
|
9078
|
+
adjustments.push(
|
|
9079
|
+
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."
|
|
9080
|
+
);
|
|
9081
|
+
if (basis === "inferred") basis = "preference";
|
|
9082
|
+
}
|
|
9083
|
+
const ctx = {
|
|
9084
|
+
note,
|
|
9085
|
+
basis,
|
|
9086
|
+
impact,
|
|
9087
|
+
existing: [...note.existing ?? [], ...(hierarchy?.confidence?.proof ?? []).map((p) => p.what).filter((w) => typeof w === "string")],
|
|
9088
|
+
placed: new Set((hierarchy?.order ?? []).map((o) => o.role))
|
|
9089
|
+
};
|
|
9090
|
+
const examined = widthsOf(note.evidence);
|
|
9091
|
+
const wanted = note.appliesTo ?? (examined.length > 0 ? examined : [...VIEWPORTS]);
|
|
9092
|
+
const claimed = wanted.filter((v) => examined.includes(v));
|
|
9093
|
+
const unknown = VIEWPORTS.filter((v) => wanted.includes(v) && !examined.includes(v));
|
|
9094
|
+
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.`);
|
|
9095
|
+
const rec = note.recommend;
|
|
9096
|
+
const recReasons = wantsChange ? whyNot(rec, ctx) : [];
|
|
9097
|
+
const sugReasons = note.suggested ? whyNot(note.suggested, ctx) : [];
|
|
9098
|
+
const ceiling = basis === "observed" ? impact ? "confirmed-correction" : "optional-refinement" : basis === "inferred" ? "design-hypothesis" : basis === "preference" ? "optional-refinement" : "no-change";
|
|
9099
|
+
let effective = wantsChange ? rec.class : "no-change";
|
|
9100
|
+
if (basis !== "unknown" && CLASS_RANK[effective] > CLASS_RANK[ceiling]) {
|
|
9101
|
+
adjustments.push(`declared ${rec.class}, but a problem that is ${basis}${basis === "observed" && !impact ? " with no stated visitor impact" : ""} supports at most ${ceiling}.`);
|
|
9102
|
+
effective = ceiling;
|
|
9103
|
+
}
|
|
9104
|
+
let withdrawn;
|
|
9105
|
+
if (wantsChange && recReasons.length > 0) {
|
|
9106
|
+
withdrawn = recReasons;
|
|
9107
|
+
effective = "no-change";
|
|
9108
|
+
adjustments.push("the recommended change is withdrawn: " + recReasons.join(" "));
|
|
9109
|
+
}
|
|
9110
|
+
const missing = (rec.requires ?? []).filter((q) => !q.source || !sources.some((s) => s.id === q.source));
|
|
9111
|
+
const outstanding = missing.map((q) => q.what);
|
|
9112
|
+
const blocking = missing.filter((q) => !(input.context === "protected-staging" && q.kind === "asset" && q.phase === "publication" && q.placeholder));
|
|
9113
|
+
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.");
|
|
9114
|
+
let outcome;
|
|
9115
|
+
if (basis === "unknown") {
|
|
9116
|
+
outcome = "gather-evidence";
|
|
9117
|
+
effective = "no-change";
|
|
9118
|
+
if (wantsChange) adjustments.push(`nothing is recommended until the problem is established${rec.validation ? `: ${rec.validation}` : ". Ask the client, or look, first."}`);
|
|
9119
|
+
} else if (effective === "no-change") outcome = "no-change";
|
|
9120
|
+
else if (blocking.length > 0) outcome = "hold";
|
|
9121
|
+
else outcome = "change";
|
|
9122
|
+
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.`);
|
|
9123
|
+
let suggested;
|
|
9124
|
+
if (note.suggested) {
|
|
9125
|
+
const recRank = STEP_RANK.get(rec.step ?? "") ?? -1;
|
|
9126
|
+
const sugRank = STEP_RANK.get(note.suggested.step) ?? 0;
|
|
9127
|
+
const sugAdds = (note.suggested.adds ?? []).length;
|
|
9128
|
+
let standing;
|
|
9129
|
+
const reasons = [...sugReasons];
|
|
9130
|
+
if (sugReasons.length > 0) standing = "not-supported";
|
|
9131
|
+
else if (effective === "no-change") {
|
|
9132
|
+
standing = "not-needed";
|
|
9133
|
+
reasons.push("the existing design already answers the problem, or nothing is yet established to answer.");
|
|
9134
|
+
} else if (sugRank > recRank || sugAdds > 0 && (rec.adds ?? []).length === 0) {
|
|
9135
|
+
standing = "comparison-needed";
|
|
9136
|
+
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.");
|
|
9137
|
+
} else standing = "supported";
|
|
9138
|
+
suggested = { standing, reasons };
|
|
9139
|
+
}
|
|
9140
|
+
results.push({
|
|
9141
|
+
id: note.id,
|
|
9142
|
+
page,
|
|
9143
|
+
lens: note.lens,
|
|
9144
|
+
basis: { declared: note.basis, effective: basis },
|
|
9145
|
+
...suggested ? { suggested } : {},
|
|
9146
|
+
recommend: { declared: rec.class, effective, ...wantsChange ? { step: rec.step } : {}, ...withdrawn ? { withdrawn } : {} },
|
|
9147
|
+
outcome,
|
|
9148
|
+
widths: { examined, claimed, unknown },
|
|
9149
|
+
adjustments,
|
|
9150
|
+
outstanding,
|
|
9151
|
+
publicationReady: outstanding.length === 0
|
|
9152
|
+
});
|
|
9153
|
+
});
|
|
9154
|
+
return report2();
|
|
9155
|
+
}
|
|
9156
|
+
|
|
9157
|
+
// src/direction/confidence.ts
|
|
9158
|
+
function viewportOf(width) {
|
|
9159
|
+
return width < 768 ? "mobile" : width < 1024 ? "tablet" : "desktop";
|
|
9160
|
+
}
|
|
9161
|
+
var isObject2 = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
9162
|
+
function validateConfidence(page, confidence, job, sources) {
|
|
9163
|
+
const problems = [];
|
|
9164
|
+
const base = `hierarchy.${page}.confidence`;
|
|
9165
|
+
const err = (at, message) => problems.push({ severity: "error", at: `${base}${at}`, message });
|
|
9166
|
+
if (!isObject2(confidence)) {
|
|
9167
|
+
err("", "confidence is an object: `examined` (the widths looked at) and `proof` (what the page offers)");
|
|
9168
|
+
return problems;
|
|
9169
|
+
}
|
|
9170
|
+
if (confidence.examined !== void 0) {
|
|
9171
|
+
if (!Array.isArray(confidence.examined)) err(".examined", `a list of widths: ${VIEWPORTS.join(", ")}`);
|
|
9172
|
+
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(", ")}`);
|
|
9173
|
+
}
|
|
9174
|
+
if (confidence.proof !== void 0 && !Array.isArray(confidence.proof)) err(".proof", "a list of evidence items");
|
|
9175
|
+
const proof = Array.isArray(confidence.proof) ? confidence.proof : [];
|
|
9176
|
+
proof.forEach((raw, i) => {
|
|
9177
|
+
const at = `.proof[${i}]`;
|
|
9178
|
+
if (!isObject2(raw)) return err(at, "an evidence item is an object: kind, what, position, depth, because");
|
|
9179
|
+
if (!PROOF_KINDS.includes(raw.kind)) err(`${at}.kind`, `one of ${PROOF_KINDS.join(", ")}`);
|
|
9180
|
+
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"');
|
|
9181
|
+
if (typeof raw.position !== "string" || !POSITIONS.has(raw.position)) err(`${at}.position`, 'the section it sits in, or "header" or "sticky"');
|
|
9182
|
+
if (!PROOF_DEPTHS.includes(raw.depth)) err(`${at}.depth`, `${PROOF_DEPTHS.join(" or ")}: met without being sought, or detail a visitor opens`);
|
|
9183
|
+
problems.push(...checkReason({ at: `${base}${at}`, because: raw.because, evidence: raw.evidence, sources, tie: "job", job }));
|
|
9184
|
+
});
|
|
9185
|
+
return problems;
|
|
9186
|
+
}
|
|
9187
|
+
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;
|
|
9188
|
+
var LATE_SECTION = 3;
|
|
9189
|
+
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(" ");
|
|
9190
|
+
var lensOf = (kind) => kind === "people" ? "identity" : "evidence";
|
|
9191
|
+
function decisionText(h) {
|
|
9192
|
+
const proof = Array.isArray(h.confidence?.proof) ? h.confidence.proof : [];
|
|
9193
|
+
return [
|
|
9194
|
+
h.primaryAction?.value,
|
|
9195
|
+
h.primaryAction?.because,
|
|
9196
|
+
h.lead?.value,
|
|
9197
|
+
h.lead?.because,
|
|
9198
|
+
...Array.isArray(h.order) ? h.order.map((o) => o?.because) : [],
|
|
9199
|
+
...Array.isArray(h.journey) ? h.journey.map((s) => s?.because) : [],
|
|
9200
|
+
...proof.flatMap((p) => isObject2(p) ? [p.what, p.because] : [])
|
|
9201
|
+
].filter((x) => typeof x === "string").join(" ");
|
|
9202
|
+
}
|
|
9203
|
+
function assessConfidence(hierarchy, job, ctx = {}) {
|
|
9204
|
+
const findings = [];
|
|
9205
|
+
if (!isObject2(hierarchy)) return findings;
|
|
9206
|
+
for (const [key, raw] of Object.entries(hierarchy)) {
|
|
9207
|
+
const h = raw;
|
|
9208
|
+
if (!PAGE_TYPES.includes(key) || !isObject2(h) || !isObject2(h.confidence)) continue;
|
|
9209
|
+
const page = key;
|
|
9210
|
+
const at = (rest) => `hierarchy.${page}.confidence${rest}`;
|
|
9211
|
+
const add = (f4) => findings.push({ page, ...f4 });
|
|
9212
|
+
const c = h.confidence;
|
|
9213
|
+
const proof = (Array.isArray(c.proof) ? c.proof : []).filter(isObject2);
|
|
9214
|
+
const order = new Set((Array.isArray(h.order) ? h.order : []).map((o) => o?.role));
|
|
9215
|
+
const bands = new Set((Array.isArray(c.examined) ? c.examined : []).filter((v) => VIEWPORTS.includes(v)));
|
|
9216
|
+
if (ctx.snapshot && (ctx.snapshotPage ?? "home") === page) bands.add(viewportOf(ctx.snapshot.viewport.width));
|
|
9217
|
+
for (const pos of Array.isArray(h.primaryAction?.positions) ? h.primaryAction.positions : []) {
|
|
9218
|
+
if (POSITIONS.has(pos) && pos !== "header" && pos !== "sticky" && !order.has(pos)) {
|
|
9219
|
+
add({
|
|
9220
|
+
lens: "action",
|
|
9221
|
+
basis: "observed",
|
|
9222
|
+
at: `hierarchy.${page}.primaryAction.positions`,
|
|
9223
|
+
message: `the ask "${h.primaryAction.value}" sits in "${pos}", and the declared order has no such section`,
|
|
9224
|
+
why: "A visitor can only take the next step where the page offers it. Either the order or the position is out of date."
|
|
9225
|
+
});
|
|
9226
|
+
}
|
|
9227
|
+
}
|
|
9228
|
+
proof.forEach((p, i) => {
|
|
9229
|
+
const where = at(`.proof[${i}]`);
|
|
9230
|
+
if (POSITIONS.has(p.position) && p.position !== "header" && p.position !== "sticky" && !order.has(p.position)) {
|
|
9231
|
+
add({
|
|
9232
|
+
lens: "hierarchy",
|
|
9233
|
+
basis: "observed",
|
|
9234
|
+
at: `${where}.position`,
|
|
9235
|
+
message: `${p.kind} evidence "${p.what}" sits in "${p.position}", and the declared order has no such section`,
|
|
9236
|
+
why: "Evidence a visitor is meant to meet has to be on the page, in a section the order names."
|
|
9237
|
+
});
|
|
9238
|
+
}
|
|
9239
|
+
const cited = Array.isArray(p.evidence) ? p.evidence : [];
|
|
9240
|
+
if (cited.length === 0) {
|
|
9241
|
+
add({
|
|
9242
|
+
lens: lensOf(p.kind),
|
|
9243
|
+
basis: "unknown",
|
|
9244
|
+
at: `${where}.evidence`,
|
|
9245
|
+
message: `no source in the file shows that "${p.what}" exists`,
|
|
9246
|
+
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."
|
|
9247
|
+
});
|
|
9248
|
+
}
|
|
9249
|
+
});
|
|
9250
|
+
const firstLook = new Set(proof.filter((p) => p.depth === "first-look").map((p) => p.kind));
|
|
9251
|
+
proof.forEach((p, i) => {
|
|
9252
|
+
if (p.depth === "on-request" && !firstLook.has(p.kind)) {
|
|
9253
|
+
add({
|
|
9254
|
+
lens: "disclosure",
|
|
9255
|
+
basis: "inferred",
|
|
9256
|
+
at: at(`.proof[${i}].depth`),
|
|
9257
|
+
message: `${p.kind} evidence "${p.what}" is held back for visitors who seek it, and nothing of the same kind is met first`,
|
|
9258
|
+
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."
|
|
9259
|
+
});
|
|
9260
|
+
}
|
|
9261
|
+
});
|
|
9262
|
+
if (job && IDENTITY_WORRY.test(identityText(job))) {
|
|
9263
|
+
const sections = Array.isArray(h.order) ? h.order.map((o) => o?.role) : [];
|
|
9264
|
+
const before = (pos) => pos === "header" || pos === "sticky" ? 0 : sections.indexOf(pos);
|
|
9265
|
+
const met = proof.filter((p) => p.kind === "people" && before(p.position) >= 0).map((p) => ({ position: p.position, before: before(p.position) }));
|
|
9266
|
+
const earliest = met.length > 0 ? met.reduce((a, b) => b.before < a.before ? b : a) : void 0;
|
|
9267
|
+
if (earliest && earliest.before >= LATE_SECTION) {
|
|
9268
|
+
add({
|
|
9269
|
+
lens: "identity",
|
|
9270
|
+
basis: "inferred",
|
|
9271
|
+
at: at(".proof"),
|
|
9272
|
+
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`,
|
|
9273
|
+
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."}`
|
|
9274
|
+
});
|
|
9275
|
+
}
|
|
9276
|
+
}
|
|
9277
|
+
if (job && Array.isArray(job.objections)) {
|
|
9278
|
+
const said = contentWords(decisionText(h));
|
|
9279
|
+
job.objections.forEach((o, i) => {
|
|
9280
|
+
if (typeof o !== "string") return;
|
|
9281
|
+
if (shares(contentWords(o), said).length === 0) {
|
|
9282
|
+
add({
|
|
9283
|
+
lens: "relevance",
|
|
9284
|
+
basis: "unknown",
|
|
9285
|
+
at: `job.objections[${i}]`,
|
|
9286
|
+
message: `no decision recorded for this page names the worry "${o}"`,
|
|
9287
|
+
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."
|
|
9288
|
+
});
|
|
9289
|
+
}
|
|
9290
|
+
});
|
|
9291
|
+
}
|
|
9292
|
+
if (bands.size === 0) {
|
|
9293
|
+
add({
|
|
9294
|
+
lens: "mobile",
|
|
9295
|
+
basis: "unknown",
|
|
9296
|
+
at: at(".examined"),
|
|
9297
|
+
message: "no width recorded: say which widths these decisions were examined at",
|
|
9298
|
+
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."
|
|
9299
|
+
});
|
|
9300
|
+
} else {
|
|
9301
|
+
for (const band of VIEWPORTS) {
|
|
9302
|
+
if (bands.has(band)) continue;
|
|
9303
|
+
add({
|
|
9304
|
+
lens: "mobile",
|
|
9305
|
+
basis: "unknown",
|
|
9306
|
+
at: at(".examined"),
|
|
9307
|
+
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`,
|
|
9308
|
+
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."
|
|
9309
|
+
});
|
|
9310
|
+
}
|
|
9311
|
+
}
|
|
9312
|
+
}
|
|
9313
|
+
return findings;
|
|
9314
|
+
}
|
|
9315
|
+
|
|
8866
9316
|
// src/direction/research.ts
|
|
8867
9317
|
function tradeOf(brief) {
|
|
8868
9318
|
const text = brief.toLowerCase();
|
|
@@ -8893,14 +9343,17 @@ function researchPrompt(input) {
|
|
|
8893
9343
|
"4. **Their words.** The phrases they actually use for the problem and for what they want. Quote them exactly.",
|
|
8894
9344
|
"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?",
|
|
8895
9345
|
"6. **Consideration.** Do they decide in minutes, days or months? What do they compare before deciding?",
|
|
8896
|
-
"7. **Where they look.** What do they search for, and where do they ask?",
|
|
9346
|
+
"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?",
|
|
9347
|
+
"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.",
|
|
9348
|
+
"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?",
|
|
8897
9349
|
"",
|
|
8898
9350
|
"## Do not",
|
|
8899
9351
|
"",
|
|
8900
9352
|
`- Do not describe, list, rank or summarise what ${category} websites look like, or how they are laid out.`,
|
|
8901
9353
|
'- Do not suggest colours, fonts, layouts, sections or visual "inspiration" from this category.',
|
|
8902
9354
|
"- Do not summarise competitors' marketing. Their claims are the category average; the customer's words are the point.",
|
|
8903
|
-
"- Do not invent quotes. If you cannot find a source for something, say so."
|
|
9355
|
+
"- Do not invent quotes. If you cannot find a source for something, say so.",
|
|
9356
|
+
"- 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."
|
|
8904
9357
|
];
|
|
8905
9358
|
if (input.visual) {
|
|
8906
9359
|
lines.push(
|
|
@@ -9231,6 +9684,7 @@ function validateDirection(input, ctx = {}) {
|
|
|
9231
9684
|
}
|
|
9232
9685
|
let job = false;
|
|
9233
9686
|
let hierarchy = [];
|
|
9687
|
+
let confidence;
|
|
9234
9688
|
if (d.version === 2) {
|
|
9235
9689
|
const j = validateJob(d.job, sources, { brief: typeof d.brief === "string" ? d.brief : "", readSource: ctx.readSource });
|
|
9236
9690
|
problems.push(...j.problems);
|
|
@@ -9238,6 +9692,10 @@ function validateDirection(input, ctx = {}) {
|
|
|
9238
9692
|
const h = validateHierarchy(d.hierarchy, d.job, sources);
|
|
9239
9693
|
problems.push(...h.problems);
|
|
9240
9694
|
hierarchy = h.pages;
|
|
9695
|
+
const pages = d.hierarchy && typeof d.hierarchy === "object" && !Array.isArray(d.hierarchy) ? d.hierarchy : {};
|
|
9696
|
+
const recorded = Object.entries(pages).filter(([k, p]) => PAGE_TYPES.includes(k) && p && typeof p === "object" && p.confidence !== void 0);
|
|
9697
|
+
for (const [page, p] of recorded) problems.push(...validateConfidence(page, p.confidence, d.job, sources));
|
|
9698
|
+
if (recorded.length) confidence = assessConfidence(d.hierarchy, d.job, { snapshot: ctx.snapshot, snapshotPage: ctx.page });
|
|
9241
9699
|
if (ctx.snapshot) problems.push(...checkOrder(d.hierarchy?.[ctx.page ?? "home"], ctx.page ?? "home", ctx.snapshot, ctx.strict));
|
|
9242
9700
|
}
|
|
9243
9701
|
if (ctx.estate?.length) {
|
|
@@ -9254,6 +9712,7 @@ function validateDirection(input, ctx = {}) {
|
|
|
9254
9712
|
valid: problems.every((p) => p.severity !== "error"),
|
|
9255
9713
|
problems,
|
|
9256
9714
|
decided,
|
|
9715
|
+
...confidence ? { confidence } : {},
|
|
9257
9716
|
layers: { job, hierarchy, tokens: { decided, total: CHOICE_KEYS.length }, complete: complete && problems.every((p) => p.severity !== "error") }
|
|
9258
9717
|
};
|
|
9259
9718
|
}
|
|
@@ -9812,6 +10271,7 @@ function retrofitPlan(report2, direction) {
|
|
|
9812
10271
|
0 && (module.exports = {
|
|
9813
10272
|
ACCENT_SCALE,
|
|
9814
10273
|
ACCENT_SHARED,
|
|
10274
|
+
ADDITIONS,
|
|
9815
10275
|
AI_PHRASES,
|
|
9816
10276
|
AI_SOURCE_TYPES,
|
|
9817
10277
|
AI_VIOLET,
|
|
@@ -9820,8 +10280,11 @@ function retrofitPlan(report2, direction) {
|
|
|
9820
10280
|
BUZZWORDS,
|
|
9821
10281
|
CATALOGUE,
|
|
9822
10282
|
CATALOGUE_VERSION,
|
|
10283
|
+
CHANGE_CLASSES,
|
|
10284
|
+
CHANGE_STEPS,
|
|
9823
10285
|
CHOICE_KEYS,
|
|
9824
10286
|
COMPONENT_ROLES,
|
|
10287
|
+
CONFIDENCE_LENSES,
|
|
9825
10288
|
CONSIDERATION,
|
|
9826
10289
|
CONTENT_FEATURES,
|
|
9827
10290
|
CONVENTION,
|
|
@@ -9838,6 +10301,7 @@ function retrofitPlan(report2, direction) {
|
|
|
9838
10301
|
EASE_TUPLE,
|
|
9839
10302
|
EDGE_STEP,
|
|
9840
10303
|
ESTATE_VERSION,
|
|
10304
|
+
EVIDENCE_KINDS,
|
|
9841
10305
|
EXIT_RATIO,
|
|
9842
10306
|
FINGERPRINT_VERSION,
|
|
9843
10307
|
FLAGGED_TARGET,
|
|
@@ -9853,6 +10317,7 @@ function retrofitPlan(report2, direction) {
|
|
|
9853
10317
|
HOUSE_TYPE,
|
|
9854
10318
|
HUMAN_TYPICAL_TARGET,
|
|
9855
10319
|
IMAGERY_TELLS,
|
|
10320
|
+
INSPECTED_KINDS,
|
|
9856
10321
|
JSON_SCHEMA_VERSION,
|
|
9857
10322
|
LABELS,
|
|
9858
10323
|
LEXICAL_FEATURES,
|
|
@@ -9873,6 +10338,8 @@ function retrofitPlan(report2, direction) {
|
|
|
9873
10338
|
PROFILE_MEASURED,
|
|
9874
10339
|
PROFILE_PARAGRAPHS,
|
|
9875
10340
|
PROMO_BADGE,
|
|
10341
|
+
PROOF_DEPTHS,
|
|
10342
|
+
PROOF_KINDS,
|
|
9876
10343
|
PROOF_NAMES,
|
|
9877
10344
|
PROPOSED,
|
|
9878
10345
|
PROVENANCE_SCAN_BYTES,
|
|
@@ -9886,7 +10353,9 @@ function retrofitPlan(report2, direction) {
|
|
|
9886
10353
|
REGISTER_IDS,
|
|
9887
10354
|
RENDERED_PATHS,
|
|
9888
10355
|
RENDERED_TELLS,
|
|
10356
|
+
REPORTED_KINDS,
|
|
9889
10357
|
REVEAL_LIMIT,
|
|
10358
|
+
REVIEW_BASES,
|
|
9890
10359
|
REVIEW_PHRASES,
|
|
9891
10360
|
SAMPLES_PER_SEGMENT,
|
|
9892
10361
|
SCALE,
|
|
@@ -9915,12 +10384,15 @@ function retrofitPlan(report2, direction) {
|
|
|
9915
10384
|
TYPE_STEPS,
|
|
9916
10385
|
TYPICAL_AT,
|
|
9917
10386
|
VAGUE_WORDS,
|
|
10387
|
+
VIEWPORTS,
|
|
9918
10388
|
accentFork,
|
|
9919
10389
|
addSite,
|
|
9920
10390
|
apcaContrast,
|
|
9921
10391
|
applyBaseline,
|
|
9922
10392
|
applyHouseGate,
|
|
9923
10393
|
applySeverity,
|
|
10394
|
+
assessConfidence,
|
|
10395
|
+
assessReview,
|
|
9924
10396
|
auditSnapshot,
|
|
9925
10397
|
bandOf,
|
|
9926
10398
|
briefTerms,
|
|
@@ -10008,6 +10480,7 @@ function retrofitPlan(report2, direction) {
|
|
|
10008
10480
|
isCategoryLabel,
|
|
10009
10481
|
isCream,
|
|
10010
10482
|
isFlagged,
|
|
10483
|
+
isStructural,
|
|
10011
10484
|
jobWords,
|
|
10012
10485
|
layoutDistance,
|
|
10013
10486
|
lexicalOf,
|
|
@@ -10078,9 +10551,11 @@ function retrofitPlan(report2, direction) {
|
|
|
10078
10551
|
typeFeatureTokens,
|
|
10079
10552
|
typicality,
|
|
10080
10553
|
unprovenClaims,
|
|
10554
|
+
validateConfidence,
|
|
10081
10555
|
validateDirection,
|
|
10082
10556
|
validateHierarchy,
|
|
10083
10557
|
validateJob,
|
|
10558
|
+
viewportOf,
|
|
10084
10559
|
visibleText,
|
|
10085
10560
|
visualMeasures,
|
|
10086
10561
|
vocabulary,
|