docs
    agency-site-kit: Changelog
    v0.11.1.md

    @sonordev/agency-site-kit Changelog

    What changed in each release, for the sites that use the kit. It's pre-1.0, so a breaking change can land in a minor release; the entry says so when it does. Releases before 0.9.0 aren't listed here.

    0.11.1

    • Draft previews open on your domain. The preview route redirected to a full URL built from the incoming request, and on some hosts that isn't the address the visitor asked for: Next builds it from the hostname its server started with, and on Netlify that can be the deploy's own *.netlify.app address. The preview then opened there, without the preview cookies (they're set on your domain), and showed "not found". The route now answers with a relative Location, which the browser resolves against the address it actually requested. The draft-mode and preview cookies ride the same response, as before.
    • The preview token stays out of the address bar. Netlify adds the incoming query to a redirect whose Location has none, so visitors landed on /work/<slug>?slug=…&token=…. The route now redirects to /work/<slug>?preview=1, which Netlify passes through as is. Nothing reads preview=1: draft mode and the cookie are what unlock the draft.
    • basePath loses a trailing slash, as it already did for createRevalidateHandler, so '/work/' no longer redirects to /work//<slug>, and the slug is encoded as one path segment.
    • A basePath that isn't a local path ('work', '//cdn.example.com') now throws a TypeError when the handler is created, as it already did for createRevalidateHandler. A relative redirect to it would leave your site.

    0.11.0

    Breaking for two setups: a site that relied on the dark fallback brand (see getPortfolioBrandConfig below), and a site that set UPTRADE_API_URL without SONOR_API_URL. Everything else is additive.

    • Full docs on sonor.dev. Quickstart, proof and provenance, sections and devices, images and labels, JSON-LD, fetching, types, the brand layout, live updates and preview, live device frames, and a subpath map. The pages ship in the package (docs/, docs.json), and so does this changelog.
    • Source maps no longer ship. The package is smaller, and stepping into the kit from your browser's devtools lands in the built JavaScript.
    • The doc comments on resolvePortfolioImage and the image-typed fields now say what Sonor sends: plain image URLs. SanityImageRef is documented as the older shape those fields no longer carry at runtime. The one-time console warning for a bare image asset ID has new wording. Nothing else changed.
    • One brand fetch. AgencySiteKitLayout now fetches the brand through getPortfolioBrandConfig, which takes optional apiKey and apiUrl. The layout's brand is cached under the portfolio tag, so a brand edit in Sonor refreshes it along with your case studies, and it retries a busy API like the other fetchers do.
    • getPortfolioBrandConfig falls back to DEFAULT_BRAND_CONFIG, the light defaults the layout always used, instead of a separate dark palette, and it merges your brand over those defaults so every field is set. If you relied on the dark fallback, pass your own brand or set theme="dark" on the layout.
    • UPTRADE_API_URL is no longer read. Set SONOR_API_URL, or leave it unset for https://api.sonor.io.

    0.10.1

    • Docs only: the README's examples credit a fictional business. No code changes.

    0.10.0

    Needs @sonordev/site-kit >= 6.5.0, for the portfolio contract's metric provenance. Breaking: the kit renders nothing.

    Every agency site owns its renderer

    PortfolioPage, PortfolioIndex, PortfolioGrid, PortfolioCard, PortfolioSchema, PortfolioLeadCTA, the skeletons and error fallback, every section component, every primitive (DeviceTrifolio, SoloMacBook, ScrollReveal, AnimatedCounter and the rest) and the data-sk-mode stage contract are gone, along with the ./portfolio/client, ./portfolio/sections/*, ./portfolio/primitives/* and ./portfolio/components/* subpaths. Sites that cared how their case studies looked were rebuilding those components anyway. Build them in your site (the setup CLI now scaffolds a starter) and use buildPortfolioJsonLd in place of PortfolioSchema.

    What stays is what every renderer has to agree on:

    • Proof. curatePortfolioProof, gateMetricsDeltas, and metric provenance from @sonordev/site-kit/portfolio/contract (measured, reported, estimated, and reportedBy on a client's number). New: metricCredit (the credit a client's number wears), metricNote (that credit, or Client-reported / Estimated, or nothing for a measured number), headlineKpi (the number a card leads with: measured or attributed-reported, never an estimate, and credited) and formatMetricValue. Every site now leads its cards with the same number and credits a client's figure the same way.
    • Order. PORTFOLIO_SECTION_ORDER, orderedSections (keeps a dashboard reorder, with the hero pinned first), sectionData and projectBrandColor.
    • JSON-LD. buildPortfolioJsonLd (an Article) and jsonLdString, which escapes <.
    • Evidence. getProofRecords(slug, { site }) and the ProofRecord type, for the evidence Sonor publishes behind a case study's numbers.
    • Unchanged: the fetchers and their error contract, the device-stage decisions (shouldRenderSoloDevice, resolveSoloScreenshot), image resolution, the sanitizers and formatters, AgencySiteKitLayout, createRevalidateHandler and createPortfolioPreviewHandler.

    /portfolio, /portfolio/pure and the root entry now export the same pure rules. /portfolio/pure stays for the sites already importing it.

    Setup scaffolds a starter renderer

    agency-site-kit-setup writes app/work/_components/ (CaseStudy, WorkGrid, work.module.css): hero, challenges, strategy, results, testimonial, gallery and call to action, and the index with category links and pagination. It's server-rendered with zero client JavaScript, styled on the --sk-* brand variables, and it's yours to change. The case study page emits Article JSON-LD, and every number you didn't measure carries its note.

    • New flag: --agency-name, the publisher in the JSON-LD.
    • The loading templates are gone (the skeletons were kit components), and the error template no longer imports anything from the kit.
    • The starter is typechecked and rendered against the kit before every release, so a kit change that would break a freshly scaffolded site can't ship.

    Smaller package

    One build (ESM and CommonJS) instead of three, and nothing client-side left in it. The package went from 327 files (2.2 MB unpacked) to 82 (0.5 MB).

    0.9.0

    Needs @sonordev/site-kit >= 6.4.0 (the peer dependency was >= 5.3.0).

    Revalidation is built on site-kit's webhook handler

    createRevalidateHandler is now a configuration of createSeoRevalidationHandler from @sonordev/site-kit/llms, plus the portfolio parts, instead of its own copy of the webhook.

    • Breaking: auth is Authorization: Bearer <SONOR_API_KEY> only, compared in constant time. That's what Sonor sends. x-api-key, ?secret=, a body secret and the REVALIDATION_SECRET variable are no longer accepted, and no other key variable is read. SONOR_API_KEY is read on every call. { secret } still overrides it and may now be a function, called on every request (secret: getSonorApiKey).
    • Validated input. Bodies over 16 KB get a 413. Every path, slug and tag is checked (local paths only, no dot segments even percent-encoded, no query, fragment or [segment]), and one bad value refuses the whole call with a 400 before any cache is touched. Before, any string became a revalidation target.
    • llms.txt and llms-full.txt refresh on every call, with /sitemap.xml.
    • Fixed: detail pages, the sitemap and feeds now regenerate. Paths were revalidated with Next's 'page' type, which matches route files (/work/[slug]/page) rather than URLs, so /work/northwind-engineering, /sitemap.xml and /feed.xml regenerated nothing and only the portfolio tag was doing any work. Literal paths are now untyped.
    • Kept: basePath (default /work) and extraPaths on every call, slug / slugs mapped to ${basePath}/${slug}, the default portfolio tag when a call names none, tags revalidated with the 'max' profile, and revalidateAll regenerating the root layout.
    • Breaking: the response is now { revalidated: string[], tags: string[] } (it was { revalidated: true, targets }), which is what Sonor reads.
    • A tag-only call carrying seo now regenerates the root layout, as it does on every other Sonor site.

    Setup: the revalidate route works, and lands where Sonor calls

    • The generated route destructured a function (export const { POST } = createRevalidateHandler()), so it had no POST and failed to typecheck. It's now export const POST = createRevalidateHandler({ basePath }), and --base-path is passed through (it was ignored).
    • agency-site-kit-setup writes it to app/api/seo-revalidate/route.ts, the URL Sonor calls, instead of app/api/revalidate/route.ts, which Sonor never called.

    Portfolio

    • getPortfolioItem results carry industry_tags (industry categories, primary first) for agency industry pages.