Changelog
2.4.0
A minor release: one new entry, @sonordev/contracts/schema-placeholders, and the llms.txt sanitizers stop cutting mid-word. Every other entry is unchanged.
- New entry
@sonordev/contracts/schema-placeholders: whether stored JSON-LD carries a template's unfilled slots, so the API that serves it and the site that renders it drop the same nodes.withoutSchemaPlaceholders(value)returns{ value, dropped, notes }: the JSON-LD without its placeholder nodes (the same reference when nothing was found,nullwhen nothing real is left), every node it removed with the reason and the evidence, and the AI note keys it stripped from nodes it kept.findSchemaPlaceholders(value)returns just the nodes, anddescribeSchemaPlaceholder(node)turns one into a line a person can act on.- It finds reserved example domains (
example.com,example.net,example.org,.example) in URLs, hosts and emails, whole-value template names ("Example", "Your Business Name"), placeholder phones, bracketed and JSX slots ([Resident Name],{plan.name},{{business_name}}),REPLACE_WITH_markers, and objects that are only a note. A URI template's{search_term_string}, a real name that contains "Example" and a 555 number outside the fiction range are left alone.
llms:clipAtWordBoundary(text, max)cuts prose to a length without splitting a word. It ends at a sentence when one closes in the last 40% of the window, otherwise at the last whole word with an ellipsis, and never returns more thanmaxcharacters.sanitizeLlmsPublicSummaryandsanitizeLlmsDisclaimerLinenow cut with it, so a long summary ends at a sentence or a whole word instead of mid-word. The limits are unchanged (400 and 280).
2.3.0
A minor release: one new entry, @sonordev/contracts/error-page. Every other entry is unchanged.
- New entry
@sonordev/contracts/error-page: telling a rendered error page from a page's content, and copy that calls a working page missing from copy that describes it, so every writer and reader of page text answers the same way.looksLikeErrorPage(text, wordCount?)is true when stored text is an error page: a 404 in its common wordings, a framework or server error, a status page, a "something went wrong" or "temporarily unavailable" screen or a maintenance notice. It reads how the text opens, decodes entities, doesn't depend on word boundaries (scraped text runs elements together, as in "404This page could not be found."), and leaves phone numbers, addresses and pages that only describe an error alone.usablePageText(text, wordCount?)returns the text, ornullfor an error page.findErrorPageClaim(copy, pageText?)anddescribesErrorPage(copy, pageText?)find generated copy that says the page itself is missing or unavailable ("Page Not Found", "this page is currently unavailable", "returns a 404", "a page-not-found message"). A phrase the page's own text really says is allowed, and copy about something unavailable on a working page isn't a claim about the page.findPageCopyClaim(fields, pageText?)runs that check over every string in a set of fields, JSON-LD included, and returns the first{ field, phrase }.ERROR_PAGE_MAX_WORDSandERROR_PAGE_CONTRACT_VERSIONare exported alongside.
2.2.0
A minor release: one new entry, @sonordev/contracts/proposal-sitemap. Every other entry is unchanged.
- New entry
@sonordev/contracts/proposal-sitemap: how a website proposal's site plan counts its pages, so the app that writes a proposal, the API that checks it and the page that shows it state the same numbers.countSitemapPlan(plan)counts core pages, the plan's own pages, re-published articles, pages carried over and pages added, each address once.sitemapTransformation(plan)decides whether the plan's before-and-after comparison shows. It shows only when the build is bigger than today; a rebuild that keeps the same pages never reads as "N → 0".normalizeSitemapLabels(labels)keeps the words a plan may use for pages that aren't services sold to industries.proposalPageCountIssues(sections)anddescribePageCountIssue(issue)find page and URL counts stated in a proposal's headline, summary, pricing and plan copy that don't match the plan.
2.1.1
- Docs only: the README names the Sonor API and the dashboard where it named internal repositories. No code changes.
2.1.0
A minor release: one new entry, @sonordev/contracts/voice. Every other entry is unchanged.
- New entry
@sonordev/contracts/voice: the house voice's contraction rule, so every app that checks or rewrites copy agrees on it.findUncontracted(text, options?)finds full forms that should be contractions ("do not" to "don't", "it is" to "it's", "let us look" to "let's look") and returns each one with its position, the matched text and the suggestion, first-letter case kept.- It leaves alone what can't contract where it stands: a clause-final "it
is" or "we are" (negations skip that guard, so "We do not." still becomes
"We don't."), "what it is and why", "can not only", a "what is" question, a
heading that opens with "What Is" (when
titleLikeis set), and "let us know" and "let us help". "let us" only contracts at the start of a sentence and before look, start, begin, consider, turn, see, review, compare, break, dig, recap, unpack, explore, examine, revisit, focus, go, talk, move, "get started", "step back" or "zoom out". "have" and "has" on their own are never touched, though "have not" and "has not" still contract. contractText(text, options?)applies those findings and returns the new text.findContracted(text, options?)is the reverse rule for question-form headings and FAQ questions: it finds contractions ("What's") and suggests the full form ("What is").VOICE_MASKis the one character that stands in for text the rule must not touch.
- Options:
titleLikemarks a heading or title.protectedRangestakes half-open[start, end)ranges (code, quotations, link destinations) that are never rewritten and never read as a clause end, except a protected line break, which still ends its line. An infinite bound means "from the start" or "to the end". A malformed range (NaN, not a number, start after end), or a truthy value that isn't an array, protects the whole text, so a bad range can never cause code to be rewritten. - Lines: multi-line text is read one line at a time. Nothing matches across a line break, and the end of a line ends a clause. A "\r" before a "\n" or at the very end of the text is part of the line ending. Hits carry offsets into the whole string.
- Known limits: the rule is deliberately plain. "must not only" and "will
not only" contract, "need not" contracts even where "need" is a noun, "let
us" is read by its position and next word rather than its meaning, a
decimal, a domain or an initial hides a "what is" question, a straight
apostrophe doesn't end a clause, only spaces and tabs join the words of a
pair, all-caps input gets a mixed-case suggestion, and
findContractedalways expands "'s" to "is" and "'d" to "would" and expands "ain't" like any other "n't" word ("ai not"). The README lists each one with an example. - Anything that isn't a string is read as no text, so a stray number or object can't throw.
- No new dependencies, and CommonJS and ESM builds like every other entry.
2.0.0
A major for one entry, slots, plus three new entries; every other entry is unchanged.
- Slots contract v2:
rich,linkandlistcontent types besidetext, with a structuredvaluethat the signature now covers.SLOTS_CONTRACT_VERSIONis 2 andsignSlotContent/verifySlotContentdefault to v2. Pass1as the version to keep signing and verifying the pre-v2 text-only form, which older site-kit releases verify. normalizeSlotValue(type, { content, value }): the one rule for what a valid slot of each type is, returning the canonical content and value to store and sign.parseSlotRichText(markdown): the rich-text subset (paragraphs, line breaks, bold, italic, safe links, lists) as a small AST to render as elements, never HTML.- New entry
@sonordev/contracts/slot-content: the pure half of the slots contract (types,normalizeSlotValue,isSafeSlotHref,parseSlotRichText) with nocrypto, so a browser can import it.slotsre-exports all of it. - New entry
@sonordev/contracts/site-cache: how Sonor refreshes a site's cached pages.SITE_CACHE_TAGS(the tag names site-kit's fetches carry and the Sonor API expires),SITE_REVALIDATE_PATH, the webhook payload and itsping, andisSiteRevalidatePong. - New entry
@sonordev/contracts/site-edit: Sonor's Edit on page. The messages between a framed site and the dashboard, which origins may drive the overlay (isSonorAppOrigin), andslotNeedles, the visible text used to find a slot on the page.
1.0.0
First release. The rules site-kit 6.x published as @sonordev/site-kit/*/contract
(website, color, sites, seo-pages, seo-meta, llms, forms, fleet, slots,
portfolio), as a dependency-free package, so the Sonor APIs and the
dashboard import them instead of keeping copies.