@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
|
@@ -39,7 +39,18 @@ var SOURCE_KINDS = [
|
|
|
39
39
|
var CHOICE_KEYS = ["accent", "ground", "display", "body", "shape", "motif", "signature"];
|
|
40
40
|
var CONSIDERATION = ["low", "considered", "high"];
|
|
41
41
|
var PAGE_TYPES = ["home", "service", "about", "pricing", "booking", "contact", "article", "landing"];
|
|
42
|
+
var PROOF_KINDS = ["work", "people", "credentials", "reviews", "experience", "outcomes"];
|
|
43
|
+
var PROOF_DEPTHS = ["first-look", "on-request"];
|
|
44
|
+
var VIEWPORTS = ["mobile", "tablet", "desktop"];
|
|
42
45
|
var HIERARCHY_FIELDS = ["primaryAction", "order", "lead", "journey"];
|
|
46
|
+
var CONFIDENCE_LENSES = ["relevance", "hierarchy", "evidence", "identity", "action", "disclosure", "personality", "differentiation", "mobile"];
|
|
47
|
+
var REVIEW_BASES = ["observed", "inferred", "preference", "unknown"];
|
|
48
|
+
var INSPECTED_KINDS = ["screenshot", "rendered", "source", "measurement", "content"];
|
|
49
|
+
var REPORTED_KINDS = ["audit", "stakeholder", "customer"];
|
|
50
|
+
var EVIDENCE_KINDS = [...INSPECTED_KINDS, ...REPORTED_KINDS];
|
|
51
|
+
var CHANGE_CLASSES = ["confirmed-correction", "design-hypothesis", "optional-refinement", "no-change"];
|
|
52
|
+
var CHANGE_STEPS = ["copy", "relocate", "group", "imagery", "trust-placement", "adjust-component", "remove-redundancy", "restructure", "new-component"];
|
|
53
|
+
var ADDITIONS = ["carousel", "javascript", "dependency", "animation", "testimonial", "cta", "duplicate-content"];
|
|
43
54
|
|
|
44
55
|
// src/direction/research.ts
|
|
45
56
|
function tradeOf(brief) {
|
|
@@ -71,14 +82,17 @@ function researchPrompt(input) {
|
|
|
71
82
|
"4. **Their words.** The phrases they actually use for the problem and for what they want. Quote them exactly.",
|
|
72
83
|
"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?",
|
|
73
84
|
"6. **Consideration.** Do they decide in minutes, days or months? What do they compare before deciding?",
|
|
74
|
-
"7. **Where they look.** What do they search for, and where do they ask?",
|
|
85
|
+
"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?",
|
|
86
|
+
"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.",
|
|
87
|
+
"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?",
|
|
75
88
|
"",
|
|
76
89
|
"## Do not",
|
|
77
90
|
"",
|
|
78
91
|
`- Do not describe, list, rank or summarise what ${category} websites look like, or how they are laid out.`,
|
|
79
92
|
'- Do not suggest colours, fonts, layouts, sections or visual "inspiration" from this category.',
|
|
80
93
|
"- Do not summarise competitors' marketing. Their claims are the category average; the customer's words are the point.",
|
|
81
|
-
"- Do not invent quotes. If you cannot find a source for something, say so."
|
|
94
|
+
"- Do not invent quotes. If you cannot find a source for something, say so.",
|
|
95
|
+
"- 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."
|
|
82
96
|
];
|
|
83
97
|
if (input.visual) {
|
|
84
98
|
lines.push(
|
|
@@ -370,6 +384,165 @@ function checkOrder(declared, page, snapshot, strict = false) {
|
|
|
370
384
|
return [{ severity: strict ? "error" : "warn", at: `hierarchy.${page}.order`, message: `the page runs ${rendered.join(", ")}; the file says ${said.join(", ")}` }];
|
|
371
385
|
}
|
|
372
386
|
|
|
387
|
+
// src/direction/confidence.ts
|
|
388
|
+
function viewportOf(width) {
|
|
389
|
+
return width < 768 ? "mobile" : width < 1024 ? "tablet" : "desktop";
|
|
390
|
+
}
|
|
391
|
+
var isObject = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
392
|
+
function validateConfidence(page, confidence, job, sources) {
|
|
393
|
+
const problems = [];
|
|
394
|
+
const base = `hierarchy.${page}.confidence`;
|
|
395
|
+
const err = (at, message) => problems.push({ severity: "error", at: `${base}${at}`, message });
|
|
396
|
+
if (!isObject(confidence)) {
|
|
397
|
+
err("", "confidence is an object: `examined` (the widths looked at) and `proof` (what the page offers)");
|
|
398
|
+
return problems;
|
|
399
|
+
}
|
|
400
|
+
if (confidence.examined !== void 0) {
|
|
401
|
+
if (!Array.isArray(confidence.examined)) err(".examined", `a list of widths: ${VIEWPORTS.join(", ")}`);
|
|
402
|
+
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(", ")}`);
|
|
403
|
+
}
|
|
404
|
+
if (confidence.proof !== void 0 && !Array.isArray(confidence.proof)) err(".proof", "a list of evidence items");
|
|
405
|
+
const proof = Array.isArray(confidence.proof) ? confidence.proof : [];
|
|
406
|
+
proof.forEach((raw, i) => {
|
|
407
|
+
const at = `.proof[${i}]`;
|
|
408
|
+
if (!isObject(raw)) return err(at, "an evidence item is an object: kind, what, position, depth, because");
|
|
409
|
+
if (!PROOF_KINDS.includes(raw.kind)) err(`${at}.kind`, `one of ${PROOF_KINDS.join(", ")}`);
|
|
410
|
+
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"');
|
|
411
|
+
if (typeof raw.position !== "string" || !POSITIONS.has(raw.position)) err(`${at}.position`, 'the section it sits in, or "header" or "sticky"');
|
|
412
|
+
if (!PROOF_DEPTHS.includes(raw.depth)) err(`${at}.depth`, `${PROOF_DEPTHS.join(" or ")}: met without being sought, or detail a visitor opens`);
|
|
413
|
+
problems.push(...checkReason({ at: `${base}${at}`, because: raw.because, evidence: raw.evidence, sources, tie: "job", job }));
|
|
414
|
+
});
|
|
415
|
+
return problems;
|
|
416
|
+
}
|
|
417
|
+
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;
|
|
418
|
+
var LATE_SECTION = 3;
|
|
419
|
+
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(" ");
|
|
420
|
+
var lensOf = (kind) => kind === "people" ? "identity" : "evidence";
|
|
421
|
+
function decisionText(h) {
|
|
422
|
+
const proof = Array.isArray(h.confidence?.proof) ? h.confidence.proof : [];
|
|
423
|
+
return [
|
|
424
|
+
h.primaryAction?.value,
|
|
425
|
+
h.primaryAction?.because,
|
|
426
|
+
h.lead?.value,
|
|
427
|
+
h.lead?.because,
|
|
428
|
+
...Array.isArray(h.order) ? h.order.map((o) => o?.because) : [],
|
|
429
|
+
...Array.isArray(h.journey) ? h.journey.map((s) => s?.because) : [],
|
|
430
|
+
...proof.flatMap((p) => isObject(p) ? [p.what, p.because] : [])
|
|
431
|
+
].filter((x) => typeof x === "string").join(" ");
|
|
432
|
+
}
|
|
433
|
+
function assessConfidence(hierarchy, job, ctx = {}) {
|
|
434
|
+
const findings = [];
|
|
435
|
+
if (!isObject(hierarchy)) return findings;
|
|
436
|
+
for (const [key, raw] of Object.entries(hierarchy)) {
|
|
437
|
+
const h = raw;
|
|
438
|
+
if (!PAGE_TYPES.includes(key) || !isObject(h) || !isObject(h.confidence)) continue;
|
|
439
|
+
const page = key;
|
|
440
|
+
const at = (rest) => `hierarchy.${page}.confidence${rest}`;
|
|
441
|
+
const add = (f) => findings.push({ page, ...f });
|
|
442
|
+
const c = h.confidence;
|
|
443
|
+
const proof = (Array.isArray(c.proof) ? c.proof : []).filter(isObject);
|
|
444
|
+
const order = new Set((Array.isArray(h.order) ? h.order : []).map((o) => o?.role));
|
|
445
|
+
const bands = new Set((Array.isArray(c.examined) ? c.examined : []).filter((v) => VIEWPORTS.includes(v)));
|
|
446
|
+
if (ctx.snapshot && (ctx.snapshotPage ?? "home") === page) bands.add(viewportOf(ctx.snapshot.viewport.width));
|
|
447
|
+
for (const pos of Array.isArray(h.primaryAction?.positions) ? h.primaryAction.positions : []) {
|
|
448
|
+
if (POSITIONS.has(pos) && pos !== "header" && pos !== "sticky" && !order.has(pos)) {
|
|
449
|
+
add({
|
|
450
|
+
lens: "action",
|
|
451
|
+
basis: "observed",
|
|
452
|
+
at: `hierarchy.${page}.primaryAction.positions`,
|
|
453
|
+
message: `the ask "${h.primaryAction.value}" sits in "${pos}", and the declared order has no such section`,
|
|
454
|
+
why: "A visitor can only take the next step where the page offers it. Either the order or the position is out of date."
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
proof.forEach((p, i) => {
|
|
459
|
+
const where = at(`.proof[${i}]`);
|
|
460
|
+
if (POSITIONS.has(p.position) && p.position !== "header" && p.position !== "sticky" && !order.has(p.position)) {
|
|
461
|
+
add({
|
|
462
|
+
lens: "hierarchy",
|
|
463
|
+
basis: "observed",
|
|
464
|
+
at: `${where}.position`,
|
|
465
|
+
message: `${p.kind} evidence "${p.what}" sits in "${p.position}", and the declared order has no such section`,
|
|
466
|
+
why: "Evidence a visitor is meant to meet has to be on the page, in a section the order names."
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
const cited = Array.isArray(p.evidence) ? p.evidence : [];
|
|
470
|
+
if (cited.length === 0) {
|
|
471
|
+
add({
|
|
472
|
+
lens: lensOf(p.kind),
|
|
473
|
+
basis: "unknown",
|
|
474
|
+
at: `${where}.evidence`,
|
|
475
|
+
message: `no source in the file shows that "${p.what}" exists`,
|
|
476
|
+
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."
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
const firstLook = new Set(proof.filter((p) => p.depth === "first-look").map((p) => p.kind));
|
|
481
|
+
proof.forEach((p, i) => {
|
|
482
|
+
if (p.depth === "on-request" && !firstLook.has(p.kind)) {
|
|
483
|
+
add({
|
|
484
|
+
lens: "disclosure",
|
|
485
|
+
basis: "inferred",
|
|
486
|
+
at: at(`.proof[${i}].depth`),
|
|
487
|
+
message: `${p.kind} evidence "${p.what}" is held back for visitors who seek it, and nothing of the same kind is met first`,
|
|
488
|
+
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."
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
if (job && IDENTITY_WORRY.test(identityText(job))) {
|
|
493
|
+
const sections = Array.isArray(h.order) ? h.order.map((o) => o?.role) : [];
|
|
494
|
+
const before = (pos) => pos === "header" || pos === "sticky" ? 0 : sections.indexOf(pos);
|
|
495
|
+
const met = proof.filter((p) => p.kind === "people" && before(p.position) >= 0).map((p) => ({ position: p.position, before: before(p.position) }));
|
|
496
|
+
const earliest = met.length > 0 ? met.reduce((a, b) => b.before < a.before ? b : a) : void 0;
|
|
497
|
+
if (earliest && earliest.before >= LATE_SECTION) {
|
|
498
|
+
add({
|
|
499
|
+
lens: "identity",
|
|
500
|
+
basis: "inferred",
|
|
501
|
+
at: at(".proof"),
|
|
502
|
+
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`,
|
|
503
|
+
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."}`
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (job && Array.isArray(job.objections)) {
|
|
508
|
+
const said = contentWords(decisionText(h));
|
|
509
|
+
job.objections.forEach((o, i) => {
|
|
510
|
+
if (typeof o !== "string") return;
|
|
511
|
+
if (shares(contentWords(o), said).length === 0) {
|
|
512
|
+
add({
|
|
513
|
+
lens: "relevance",
|
|
514
|
+
basis: "unknown",
|
|
515
|
+
at: `job.objections[${i}]`,
|
|
516
|
+
message: `no decision recorded for this page names the worry "${o}"`,
|
|
517
|
+
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."
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
if (bands.size === 0) {
|
|
523
|
+
add({
|
|
524
|
+
lens: "mobile",
|
|
525
|
+
basis: "unknown",
|
|
526
|
+
at: at(".examined"),
|
|
527
|
+
message: "no width recorded: say which widths these decisions were examined at",
|
|
528
|
+
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."
|
|
529
|
+
});
|
|
530
|
+
} else {
|
|
531
|
+
for (const band of VIEWPORTS) {
|
|
532
|
+
if (bands.has(band)) continue;
|
|
533
|
+
add({
|
|
534
|
+
lens: "mobile",
|
|
535
|
+
basis: "unknown",
|
|
536
|
+
at: at(".examined"),
|
|
537
|
+
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`,
|
|
538
|
+
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."
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return findings;
|
|
544
|
+
}
|
|
545
|
+
|
|
373
546
|
// src/direction/job.ts
|
|
374
547
|
var JOB_VERBS = new Set(
|
|
375
548
|
"find choose pick book get fix stop avoid know feel understand compare decide keep protect prepare plan sort arrange replace repair learn prove show recover reduce save spend hire check see buy sell move start finish handle manage meet reach return treat look end make give take bring hand leave trust rely feel prove explain secure settle solve restore improve change become stay pass sign agree ask call reassure justify confirm".split(" ")
|
|
@@ -700,6 +873,7 @@ function validateDirection(input, ctx = {}) {
|
|
|
700
873
|
}
|
|
701
874
|
let job = false;
|
|
702
875
|
let hierarchy = [];
|
|
876
|
+
let confidence;
|
|
703
877
|
if (d.version === 2) {
|
|
704
878
|
const j = validateJob(d.job, sources, { brief: typeof d.brief === "string" ? d.brief : "", readSource: ctx.readSource });
|
|
705
879
|
problems.push(...j.problems);
|
|
@@ -707,6 +881,10 @@ function validateDirection(input, ctx = {}) {
|
|
|
707
881
|
const h = validateHierarchy(d.hierarchy, d.job, sources);
|
|
708
882
|
problems.push(...h.problems);
|
|
709
883
|
hierarchy = h.pages;
|
|
884
|
+
const pages = d.hierarchy && typeof d.hierarchy === "object" && !Array.isArray(d.hierarchy) ? d.hierarchy : {};
|
|
885
|
+
const recorded = Object.entries(pages).filter(([k, p]) => PAGE_TYPES.includes(k) && p && typeof p === "object" && p.confidence !== void 0);
|
|
886
|
+
for (const [page, p] of recorded) problems.push(...validateConfidence(page, p.confidence, d.job, sources));
|
|
887
|
+
if (recorded.length) confidence = assessConfidence(d.hierarchy, d.job, { snapshot: ctx.snapshot, snapshotPage: ctx.page });
|
|
710
888
|
if (ctx.snapshot) problems.push(...checkOrder(d.hierarchy?.[ctx.page ?? "home"], ctx.page ?? "home", ctx.snapshot, ctx.strict));
|
|
711
889
|
}
|
|
712
890
|
if (ctx.estate?.length) {
|
|
@@ -723,6 +901,7 @@ function validateDirection(input, ctx = {}) {
|
|
|
723
901
|
valid: problems.every((p) => p.severity !== "error"),
|
|
724
902
|
problems,
|
|
725
903
|
decided,
|
|
904
|
+
...confidence ? { confidence } : {},
|
|
726
905
|
layers: { job, hierarchy, tokens: { decided, total: CHOICE_KEYS.length }, complete: complete && problems.every((p) => p.severity !== "error") }
|
|
727
906
|
};
|
|
728
907
|
}
|
|
@@ -730,8 +909,20 @@ function validateDirection(input, ctx = {}) {
|
|
|
730
909
|
export {
|
|
731
910
|
DIRECTION_VERSION,
|
|
732
911
|
PAGE_TYPES,
|
|
912
|
+
VIEWPORTS,
|
|
913
|
+
CONFIDENCE_LENSES,
|
|
914
|
+
REVIEW_BASES,
|
|
915
|
+
INSPECTED_KINDS,
|
|
916
|
+
EVIDENCE_KINDS,
|
|
917
|
+
CHANGE_CLASSES,
|
|
918
|
+
CHANGE_STEPS,
|
|
919
|
+
ADDITIONS,
|
|
733
920
|
PROPOSED,
|
|
921
|
+
PREFERENCE,
|
|
922
|
+
CONVENTION,
|
|
923
|
+
checkReason,
|
|
734
924
|
researchPrompt,
|
|
925
|
+
SECTION_ROLES,
|
|
735
926
|
summariseDirection,
|
|
736
927
|
compareDirections,
|
|
737
928
|
compareToDeclared,
|