# @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.
