@domandigital/craft 0.23.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -5
- package/CHARACTER.md +98 -97
- package/COPY.md +3 -2
- package/PRINCIPLES.md +5 -5
- package/README.md +8 -8
- package/REGISTERS.md +3 -3
- package/design-review.schema.json +1 -1
- package/dist/{audit-DESBVHQJ.js → audit-Q73WGKMS.js} +3 -3
- package/dist/audit.cjs +88 -23
- package/dist/audit.js +88 -23
- package/dist/{chunk-GA3AJN67.js → chunk-2CCL5CO7.js} +17 -15
- package/dist/{chunk-M5QSMIT6.js → chunk-4HPAMB52.js} +4 -4
- package/dist/{chunk-LRWKKJIW.js → chunk-6TOY3FAT.js} +5 -5
- package/dist/{chunk-GDDBCHQR.js → chunk-GJZ4Y5PT.js} +5 -5
- package/dist/{chunk-ZV3FPQPM.js → chunk-OA2MBUJJ.js} +1 -1
- package/dist/{chunk-V3GJSISR.js → chunk-OOT4QLO3.js} +14 -14
- package/dist/{chunk-7DPB5G4G.js → chunk-PXJKYMXF.js} +2 -2
- package/dist/{chunk-EHMEMSM2.js → chunk-R4KPE4G5.js} +72 -9
- package/dist/{cli-FCFRPPHZ.js → cli-766R2BUR.js} +5 -5
- package/dist/{cli-3PFEOEQI.js → cli-CE7ENNNG.js} +6 -6
- package/dist/{cli-SOH4QMMW.js → cli-DTJOOTNM.js} +3 -3
- package/dist/{cli-36YQA6BP.js → cli-EQJITFVE.js} +4 -4
- package/dist/{cli-RXXGSKD6.js → cli-SD2BJSGP.js} +6 -6
- package/dist/{cli-TP5ZMJOL.js → cli-TBBXUECM.js} +5 -5
- package/dist/{cli-YOSHHLIV.js → cli-TV2IJ5TV.js} +2 -3
- package/dist/{cli-7IBKMUMM.js → cli-U3SPFILJ.js} +9 -9
- package/dist/cli.js +3 -3
- package/dist/index.cjs +89 -25
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +89 -25
- package/dist/{viewports-cli-ET664I2Y.js → viewports-cli-SOOHAD5D.js} +3 -3
- package/package.json +1 -1
- package/word-lists.json +1 -1
package/README.md
CHANGED
|
@@ -121,11 +121,11 @@ no change is needed. Evidence only reported by an audit or a stakeholder cannot
|
|
|
121
121
|
make a problem observed, and a width nobody looked at stays unknown. See
|
|
122
122
|
[Reviewing a proposed change](./CHARACTER.md#reviewing-a-proposed-change).
|
|
123
123
|
|
|
124
|
-
A reviewer's own review is kept whole in the same shape, in
|
|
125
|
-
reviewer corpus
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
[
|
|
124
|
+
A reviewer's own review is kept whole in the same shape, in a private
|
|
125
|
+
reviewer corpus kept outside this repository, with what was done with each
|
|
126
|
+
note and the principle it teaches. A studio reviewer's review of the studio's
|
|
127
|
+
own site is the first. See
|
|
128
|
+
[Review of the studio's own site](./CHARACTER.md#review-of-the-studios-own-site-2026-10-19).
|
|
129
129
|
|
|
130
130
|
`--snapshot` accepts saved files and preview or production URLs. Drift fails
|
|
131
131
|
with `--strict`; declared estate comparisons stay advisory. Version 1 files
|
|
@@ -378,8 +378,8 @@ the one loaded page in place:
|
|
|
378
378
|
- **On a landscape phone (844 x 390):** header and fixed layers leave at least
|
|
379
379
|
half the height.
|
|
380
380
|
- **Page length (phone screens):** page height at 375 x 667 over 667, to the
|
|
381
|
-
footer (lazy images loaded). A phone page has a length budget (
|
|
382
|
-
|
|
381
|
+
footer (lazy images loaded). A phone page has a length budget (one client home
|
|
382
|
+
page ran 12.7 screens against a target of 6.5). Set one per route or
|
|
383
383
|
route pattern with `screens`; a route over its budget fails the `screens`
|
|
384
384
|
check, and with no budget the count is only reported. Keys are a route
|
|
385
385
|
(`/`), a pattern (`/areas/[town]`), a prefix (`/services/*`, the route and
|
|
@@ -454,4 +454,4 @@ select a better design automatically. Protected staging reviews may reserve
|
|
|
454
454
|
explicitly marked publication assets while reporting `publicationReady: false`.
|
|
455
455
|
See CHARACTER.md, “Context and learning from a later result”.
|
|
456
456
|
|
|
457
|
-
Opening photographs are selected as a rendered sequence: compare their quality and crop before filling a slide count or covering every service. See [Select photographs for their position in the sequence](./CHARACTER.md#select-photographs-for-their-position-in-the-sequence) for the
|
|
457
|
+
Opening photographs are selected as a rendered sequence: compare their quality and crop before filling a slide count or covering every service. See [Select photographs for their position in the sequence](./CHARACTER.md#select-photographs-for-their-position-in-the-sequence) for the follow-up on one client's opening photographs and its limits.
|
package/REGISTERS.md
CHANGED
|
@@ -29,8 +29,8 @@ nominalisations, pronoun openers, paired phrases, contractions, person and
|
|
|
29
29
|
passive voice. Names, figures and specifics are left out, because they say
|
|
30
30
|
what a text is about, not how it is written.
|
|
31
31
|
|
|
32
|
-
It needs five paragraphs of two to eight sentences each. In
|
|
33
|
-
|
|
32
|
+
It needs five paragraphs of two to eight sentences each. In the register
|
|
33
|
+
separation report (kept in the private craft corpus), single passages from different
|
|
34
34
|
registers overlap too much to place (26 of 258 pair tests separate), while
|
|
35
35
|
five-paragraph averages separate every register pair (78 of 258). Anything
|
|
36
36
|
shorter gets the brief, not a measurement.
|
|
@@ -157,7 +157,7 @@ Generated from `src/register/registers.ts` by
|
|
|
157
157
|
**Sources:**
|
|
158
158
|
|
|
159
159
|
- house judgement: No public style authority exists for premium-brand prose. These directives are house judgement, to be revised as the estate grows.
|
|
160
|
-
-
|
|
160
|
+
- Register separation, 2026-09-29 (report in the private craft corpus): Literary prose (Gutenberg) carries about twice the comma density of the other human registers, the one feature that separated it from all four. Longer, jointed sentences are normal here.
|
|
161
161
|
|
|
162
162
|
**Always:** Every register keeps the COPY.md blocking tier and the proof rule. Draft first, then run `craft copy <file>` on the draft.
|
|
163
163
|
<!-- craft:registers:end -->
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"source": {
|
|
34
34
|
"type": "object",
|
|
35
|
-
"description": "Where the review came from, when it is a reviewer's own record (a reviewer corpus
|
|
35
|
+
"description": "Where the review came from, when it is a reviewer's own record (a reviewer corpus, such as the one kept in the private craft corpus).",
|
|
36
36
|
"required": [
|
|
37
37
|
"tool"
|
|
38
38
|
],
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
snapshotPage,
|
|
10
10
|
snapshotUrl,
|
|
11
11
|
snapshotUrls
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OA2MBUJJ.js";
|
|
13
13
|
import "./chunk-JYNXGCYZ.js";
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-2CCL5CO7.js";
|
|
15
|
+
import "./chunk-R4KPE4G5.js";
|
|
16
16
|
import "./chunk-QYBAVU3P.js";
|
|
17
17
|
export {
|
|
18
18
|
PROVENANCE_BUDGET_MS,
|
package/dist/audit.cjs
CHANGED
|
@@ -358,6 +358,29 @@ var PARALLEL_PAIR = [
|
|
|
358
358
|
// a verbless list as a tagline: "Service account, match-day reminders, analytics."
|
|
359
359
|
/^[ \t]*(?:#{1,6}[ \t]+)?[A-Z][\w'’-]*(?:[ \t]+(?!(?:and|or)\b)[\w'’-]+){0,2}(?:,[ \t]+(?!(?:and|or)\b)[\w'’-]+(?:[ \t]+(?!(?:and|or)\b)[\w'’-]+){0,2}){2,3}\.[ \t]*$/m
|
|
360
360
|
];
|
|
361
|
+
var CLAUSE_START = String.raw`(?<=^[ \t]*(?:[-*][ \t]+|#{1,6}[ \t]+)?|[;:,(—–][ \t]*|[.!?][ \t]+)`;
|
|
362
|
+
var ABSOLUTE_NEGATION = new RegExp(
|
|
363
|
+
`${CLAUSE_START}(?:never|nothing|nobody|no[ \\t]+one|no)(?:[ \\t]+[\\w'\u2019\xA3$\u20AC-]+){1,6}(?=[ \\t]*(?:[.,;:!?)"\u201D\u2014\u2013]|$))`,
|
|
364
|
+
"im"
|
|
365
|
+
);
|
|
366
|
+
var FINITE = /^(?:is|are|was|were|am|be|been|being|has|have|had|do|does|did|can|could|will|would|shall|should|may|might|must|need|needs|gets|got|goes|went|comes|came|makes|means|matters|happens|stays|changes|works|takes|says|knows|seems|feels|looks|leaves|left|appears|uses|runs|shows|sends|holds|reads|writes|opens|stores|reaches|applies|isn['’]t|aren['’]t|wasn['’]t)$/i;
|
|
367
|
+
var NO_IDIOM = /^(?:date|longer|later|sooner|matter|more|less|fewer|other|one|doubt|wonder|thanks|problem)$/i;
|
|
368
|
+
var PARTICIPLE = /^[\w-]*(?:ed|en|wn|ld|lt|nt|pt|ne|id)$/i;
|
|
369
|
+
function notAbsoluteFragment(block, match, index) {
|
|
370
|
+
const [head, ...rest] = match.trim().split(/[ \t]+/);
|
|
371
|
+
let words = rest;
|
|
372
|
+
const lead = head.toLowerCase();
|
|
373
|
+
if (lead === "no" && words[0]?.toLowerCase() === "one") words = words.slice(1);
|
|
374
|
+
else if (lead === "no" && NO_IDIOM.test(words[0] ?? "")) return true;
|
|
375
|
+
if (words.some((w) => FINITE.test(w) || /['’](?:s|re|ve|ll|d)$/i.test(w))) return true;
|
|
376
|
+
if (lead === "never" && !PARTICIPLE.test(words[0] ?? "")) return true;
|
|
377
|
+
if (lead !== "no" && lead !== "never" && /^(?!else$|less$|across$)[a-z]+s$/i.test(words[0] ?? "")) return true;
|
|
378
|
+
const sentence = lineAround(block.text, index);
|
|
379
|
+
const line = sentence.trim().replace(/^[-*]\s+/, "");
|
|
380
|
+
if (NO_X_NO_Y.test(sentence) || NO_X_BADGE.test(line)) return true;
|
|
381
|
+
if (NEGATIVE_REASSURANCE.some((p) => wordList([p]).test(match))) return true;
|
|
382
|
+
return emptyState(block, match, index);
|
|
383
|
+
}
|
|
361
384
|
var alreadyBlocked = (block, match, index) => {
|
|
362
385
|
const before = block.text.slice(0, index);
|
|
363
386
|
const start = Math.max(before.search(/[^.!?\n]*$/), 0);
|
|
@@ -484,7 +507,7 @@ var COPY_TELLS = [
|
|
|
484
507
|
fixtures: {
|
|
485
508
|
flag: [
|
|
486
509
|
f("content/home.md", "Your options, in plain English."),
|
|
487
|
-
//
|
|
510
|
+
// A reviewer's draft lines, Pastel 986559 #12 and #13 (the diagnosis taken, the wording not).
|
|
488
511
|
f("content/clinics.md", "Tell us what's happening and we'll say plainly what we would take on."),
|
|
489
512
|
f("content/pricing.md", "DD's payment terms, plainly stated."),
|
|
490
513
|
f("content/clinics.md", "We explain our findings in plain terms."),
|
|
@@ -610,7 +633,7 @@ var COPY_TELLS = [
|
|
|
610
633
|
fixtures: {
|
|
611
634
|
flag: [
|
|
612
635
|
// From the portal's seeded prospect follow-ups, 2026-10-19.
|
|
613
|
-
f("emails/followup.md", "Alright Dave, it's
|
|
636
|
+
f("emails/followup.md", "Alright Dave, it's Sam. No pressure, have a look when you get a min."),
|
|
614
637
|
f("emails/followup.md", "All good if you're not fussed mate, I'll leave it there."),
|
|
615
638
|
f("emails/followup.md", "Carlo, built the shop a proper site."),
|
|
616
639
|
f("content/contact.md", "No worries, we'll get you sorted."),
|
|
@@ -623,11 +646,11 @@ var COPY_TELLS = [
|
|
|
623
646
|
f("content/home.md", "Proper planning saves a second visit."),
|
|
624
647
|
f("content/certificates.md", "Download a sorted list of certificates, sorted by date."),
|
|
625
648
|
f("content/directions.md", "At the junction, take the right then left turn."),
|
|
626
|
-
//
|
|
649
|
+
// A barber client's own approved line, and "mate" as a noun.
|
|
627
650
|
f("content/home.md", "Every cut starts with a proper chat about what you actually want."),
|
|
628
651
|
f("content/home.md", "It feels like getting a cut from a mate who knows what he is doing."),
|
|
629
652
|
f("emails/quote.md", "Have a look at the quote and ring me if Thursday does not work."),
|
|
630
|
-
f("content/contact.md", "Give us a ring on
|
|
653
|
+
f("content/contact.md", "Give us a ring on 01632 960123. We take a look at the fault before quoting."),
|
|
631
654
|
f("content/home.md", "Built to last, and made for older houses.")
|
|
632
655
|
]
|
|
633
656
|
}
|
|
@@ -639,14 +662,14 @@ var COPY_TELLS = [
|
|
|
639
662
|
severity: "warn",
|
|
640
663
|
surface: "copy",
|
|
641
664
|
why: "'What this is and is not', 'we have not yet built', 'our experience so far is one practice', 'what we would and wouldn't take on': a model scopes a business by confessing its limits. It reads as small print, and it turns a buyer away before they make contact.",
|
|
642
|
-
fix: "Scope by being precise about what is true, and leave the door open: 'Built and run by us for
|
|
665
|
+
fix: "Scope by being precise about what is true, and leave the door open: 'Built and run by us for a children's therapy practice whose clinicians work across several sites.' Keep contractual facts (eligibility, who runs what) and say them as what the buyer gets.",
|
|
643
666
|
detect: phrases(CONFESSION, quoted),
|
|
644
667
|
fixtures: {
|
|
645
668
|
flag: [
|
|
646
|
-
// Each one from the
|
|
669
|
+
// Each one from the studio's own site, as a reviewer flagged it (Pastel 986559 #8, #12, #13).
|
|
647
670
|
f("content/clinics.md", "What this is and is not: a practice portal we built and run for one practice."),
|
|
648
671
|
f("content/clinics.md", "We have not yet built and run the digital systems of a practice with several clinicians."),
|
|
649
|
-
f("content/home.md", "Including the client portal we run for
|
|
672
|
+
f("content/home.md", "Including the client portal we run for a therapy practice. Our clinic experience so far is one practice."),
|
|
650
673
|
f("content/clinics.md", "Tell us what's happening and we'll say what we would and wouldn't take on."),
|
|
651
674
|
f("content/clinics.md", "We will say what carries over from this work and what we would need to learn first."),
|
|
652
675
|
f("content/clinics.md", "We will tell you where our experience applies directly and where it is close."),
|
|
@@ -655,8 +678,8 @@ var COPY_TELLS = [
|
|
|
655
678
|
f("content/mobile.md", "Mobile is our newest line, so we tell you what we have and haven't done.")
|
|
656
679
|
],
|
|
657
680
|
pass: [
|
|
658
|
-
f("content/clinics.md", "Built and run by us for
|
|
659
|
-
f("content/home.md", "The client portal we run for
|
|
681
|
+
f("content/clinics.md", "Built and run by us for a therapy practice, a growing one whose clinicians work remotely."),
|
|
682
|
+
f("content/home.md", "The client portal we run for a children's therapy practice."),
|
|
660
683
|
// Contractual facts, said as they are.
|
|
661
684
|
f("content/pricing.md", "Pay monthly is open to UK residents aged 18 and over, subject to status."),
|
|
662
685
|
f("content/pricing.md", "Your monthly payments do not buy the site; the buy-out price does."),
|
|
@@ -896,6 +919,46 @@ var COPY_TELLS = [
|
|
|
896
919
|
]
|
|
897
920
|
}
|
|
898
921
|
},
|
|
922
|
+
{
|
|
923
|
+
id: "absolute-negation",
|
|
924
|
+
name: "Absolute negation fragment",
|
|
925
|
+
generation: 2,
|
|
926
|
+
severity: "warn",
|
|
927
|
+
surface: "copy",
|
|
928
|
+
why: "'never pre-ticked', 'no servers', 'nothing to buy or run': a verbless fragment that opens on never, nothing, nobody or 'no' + noun. Read aloud it sounds rigid, it promises an absolute the copy rarely means, and it defines the thing by what it is not.",
|
|
929
|
+
fix: "Say what happens, as a sentence: 'A guardian ticks the consent box in the email.', 'Microsoft runs the servers.' Keep the absolute only where it is literally true and the reader needs it, and give it a subject and a verb.",
|
|
930
|
+
detect: phrases([ABSOLUTE_NEGATION], quoted, allText, notAbsoluteFragment),
|
|
931
|
+
fixtures: {
|
|
932
|
+
flag: [
|
|
933
|
+
// From a 92EL deck that passed the gate, October 2026.
|
|
934
|
+
f("content/deck.md", "A guardian consents by email; never pre-ticked."),
|
|
935
|
+
f("content/deck.md", "Pay per user per month; no servers."),
|
|
936
|
+
f("content/deck.md", "Microsoft runs it, so there is less to manage: nothing to buy or run."),
|
|
937
|
+
f("content/deck.md", "Players register on their phone. Nothing on-site to join."),
|
|
938
|
+
f("content/deck.md", "Each player is checked against the list once, no duplicates."),
|
|
939
|
+
f("app/slide.tsx", `export const note = "Nobody to chase for a form.";`)
|
|
940
|
+
],
|
|
941
|
+
pass: [
|
|
942
|
+
f("content/report.md", "Players never open the lists."),
|
|
943
|
+
f("content/report.md", "Nothing is copied by hand."),
|
|
944
|
+
f("content/report.md", "It is not tested at national scale."),
|
|
945
|
+
f("content/report.md", "Never share your password with anyone."),
|
|
946
|
+
f("content/report.md", "No one knows the password but the club secretary."),
|
|
947
|
+
// A Harvard citation with no date.
|
|
948
|
+
f("content/report.md", "Children's data needs a lawful basis (ICO, no date)."),
|
|
949
|
+
f("content/report.md", "- Information Commissioner's Office (ICO), no date. Children and the UK GDPR."),
|
|
950
|
+
// A form answer option.
|
|
951
|
+
f("content/form.md", "Is the player under 18?\n\n- Yes\n- No"),
|
|
952
|
+
f("app/form.tsx", `export const options = ["Yes", "No"];`),
|
|
953
|
+
f("content/form.md", "No, the player is 18 or over."),
|
|
954
|
+
f("content/home.md", "We no longer post paper forms."),
|
|
955
|
+
f("content/report.md", "If the check fails, nothing new appears in the list."),
|
|
956
|
+
// The blocking tells report these.
|
|
957
|
+
f("content/home.md", "We reply within a day. No obligation, no spam."),
|
|
958
|
+
f("app/list.tsx", `export const empty = "No results found.";`)
|
|
959
|
+
]
|
|
960
|
+
}
|
|
961
|
+
},
|
|
899
962
|
{
|
|
900
963
|
id: "where-x-meets-y",
|
|
901
964
|
name: "'Where X meets Y'",
|
|
@@ -2541,7 +2604,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2541
2604
|
generation: 1,
|
|
2542
2605
|
severity: "warn",
|
|
2543
2606
|
surface: "source",
|
|
2544
|
-
why: "When every block fades up on scroll, the motion stops meaning anything and the page feels slow.
|
|
2607
|
+
why: "When every block fades up on scroll, the motion stops meaning anything and the page feels slow. One salon client's site had 70, the studio's own 40, a therapy practice's 27.",
|
|
2545
2608
|
fix: `Keep one gated signature moment per page (STANDARD section 7) and let the rest arrive still. The limit here is ${REVEAL_LIMIT} per scan.`,
|
|
2546
2609
|
detect: revealHits,
|
|
2547
2610
|
fixtures: {
|
|
@@ -2557,7 +2620,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2557
2620
|
generation: 1,
|
|
2558
2621
|
severity: "warn",
|
|
2559
2622
|
surface: "source",
|
|
2560
|
-
why: "rounded-full on every button, badge and tag is the framework's friendliest default.
|
|
2623
|
+
why: "rounded-full on every button, badge and tag is the framework's friendliest default. One salon client's site had 162, the studio's own 125. A shape used everywhere is not a shape language.",
|
|
2561
2624
|
fix: `Choose the shape language on purpose (and record it), then use the pill only where it means something. The limit here is ${PILL_LIMIT} per scan.`,
|
|
2562
2625
|
detect: pillHits,
|
|
2563
2626
|
fixtures: {
|
|
@@ -2572,7 +2635,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2572
2635
|
generation: 1,
|
|
2573
2636
|
severity: "warn",
|
|
2574
2637
|
surface: "source",
|
|
2575
|
-
why: "A generator exports the whole shadcn/ui kit whether the site uses it or not.
|
|
2638
|
+
why: "A generator exports the whole shadcn/ui kit whether the site uses it or not. One client's journal site ships a full set under its own book, vine and petal assets.",
|
|
2576
2639
|
fix: "Delete the primitives nothing imports. Restyle the ones that stay so they belong to this site.",
|
|
2577
2640
|
detect: shadcnHits,
|
|
2578
2641
|
fixtures: {
|
|
@@ -2630,7 +2693,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2630
2693
|
f3("components/Hero.tsx", `<span className="font-serif italic">reimagined</span>`),
|
|
2631
2694
|
f3("app/globals.css", `.hero h1 { font-style: italic; }`)
|
|
2632
2695
|
],
|
|
2633
|
-
pass: [f3("components/Hero.tsx", `<h1 className="font-display text-6xl">Nails in
|
|
2696
|
+
pass: [f3("components/Hero.tsx", `<h1 className="font-display text-6xl">Nails in Kettering</h1>
|
|
2634
2697
|
<p><em>Walk-ins</em> welcome.</p>`)]
|
|
2635
2698
|
}
|
|
2636
2699
|
},
|
|
@@ -2646,11 +2709,11 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2646
2709
|
fixtures: {
|
|
2647
2710
|
flag: [
|
|
2648
2711
|
f3("components/Hero.tsx", `<span className="inline-flex rounded-full border px-3 py-1 text-xs">Now booking</span>
|
|
2649
|
-
<h1>Nails in
|
|
2712
|
+
<h1>Nails in Kettering</h1>`),
|
|
2650
2713
|
f3("components/Hero.tsx", `<span className={cn("rounded-full px-3 text-xs", "bg-rose-500/10")}>New</span>
|
|
2651
2714
|
<h1>Nails</h1>`)
|
|
2652
2715
|
],
|
|
2653
|
-
pass: [f3("components/Hero.tsx", `<h1>Nails in
|
|
2716
|
+
pass: [f3("components/Hero.tsx", `<h1>Nails in Kettering</h1>
|
|
2654
2717
|
<span className="inline-flex rounded-full border px-3 py-1 text-xs">Tag</span>`)]
|
|
2655
2718
|
}
|
|
2656
2719
|
},
|
|
@@ -2665,7 +2728,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2665
2728
|
detect: heroCtaPairHits,
|
|
2666
2729
|
fixtures: {
|
|
2667
2730
|
flag: [
|
|
2668
|
-
//
|
|
2731
|
+
// A reviewer's #5 and #36 (Pastel 986559): the studio's own hero and process close.
|
|
2669
2732
|
f3(
|
|
2670
2733
|
"src/components/Hero.astro",
|
|
2671
2734
|
`<section class="hero">
|
|
@@ -2674,20 +2737,20 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2674
2737
|
<a href="/work" class="btn btn-ghost">See our work</a>
|
|
2675
2738
|
</section>`
|
|
2676
2739
|
),
|
|
2677
|
-
// The null build's hero, verbatim in shape (
|
|
2740
|
+
// The null build's hero, verbatim in shape (an electrician brief's null pages, in the private dd-craft-corpus).
|
|
2678
2741
|
f3(
|
|
2679
2742
|
"index.html",
|
|
2680
2743
|
`<nav><a class="btn" href="#contact">Quote</a></nav>
|
|
2681
2744
|
<section id="hero" class="hero">
|
|
2682
|
-
<h1>Electricians in
|
|
2745
|
+
<h1>Electricians in Reading</h1>
|
|
2683
2746
|
<a href="#contact" class="btn btn-primary">Book a Free Quote</a>
|
|
2684
|
-
<a href="tel:+
|
|
2747
|
+
<a href="tel:+441632960210" class="btn btn-outline">Call 01632 960 210</a>
|
|
2685
2748
|
</section>`
|
|
2686
2749
|
),
|
|
2687
2750
|
f3(
|
|
2688
2751
|
"app/page.tsx",
|
|
2689
2752
|
`<section className="relative py-24">
|
|
2690
|
-
<h1 className="text-5xl">Nails in
|
|
2753
|
+
<h1 className="text-5xl">Nails in Kettering</h1>
|
|
2691
2754
|
<div className="flex gap-4">
|
|
2692
2755
|
<Button asChild><a href="/book">Book now</a></Button>
|
|
2693
2756
|
<Button variant="outline">View treatments</Button>
|
|
@@ -2716,7 +2779,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2716
2779
|
</header>`
|
|
2717
2780
|
),
|
|
2718
2781
|
f3("app/page.tsx", `<section className="py-24">
|
|
2719
|
-
<h1 className="text-5xl">Nails in
|
|
2782
|
+
<h1 className="text-5xl">Nails in Kettering</h1>
|
|
2720
2783
|
<a className="rounded bg-ink px-5 py-3" href="/book">Book now</a>
|
|
2721
2784
|
</section>
|
|
2722
2785
|
<section><a className="rounded bg-ink px-5 py-3" href="/prices">Prices</a></section>`)
|
|
@@ -3492,13 +3555,15 @@ var HOUSE = {
|
|
|
3492
3555
|
"stock-email-line": { tier: "review", label: "stock email line, start with the reason for writing and end with the next step" },
|
|
3493
3556
|
// New 2026-10-19 (founder): review first, like every new tell, until estate hits are read.
|
|
3494
3557
|
"matey-register": { tier: "review", label: "matey register, write plain and complete British English" },
|
|
3495
|
-
// New 2026-10-20 (founder, from
|
|
3558
|
+
// New 2026-10-20 (founder, from a reviewer's notes): review first, like every new tell.
|
|
3496
3559
|
"confession-line": { tier: "review", label: "confession line, scope the work by saying precisely what is true" },
|
|
3497
3560
|
// New 2026-10-23 (a 92EL report passed the gate with three): review, because a
|
|
3498
3561
|
// genuine disclaimer has the same shape. The gerund pair joined not-just-but.
|
|
3499
3562
|
// New 2026-10-23 (founder, from 92EL deliverables): review, since a short slide label is often fine.
|
|
3500
3563
|
"parallel-pair": { tier: "review", label: "verbless parallel pair, write the sentence it stands for" },
|
|
3501
3564
|
"trailing-contrast": { tier: "review", label: "trailing contrast. Say the positive claim, unless it is a disclaimer the reader needs" },
|
|
3565
|
+
// New 2026-10-26 (founder, from a 92EL deck): review first, like every new tell, until estate hits are read.
|
|
3566
|
+
"absolute-negation": { tier: "review", label: "absolute negation fragment. Say what happens, as a sentence with a subject and a verb" },
|
|
3502
3567
|
"ai-vocabulary": { tier: "review", label: "AI vocabulary" },
|
|
3503
3568
|
"stock-phrase": { tier: "review", label: "stock phrase" },
|
|
3504
3569
|
"hollow-imperative": { tier: "review", label: "hollow imperative" },
|
|
@@ -4267,7 +4332,7 @@ var IMAGERY_TELLS = [
|
|
|
4267
4332
|
];
|
|
4268
4333
|
|
|
4269
4334
|
// src/character/check.ts
|
|
4270
|
-
var CATALOGUE_VERSION = "2026.10.
|
|
4335
|
+
var CATALOGUE_VERSION = "2026.10.9";
|
|
4271
4336
|
var CATALOGUE = [
|
|
4272
4337
|
...[...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map((t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t),
|
|
4273
4338
|
...SPECIFICITY_COPY_TELLS,
|
package/dist/audit.js
CHANGED
|
@@ -323,6 +323,29 @@ var PARALLEL_PAIR = [
|
|
|
323
323
|
// a verbless list as a tagline: "Service account, match-day reminders, analytics."
|
|
324
324
|
/^[ \t]*(?:#{1,6}[ \t]+)?[A-Z][\w'’-]*(?:[ \t]+(?!(?:and|or)\b)[\w'’-]+){0,2}(?:,[ \t]+(?!(?:and|or)\b)[\w'’-]+(?:[ \t]+(?!(?:and|or)\b)[\w'’-]+){0,2}){2,3}\.[ \t]*$/m
|
|
325
325
|
];
|
|
326
|
+
var CLAUSE_START = String.raw`(?<=^[ \t]*(?:[-*][ \t]+|#{1,6}[ \t]+)?|[;:,(—–][ \t]*|[.!?][ \t]+)`;
|
|
327
|
+
var ABSOLUTE_NEGATION = new RegExp(
|
|
328
|
+
`${CLAUSE_START}(?:never|nothing|nobody|no[ \\t]+one|no)(?:[ \\t]+[\\w'\u2019\xA3$\u20AC-]+){1,6}(?=[ \\t]*(?:[.,;:!?)"\u201D\u2014\u2013]|$))`,
|
|
329
|
+
"im"
|
|
330
|
+
);
|
|
331
|
+
var FINITE = /^(?:is|are|was|were|am|be|been|being|has|have|had|do|does|did|can|could|will|would|shall|should|may|might|must|need|needs|gets|got|goes|went|comes|came|makes|means|matters|happens|stays|changes|works|takes|says|knows|seems|feels|looks|leaves|left|appears|uses|runs|shows|sends|holds|reads|writes|opens|stores|reaches|applies|isn['’]t|aren['’]t|wasn['’]t)$/i;
|
|
332
|
+
var NO_IDIOM = /^(?:date|longer|later|sooner|matter|more|less|fewer|other|one|doubt|wonder|thanks|problem)$/i;
|
|
333
|
+
var PARTICIPLE = /^[\w-]*(?:ed|en|wn|ld|lt|nt|pt|ne|id)$/i;
|
|
334
|
+
function notAbsoluteFragment(block, match, index) {
|
|
335
|
+
const [head, ...rest] = match.trim().split(/[ \t]+/);
|
|
336
|
+
let words = rest;
|
|
337
|
+
const lead = head.toLowerCase();
|
|
338
|
+
if (lead === "no" && words[0]?.toLowerCase() === "one") words = words.slice(1);
|
|
339
|
+
else if (lead === "no" && NO_IDIOM.test(words[0] ?? "")) return true;
|
|
340
|
+
if (words.some((w) => FINITE.test(w) || /['’](?:s|re|ve|ll|d)$/i.test(w))) return true;
|
|
341
|
+
if (lead === "never" && !PARTICIPLE.test(words[0] ?? "")) return true;
|
|
342
|
+
if (lead !== "no" && lead !== "never" && /^(?!else$|less$|across$)[a-z]+s$/i.test(words[0] ?? "")) return true;
|
|
343
|
+
const sentence = lineAround(block.text, index);
|
|
344
|
+
const line = sentence.trim().replace(/^[-*]\s+/, "");
|
|
345
|
+
if (NO_X_NO_Y.test(sentence) || NO_X_BADGE.test(line)) return true;
|
|
346
|
+
if (NEGATIVE_REASSURANCE.some((p) => wordList([p]).test(match))) return true;
|
|
347
|
+
return emptyState(block, match, index);
|
|
348
|
+
}
|
|
326
349
|
var alreadyBlocked = (block, match, index) => {
|
|
327
350
|
const before = block.text.slice(0, index);
|
|
328
351
|
const start = Math.max(before.search(/[^.!?\n]*$/), 0);
|
|
@@ -449,7 +472,7 @@ var COPY_TELLS = [
|
|
|
449
472
|
fixtures: {
|
|
450
473
|
flag: [
|
|
451
474
|
f("content/home.md", "Your options, in plain English."),
|
|
452
|
-
//
|
|
475
|
+
// A reviewer's draft lines, Pastel 986559 #12 and #13 (the diagnosis taken, the wording not).
|
|
453
476
|
f("content/clinics.md", "Tell us what's happening and we'll say plainly what we would take on."),
|
|
454
477
|
f("content/pricing.md", "DD's payment terms, plainly stated."),
|
|
455
478
|
f("content/clinics.md", "We explain our findings in plain terms."),
|
|
@@ -575,7 +598,7 @@ var COPY_TELLS = [
|
|
|
575
598
|
fixtures: {
|
|
576
599
|
flag: [
|
|
577
600
|
// From the portal's seeded prospect follow-ups, 2026-10-19.
|
|
578
|
-
f("emails/followup.md", "Alright Dave, it's
|
|
601
|
+
f("emails/followup.md", "Alright Dave, it's Sam. No pressure, have a look when you get a min."),
|
|
579
602
|
f("emails/followup.md", "All good if you're not fussed mate, I'll leave it there."),
|
|
580
603
|
f("emails/followup.md", "Carlo, built the shop a proper site."),
|
|
581
604
|
f("content/contact.md", "No worries, we'll get you sorted."),
|
|
@@ -588,11 +611,11 @@ var COPY_TELLS = [
|
|
|
588
611
|
f("content/home.md", "Proper planning saves a second visit."),
|
|
589
612
|
f("content/certificates.md", "Download a sorted list of certificates, sorted by date."),
|
|
590
613
|
f("content/directions.md", "At the junction, take the right then left turn."),
|
|
591
|
-
//
|
|
614
|
+
// A barber client's own approved line, and "mate" as a noun.
|
|
592
615
|
f("content/home.md", "Every cut starts with a proper chat about what you actually want."),
|
|
593
616
|
f("content/home.md", "It feels like getting a cut from a mate who knows what he is doing."),
|
|
594
617
|
f("emails/quote.md", "Have a look at the quote and ring me if Thursday does not work."),
|
|
595
|
-
f("content/contact.md", "Give us a ring on
|
|
618
|
+
f("content/contact.md", "Give us a ring on 01632 960123. We take a look at the fault before quoting."),
|
|
596
619
|
f("content/home.md", "Built to last, and made for older houses.")
|
|
597
620
|
]
|
|
598
621
|
}
|
|
@@ -604,14 +627,14 @@ var COPY_TELLS = [
|
|
|
604
627
|
severity: "warn",
|
|
605
628
|
surface: "copy",
|
|
606
629
|
why: "'What this is and is not', 'we have not yet built', 'our experience so far is one practice', 'what we would and wouldn't take on': a model scopes a business by confessing its limits. It reads as small print, and it turns a buyer away before they make contact.",
|
|
607
|
-
fix: "Scope by being precise about what is true, and leave the door open: 'Built and run by us for
|
|
630
|
+
fix: "Scope by being precise about what is true, and leave the door open: 'Built and run by us for a children's therapy practice whose clinicians work across several sites.' Keep contractual facts (eligibility, who runs what) and say them as what the buyer gets.",
|
|
608
631
|
detect: phrases(CONFESSION, quoted),
|
|
609
632
|
fixtures: {
|
|
610
633
|
flag: [
|
|
611
|
-
// Each one from the
|
|
634
|
+
// Each one from the studio's own site, as a reviewer flagged it (Pastel 986559 #8, #12, #13).
|
|
612
635
|
f("content/clinics.md", "What this is and is not: a practice portal we built and run for one practice."),
|
|
613
636
|
f("content/clinics.md", "We have not yet built and run the digital systems of a practice with several clinicians."),
|
|
614
|
-
f("content/home.md", "Including the client portal we run for
|
|
637
|
+
f("content/home.md", "Including the client portal we run for a therapy practice. Our clinic experience so far is one practice."),
|
|
615
638
|
f("content/clinics.md", "Tell us what's happening and we'll say what we would and wouldn't take on."),
|
|
616
639
|
f("content/clinics.md", "We will say what carries over from this work and what we would need to learn first."),
|
|
617
640
|
f("content/clinics.md", "We will tell you where our experience applies directly and where it is close."),
|
|
@@ -620,8 +643,8 @@ var COPY_TELLS = [
|
|
|
620
643
|
f("content/mobile.md", "Mobile is our newest line, so we tell you what we have and haven't done.")
|
|
621
644
|
],
|
|
622
645
|
pass: [
|
|
623
|
-
f("content/clinics.md", "Built and run by us for
|
|
624
|
-
f("content/home.md", "The client portal we run for
|
|
646
|
+
f("content/clinics.md", "Built and run by us for a therapy practice, a growing one whose clinicians work remotely."),
|
|
647
|
+
f("content/home.md", "The client portal we run for a children's therapy practice."),
|
|
625
648
|
// Contractual facts, said as they are.
|
|
626
649
|
f("content/pricing.md", "Pay monthly is open to UK residents aged 18 and over, subject to status."),
|
|
627
650
|
f("content/pricing.md", "Your monthly payments do not buy the site; the buy-out price does."),
|
|
@@ -861,6 +884,46 @@ var COPY_TELLS = [
|
|
|
861
884
|
]
|
|
862
885
|
}
|
|
863
886
|
},
|
|
887
|
+
{
|
|
888
|
+
id: "absolute-negation",
|
|
889
|
+
name: "Absolute negation fragment",
|
|
890
|
+
generation: 2,
|
|
891
|
+
severity: "warn",
|
|
892
|
+
surface: "copy",
|
|
893
|
+
why: "'never pre-ticked', 'no servers', 'nothing to buy or run': a verbless fragment that opens on never, nothing, nobody or 'no' + noun. Read aloud it sounds rigid, it promises an absolute the copy rarely means, and it defines the thing by what it is not.",
|
|
894
|
+
fix: "Say what happens, as a sentence: 'A guardian ticks the consent box in the email.', 'Microsoft runs the servers.' Keep the absolute only where it is literally true and the reader needs it, and give it a subject and a verb.",
|
|
895
|
+
detect: phrases([ABSOLUTE_NEGATION], quoted, allText, notAbsoluteFragment),
|
|
896
|
+
fixtures: {
|
|
897
|
+
flag: [
|
|
898
|
+
// From a 92EL deck that passed the gate, October 2026.
|
|
899
|
+
f("content/deck.md", "A guardian consents by email; never pre-ticked."),
|
|
900
|
+
f("content/deck.md", "Pay per user per month; no servers."),
|
|
901
|
+
f("content/deck.md", "Microsoft runs it, so there is less to manage: nothing to buy or run."),
|
|
902
|
+
f("content/deck.md", "Players register on their phone. Nothing on-site to join."),
|
|
903
|
+
f("content/deck.md", "Each player is checked against the list once, no duplicates."),
|
|
904
|
+
f("app/slide.tsx", `export const note = "Nobody to chase for a form.";`)
|
|
905
|
+
],
|
|
906
|
+
pass: [
|
|
907
|
+
f("content/report.md", "Players never open the lists."),
|
|
908
|
+
f("content/report.md", "Nothing is copied by hand."),
|
|
909
|
+
f("content/report.md", "It is not tested at national scale."),
|
|
910
|
+
f("content/report.md", "Never share your password with anyone."),
|
|
911
|
+
f("content/report.md", "No one knows the password but the club secretary."),
|
|
912
|
+
// A Harvard citation with no date.
|
|
913
|
+
f("content/report.md", "Children's data needs a lawful basis (ICO, no date)."),
|
|
914
|
+
f("content/report.md", "- Information Commissioner's Office (ICO), no date. Children and the UK GDPR."),
|
|
915
|
+
// A form answer option.
|
|
916
|
+
f("content/form.md", "Is the player under 18?\n\n- Yes\n- No"),
|
|
917
|
+
f("app/form.tsx", `export const options = ["Yes", "No"];`),
|
|
918
|
+
f("content/form.md", "No, the player is 18 or over."),
|
|
919
|
+
f("content/home.md", "We no longer post paper forms."),
|
|
920
|
+
f("content/report.md", "If the check fails, nothing new appears in the list."),
|
|
921
|
+
// The blocking tells report these.
|
|
922
|
+
f("content/home.md", "We reply within a day. No obligation, no spam."),
|
|
923
|
+
f("app/list.tsx", `export const empty = "No results found.";`)
|
|
924
|
+
]
|
|
925
|
+
}
|
|
926
|
+
},
|
|
864
927
|
{
|
|
865
928
|
id: "where-x-meets-y",
|
|
866
929
|
name: "'Where X meets Y'",
|
|
@@ -2506,7 +2569,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2506
2569
|
generation: 1,
|
|
2507
2570
|
severity: "warn",
|
|
2508
2571
|
surface: "source",
|
|
2509
|
-
why: "When every block fades up on scroll, the motion stops meaning anything and the page feels slow.
|
|
2572
|
+
why: "When every block fades up on scroll, the motion stops meaning anything and the page feels slow. One salon client's site had 70, the studio's own 40, a therapy practice's 27.",
|
|
2510
2573
|
fix: `Keep one gated signature moment per page (STANDARD section 7) and let the rest arrive still. The limit here is ${REVEAL_LIMIT} per scan.`,
|
|
2511
2574
|
detect: revealHits,
|
|
2512
2575
|
fixtures: {
|
|
@@ -2522,7 +2585,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2522
2585
|
generation: 1,
|
|
2523
2586
|
severity: "warn",
|
|
2524
2587
|
surface: "source",
|
|
2525
|
-
why: "rounded-full on every button, badge and tag is the framework's friendliest default.
|
|
2588
|
+
why: "rounded-full on every button, badge and tag is the framework's friendliest default. One salon client's site had 162, the studio's own 125. A shape used everywhere is not a shape language.",
|
|
2526
2589
|
fix: `Choose the shape language on purpose (and record it), then use the pill only where it means something. The limit here is ${PILL_LIMIT} per scan.`,
|
|
2527
2590
|
detect: pillHits,
|
|
2528
2591
|
fixtures: {
|
|
@@ -2537,7 +2600,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2537
2600
|
generation: 1,
|
|
2538
2601
|
severity: "warn",
|
|
2539
2602
|
surface: "source",
|
|
2540
|
-
why: "A generator exports the whole shadcn/ui kit whether the site uses it or not.
|
|
2603
|
+
why: "A generator exports the whole shadcn/ui kit whether the site uses it or not. One client's journal site ships a full set under its own book, vine and petal assets.",
|
|
2541
2604
|
fix: "Delete the primitives nothing imports. Restyle the ones that stay so they belong to this site.",
|
|
2542
2605
|
detect: shadcnHits,
|
|
2543
2606
|
fixtures: {
|
|
@@ -2595,7 +2658,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2595
2658
|
f3("components/Hero.tsx", `<span className="font-serif italic">reimagined</span>`),
|
|
2596
2659
|
f3("app/globals.css", `.hero h1 { font-style: italic; }`)
|
|
2597
2660
|
],
|
|
2598
|
-
pass: [f3("components/Hero.tsx", `<h1 className="font-display text-6xl">Nails in
|
|
2661
|
+
pass: [f3("components/Hero.tsx", `<h1 className="font-display text-6xl">Nails in Kettering</h1>
|
|
2599
2662
|
<p><em>Walk-ins</em> welcome.</p>`)]
|
|
2600
2663
|
}
|
|
2601
2664
|
},
|
|
@@ -2611,11 +2674,11 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2611
2674
|
fixtures: {
|
|
2612
2675
|
flag: [
|
|
2613
2676
|
f3("components/Hero.tsx", `<span className="inline-flex rounded-full border px-3 py-1 text-xs">Now booking</span>
|
|
2614
|
-
<h1>Nails in
|
|
2677
|
+
<h1>Nails in Kettering</h1>`),
|
|
2615
2678
|
f3("components/Hero.tsx", `<span className={cn("rounded-full px-3 text-xs", "bg-rose-500/10")}>New</span>
|
|
2616
2679
|
<h1>Nails</h1>`)
|
|
2617
2680
|
],
|
|
2618
|
-
pass: [f3("components/Hero.tsx", `<h1>Nails in
|
|
2681
|
+
pass: [f3("components/Hero.tsx", `<h1>Nails in Kettering</h1>
|
|
2619
2682
|
<span className="inline-flex rounded-full border px-3 py-1 text-xs">Tag</span>`)]
|
|
2620
2683
|
}
|
|
2621
2684
|
},
|
|
@@ -2630,7 +2693,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2630
2693
|
detect: heroCtaPairHits,
|
|
2631
2694
|
fixtures: {
|
|
2632
2695
|
flag: [
|
|
2633
|
-
//
|
|
2696
|
+
// A reviewer's #5 and #36 (Pastel 986559): the studio's own hero and process close.
|
|
2634
2697
|
f3(
|
|
2635
2698
|
"src/components/Hero.astro",
|
|
2636
2699
|
`<section class="hero">
|
|
@@ -2639,20 +2702,20 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2639
2702
|
<a href="/work" class="btn btn-ghost">See our work</a>
|
|
2640
2703
|
</section>`
|
|
2641
2704
|
),
|
|
2642
|
-
// The null build's hero, verbatim in shape (
|
|
2705
|
+
// The null build's hero, verbatim in shape (an electrician brief's null pages, in the private dd-craft-corpus).
|
|
2643
2706
|
f3(
|
|
2644
2707
|
"index.html",
|
|
2645
2708
|
`<nav><a class="btn" href="#contact">Quote</a></nav>
|
|
2646
2709
|
<section id="hero" class="hero">
|
|
2647
|
-
<h1>Electricians in
|
|
2710
|
+
<h1>Electricians in Reading</h1>
|
|
2648
2711
|
<a href="#contact" class="btn btn-primary">Book a Free Quote</a>
|
|
2649
|
-
<a href="tel:+
|
|
2712
|
+
<a href="tel:+441632960210" class="btn btn-outline">Call 01632 960 210</a>
|
|
2650
2713
|
</section>`
|
|
2651
2714
|
),
|
|
2652
2715
|
f3(
|
|
2653
2716
|
"app/page.tsx",
|
|
2654
2717
|
`<section className="relative py-24">
|
|
2655
|
-
<h1 className="text-5xl">Nails in
|
|
2718
|
+
<h1 className="text-5xl">Nails in Kettering</h1>
|
|
2656
2719
|
<div className="flex gap-4">
|
|
2657
2720
|
<Button asChild><a href="/book">Book now</a></Button>
|
|
2658
2721
|
<Button variant="outline">View treatments</Button>
|
|
@@ -2681,7 +2744,7 @@ const inter = Inter({ subsets: ["latin"] });`),
|
|
|
2681
2744
|
</header>`
|
|
2682
2745
|
),
|
|
2683
2746
|
f3("app/page.tsx", `<section className="py-24">
|
|
2684
|
-
<h1 className="text-5xl">Nails in
|
|
2747
|
+
<h1 className="text-5xl">Nails in Kettering</h1>
|
|
2685
2748
|
<a className="rounded bg-ink px-5 py-3" href="/book">Book now</a>
|
|
2686
2749
|
</section>
|
|
2687
2750
|
<section><a className="rounded bg-ink px-5 py-3" href="/prices">Prices</a></section>`)
|
|
@@ -3457,13 +3520,15 @@ var HOUSE = {
|
|
|
3457
3520
|
"stock-email-line": { tier: "review", label: "stock email line, start with the reason for writing and end with the next step" },
|
|
3458
3521
|
// New 2026-10-19 (founder): review first, like every new tell, until estate hits are read.
|
|
3459
3522
|
"matey-register": { tier: "review", label: "matey register, write plain and complete British English" },
|
|
3460
|
-
// New 2026-10-20 (founder, from
|
|
3523
|
+
// New 2026-10-20 (founder, from a reviewer's notes): review first, like every new tell.
|
|
3461
3524
|
"confession-line": { tier: "review", label: "confession line, scope the work by saying precisely what is true" },
|
|
3462
3525
|
// New 2026-10-23 (a 92EL report passed the gate with three): review, because a
|
|
3463
3526
|
// genuine disclaimer has the same shape. The gerund pair joined not-just-but.
|
|
3464
3527
|
// New 2026-10-23 (founder, from 92EL deliverables): review, since a short slide label is often fine.
|
|
3465
3528
|
"parallel-pair": { tier: "review", label: "verbless parallel pair, write the sentence it stands for" },
|
|
3466
3529
|
"trailing-contrast": { tier: "review", label: "trailing contrast. Say the positive claim, unless it is a disclaimer the reader needs" },
|
|
3530
|
+
// New 2026-10-26 (founder, from a 92EL deck): review first, like every new tell, until estate hits are read.
|
|
3531
|
+
"absolute-negation": { tier: "review", label: "absolute negation fragment. Say what happens, as a sentence with a subject and a verb" },
|
|
3467
3532
|
"ai-vocabulary": { tier: "review", label: "AI vocabulary" },
|
|
3468
3533
|
"stock-phrase": { tier: "review", label: "stock phrase" },
|
|
3469
3534
|
"hollow-imperative": { tier: "review", label: "hollow imperative" },
|
|
@@ -4232,7 +4297,7 @@ var IMAGERY_TELLS = [
|
|
|
4232
4297
|
];
|
|
4233
4298
|
|
|
4234
4299
|
// src/character/check.ts
|
|
4235
|
-
var CATALOGUE_VERSION = "2026.10.
|
|
4300
|
+
var CATALOGUE_VERSION = "2026.10.9";
|
|
4236
4301
|
var CATALOGUE = [
|
|
4237
4302
|
...[...SOURCE_TELLS, ...COPY_TELLS, ...RESEARCH_TELLS, ...DENSITY_TELLS].map((t) => RENDERED_PATHS[t.id] ? { ...t, rendered: RENDERED_PATHS[t.id] } : t),
|
|
4238
4303
|
...SPECIFICITY_COPY_TELLS,
|