Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
508 commits
Select commit Hold shift + click to select a range
94770dd
build(web): drop postcss-nested-ancestors
damianrosellen1 Oct 14, 2026
a06cc04
build(web): run next dev on Turbopack
damianrosellen1 Oct 14, 2026
ca7f34b
perf(web): load the content-ref filters only where they render
damianrosellen1 Oct 14, 2026
11bef0e
ci: fail the build when a route outgrows its JS budget
damianrosellen1 Oct 14, 2026
e1a5352
fix(scaffold): wire inline module components through direct imports
damianrosellen1 Oct 14, 2026
fabd827
ci: size the JS budgets for both branches
damianrosellen1 Oct 14, 2026
38d91e9
refactor(studio): drop sanity-plugin-netlify
damianrosellen1 Oct 14, 2026
819616a
refactor(studio): drop the dashboard tool
damianrosellen1 Oct 14, 2026
7ca50ae
build(studio): self-host the Geist fonts
damianrosellen1 Oct 14, 2026
1737600
fix(studio): keep singletons out of the global create menu
damianrosellen1 Oct 14, 2026
61f50b9
fix(dataset-resolve): never fall back to production silently
damianrosellen1 Oct 14, 2026
1c9f330
refactor(studio): remove dead code
damianrosellen1 Oct 14, 2026
0d1db78
refactor(studio): build Presentation routes from the conventions
damianrosellen1 Oct 14, 2026
8ac3c32
refactor(studio): one preview component per job
damianrosellen1 Oct 14, 2026
c2f746f
chore(studio): remove the project-body rich-text migration
damianrosellen1 Oct 14, 2026
5e25a39
build: allow esbuild's install script explicitly
damianrosellen1 Oct 14, 2026
7c3d72d
perf(studio): cache hashed Studio assets for a year on Netlify
damianrosellen1 Oct 14, 2026
e29bad1
docs: mark migrate:seo-i18n as main-only in the CLI table
damianrosellen1 Oct 14, 2026
b83d671
refactor(studio): define the language field once
damianrosellen1 Oct 14, 2026
5839faa
fix(hooks): refuse partly staged files in pre-commit
damianrosellen1 Oct 14, 2026
5d927ff
fix(web): answer missing pages with a real 404 status
damianrosellen1 Oct 14, 2026
7cfd591
fix(web): keep the page visible while a carousel video loads
damianrosellen1 Oct 14, 2026
2ea7031
fix(web): make the stega type guard hold in components
damianrosellen1 Oct 14, 2026
1af4817
fix(web): put the sitemap on the shared freshness rule, correct the docs
damianrosellen1 Oct 14, 2026
f2a4a86
refactor(web): drop the lang effect DocumentShell already covers
damianrosellen1 Oct 14, 2026
5aeed2c
fix(web): list the same projects in "all" mode as the Studio picker o…
damianrosellen1 Oct 14, 2026
25ae4f7
refactor(web): drop barrels and type names nothing imports
damianrosellen1 Oct 14, 2026
918af2b
refactor(wiring): drop the component barrel as a wiring point
damianrosellen1 Oct 14, 2026
f8ed98b
refactor(tooling): drop checks and code that add no guarantee
damianrosellen1 Oct 14, 2026
e84df33
docs: fix statements the refactor left wrong
damianrosellen1 Oct 14, 2026
dfd466f
refactor(web): require the locale for rich-text links and inline modules
damianrosellen1 Oct 14, 2026
453ed86
refactor(web): drop the unreachable `{ code }` branch for cookie sect…
damianrosellen1 Oct 14, 2026
d3e898d
refactor(web): one metadata builder, no unreachable title steps
damianrosellen1 Oct 14, 2026
ae40d00
refactor(web): drop the unsigned webhook path
damianrosellen1 Oct 14, 2026
306b35b
refactor(web): trim query and sitemap leftovers
damianrosellen1 Oct 14, 2026
f1c29a3
refactor(web): one shape for lazy boundaries, one module stack, short…
damianrosellen1 Oct 14, 2026
06a6ea5
fix(web): build image URLs from the same dataset as the content
damianrosellen1 Oct 14, 2026
22ace44
refactor(dataset-resolve): probe datasets, drop the Management API path
damianrosellen1 Oct 14, 2026
1f23ce7
fix(studio): keep singletons out of every create menu
damianrosellen1 Oct 14, 2026
8e4473c
refactor(studio): trim preview helpers and presentation constants
damianrosellen1 Oct 14, 2026
b14a443
fix(studio): show placeholder icons in module previews
damianrosellen1 Oct 14, 2026
7735682
refactor(web): drop impossible guards from the loop player
damianrosellen1 Oct 14, 2026
e679a00
refactor(web): type the image alt, name the media helpers as the typi…
damianrosellen1 Oct 14, 2026
ffde5a2
fix(web): fall back to the default language for consent and favicon
damianrosellen1 Oct 14, 2026
438adf1
refactor(web): use main's cache tags on the variant
damianrosellen1 Oct 14, 2026
ef0650e
refactor(studio): one query and one path helper for Presentation loca…
damianrosellen1 Oct 14, 2026
fdebd02
refactor(web): drop language projections nothing reads
damianrosellen1 Oct 14, 2026
d8c1323
refactor: match main where the variant drifted without an i18n reason
damianrosellen1 Oct 14, 2026
c6fe8d5
docs(web): fetch examples typed by TypeGen, not by hand
damianrosellen1 Oct 14, 2026
cca592f
docs(studio): point to `languageField` instead of "copy it from page.ts"
damianrosellen1 Oct 14, 2026
1d879a8
fix(hooks): refuse partly staged files of every change type
damianrosellen1 Oct 14, 2026
86127df
fix(studio): require the exact language id as default language
damianrosellen1 Oct 15, 2026
fd876c7
Merge pull request #209 from backendforth/chore/branch-alignment-variant
damianrosellen1 Oct 15, 2026
d27b1fe
Merge pull request #211 from backendforth/refactor/phase-2-bugs-variant
damianrosellen1 Oct 15, 2026
623f27d
Merge pull request #213 from backendforth/refactor/phase-3-typegen-va…
damianrosellen1 Oct 15, 2026
c949c78
Merge pull request #215 from backendforth/refactor/phase-4-data-variant
damianrosellen1 Oct 15, 2026
7a8800a
Merge pull request #217 from backendforth/refactor/phase-5-bundle-var…
damianrosellen1 Oct 15, 2026
259cf5e
Merge pull request #219 from backendforth/refactor/phase-6-studio-var…
damianrosellen1 Oct 15, 2026
52cacb4
Merge pull request #221 from backendforth/refactor/review-fixes-variant
damianrosellen1 Oct 15, 2026
18d6e20
ci: run the four checks in parallel and lint with biome ci
damianrosellen1 Oct 15, 2026
ef80e64
build(deps): let dependabot update github actions on both branches
damianrosellen1 Oct 15, 2026
f669eb3
ci: pin shared workflow actions to commit SHAs on node24 majors
damianrosellen1 Oct 15, 2026
50d1083
ci: limit dependabot auto-merge to the starter repo
damianrosellen1 Oct 15, 2026
ffb8702
refactor(web): type the English fallback of the locale tables as present
damianrosellen1 Oct 15, 2026
38d2774
build(ts): tighten the web and studio compiler checks
damianrosellen1 Oct 15, 2026
b9af107
build(tooling): type-check the check-wiring and scaffold-module scripts
damianrosellen1 Oct 15, 2026
ed1968e
build(netlify): drop the header rules that never applied
damianrosellen1 Oct 15, 2026
d0e18a4
build(biome): drop the README ignore, let app format scripts fix too
damianrosellen1 Oct 15, 2026
fb677ff
chore(tooling): remove strip-readmes
damianrosellen1 Oct 15, 2026
2b48e6e
docs: keep one copy of the wiring checklist and the command tables
damianrosellen1 Oct 15, 2026
9ceb601
chore(scripts): drop update scripts that pnpm's built-in shadows
damianrosellen1 Oct 15, 2026
c8c2b9f
docs: correct statements that no longer match the code
damianrosellen1 Oct 15, 2026
ab0b76d
docs: describe the stricter typecheck, format scripts and netlify.toml
damianrosellen1 Oct 15, 2026
af47c8f
docs(env): tighten the .env.example files
damianrosellen1 Oct 15, 2026
f9e1858
docs: describe the parallel CI jobs and the Actions updates
damianrosellen1 Oct 15, 2026
c95012b
docs(agents): make the Copilot file a pointer and define mirror lines
damianrosellen1 Oct 15, 2026
25c0e6c
docs(mirrors): cut CLAUDE.md files and Cursor rules to pointers
damianrosellen1 Oct 15, 2026
cd41c16
docs: fix three stale comments
damianrosellen1 Oct 15, 2026
06bb29d
docs(copilot): keep the rules a review needs in the Copilot file
damianrosellen1 Oct 15, 2026
4661ca6
docs(env): drop the main-only migration tokens from the Studio example
damianrosellen1 Oct 15, 2026
6a5499c
Merge pull request #223 from backendforth/refactor/phase-7-tooling-va…
damianrosellen1 Oct 15, 2026
47f6b73
fix(i18n): resolve a plain-string value to nothing instead of throwing
damianrosellen1 Oct 15, 2026
5e9f3ff
fix(web): localize the 404 tab title
damianrosellen1 Oct 15, 2026
ca8cf70
feat(cookies): type the banner's sections and privacy link
damianrosellen1 Oct 15, 2026
20bfd89
feat(i18n): localize the cookie banner's default texts
damianrosellen1 Oct 15, 2026
4c10f5e
fix(cookies): restart the banner when its language changes
damianrosellen1 Oct 15, 2026
d4471f9
build(studio): remove @sanity/code-input
damianrosellen1 Oct 15, 2026
0dbb674
docs: describe the typed, localized cookie banner
damianrosellen1 Oct 15, 2026
64bf051
fix(cookies): require the policy link only while the banner is on
damianrosellen1 Oct 15, 2026
9857e62
perf(i18n): keep the cookie banner's defaults out of the client bundle
damianrosellen1 Oct 15, 2026
45533e2
feat(studio): migrate the cookie banner to typed sections
damianrosellen1 Oct 15, 2026
7e87725
fix(cookies): allow only http(s) URLs as the banner's policy link
damianrosellen1 Oct 15, 2026
872bc3d
fix(studio): give a migrated policy link the title the link type requ…
damianrosellen1 Oct 15, 2026
12c238a
fix(cookies): prefix a fallback banner's policy link with its own lan…
damianrosellen1 Oct 15, 2026
3a2f49b
Merge pull request #225 from backendforth/refactor/phase-8-i18n-capti…
damianrosellen1 Oct 15, 2026
5064808
fix(carousel): size multi-slide rows in css from the media dimensions
damianrosellen1 Oct 15, 2026
34fd8dc
refactor(modules): one wrapper per module, lcp priority for carousels…
damianrosellen1 Oct 15, 2026
766ee87
fix(media): let mux pick the poster frame when none is set
damianrosellen1 Oct 15, 2026
7d23e21
perf(media): poster in the player's first paint, preload only for aut…
damianrosellen1 Oct 15, 2026
0268834
refactor(media): drop the inert parts of the loop hls setup
damianrosellen1 Oct 15, 2026
1e6162e
fix(media): srcset reaches the asset width, sizes match the grid
damianrosellen1 Oct 15, 2026
5c45c5e
refactor(media): remove dead fill height, one loop-intent helper
damianrosellen1 Oct 15, 2026
e81c921
perf(queries): project media by field type, inline modules only resolved
damianrosellen1 Oct 15, 2026
43c6285
fix(revalidate): purge on asset changes, drop per-slug tags
damianrosellen1 Oct 15, 2026
77f9d48
fix(i18n): one label table, regional locales use their base language
damianrosellen1 Oct 15, 2026
7cafec6
fix(content-refs): keep the editor's order, resolve labels on the server
damianrosellen1 Oct 15, 2026
95e7185
fix(routing): proxy skips api routes by segment, redirects keep the q…
damianrosellen1 Oct 15, 2026
7eabd4c
fix(rich-text): function links work, internal links navigate client-side
damianrosellen1 Oct 15, 2026
d3fc4b7
fix(styles): pill controls get their size again, unused utilities go
damianrosellen1 Oct 15, 2026
5e30a27
refactor(queries): drop unread fields, reuse the shared fragments
damianrosellen1 Oct 15, 2026
649de6d
fix(seo): cut the share image to 1200×630 from the editor's crop
damianrosellen1 Oct 15, 2026
f9941dd
refactor(sanity): drop unused image helpers and the second live client
damianrosellen1 Oct 15, 2026
4d6e8ea
build(web): drop the direct @portabletext/react dependency
damianrosellen1 Oct 15, 2026
4e25faf
docs(web): correct stale comments on media, lazy images and the bundl…
damianrosellen1 Oct 15, 2026
844aae1
refactor(web): keep file-internal helpers private
damianrosellen1 Oct 15, 2026
677224d
refactor(studio): name the error settings schema file after its type
damianrosellen1 Oct 15, 2026
080acf7
fix(studio): drop the 500-page fields nobody renders
damianrosellen1 Oct 15, 2026
1e32190
fix(studio): stop seeding tracker banner descriptions in English
damianrosellen1 Oct 15, 2026
f51c58a
fix(studio): stop claiming the default language drives the Studio
damianrosellen1 Oct 15, 2026
b9e5f7e
fix(studio): don't require a title on rich-text link annotations
damianrosellen1 Oct 15, 2026
491f764
fix(studio): ask for the scroll-to anchor id only where it is used
damianrosellen1 Oct 15, 2026
08e8132
fix(studio): stop flagging 2160p Mux assets for re-encode
damianrosellen1 Oct 15, 2026
71b5213
fix(studio): show loop posters and require a slide in the carousel
damianrosellen1 Oct 15, 2026
dada64d
refactor(studio): reuse hasMuxAsset in module.media validation
damianrosellen1 Oct 15, 2026
eb2c4b1
refactor(scaffold-module): start scaffolded modules with a heading field
damianrosellen1 Oct 15, 2026
3e51f4b
docs(studio): correct stale READMEs and comments
damianrosellen1 Oct 15, 2026
e71070b
refactor(studio): give the cookie banner a privacy icon
damianrosellen1 Oct 15, 2026
95bfc46
refactor(studio): remove dead code and leftovers
damianrosellen1 Oct 15, 2026
399632a
fix(studio): fall back to localhost when the preview origin is empty
damianrosellen1 Oct 15, 2026
b644ea0
refactor(studio): type the env variables the Studio actually reads
damianrosellen1 Oct 15, 2026
10fd4ef
refactor(studio): give seo.page and seo.fallback a file each
damianrosellen1 Oct 15, 2026
3adbf2b
refactor(studio): move the work singleton next to home
damianrosellen1 Oct 15, 2026
837b760
build(studio): drop the unused studio typegen output
damianrosellen1 Oct 15, 2026
1e0e867
refactor(studio): one label helper for previews, name the slug check …
damianrosellen1 Oct 15, 2026
3a13b91
fix: align banner and carousel details with main
damianrosellen1 Oct 15, 2026
b14b90a
fix(routing): skip languages the site doesn't serve, alternates use t…
damianrosellen1 Oct 15, 2026
c66d05c
fix(studio): new documents start in the default language, one singlet…
damianrosellen1 Oct 15, 2026
0238b0c
docs(web): cut stale fetch examples from the data-layer READMEs
damianrosellen1 Oct 15, 2026
b419c29
docs(web): drop history notes and stale lang/theme claims
damianrosellen1 Oct 15, 2026
48f2ebd
docs: keep dataset rules and the document-type checklist in one place
damianrosellen1 Oct 15, 2026
18ee3b7
docs: make AGENTS.md examples hold on both branches
damianrosellen1 Oct 15, 2026
fd9761b
docs: the variant's language field starts in the default language
damianrosellen1 Oct 15, 2026
21ddb5a
docs(web): content refs no longer project the selection field
damianrosellen1 Oct 15, 2026
dc2c12c
fix(web): tolerate main-shaped titles from a shared dataset
damianrosellen1 Oct 15, 2026
35d9fbd
docs: say why the variant still resolves localized link labels
damianrosellen1 Oct 15, 2026
0d74dca
refactor(links): drop the scroll-to link function
damianrosellen1 Oct 15, 2026
175de9d
fix(media): reduced motion mid-play pauses the clip instead of unmuti…
damianrosellen1 Oct 15, 2026
d5884cc
fix(studio): a language id is never a page slug
damianrosellen1 Oct 15, 2026
985d4d7
fix(modules): a leading text module passes priority to its first embed
damianrosellen1 Oct 15, 2026
430d8a3
fix(modules): draw the divider between shown modules
damianrosellen1 Oct 15, 2026
f0a3108
refactor(styles): element defaults in @layer base
damianrosellen1 Oct 15, 2026
99221ae
refactor(modules): module.text names its heading `heading`
damianrosellen1 Oct 15, 2026
f8b97b0
fix(studio): release versions don't count as a second singleton
damianrosellen1 Oct 15, 2026
bb8fb6c
refactor(carousel): keep slides in one module.media list
damianrosellen1 Oct 15, 2026
234664f
refactor(sanity): stop projecting image fields nothing reads
damianrosellen1 Oct 15, 2026
89e7e9f
fix(media): only the active carousel loop plays
damianrosellen1 Oct 15, 2026
2d01f06
fix(media): pick the loop's stream tier once, at first attach
damianrosellen1 Oct 15, 2026
f1f4607
fix(media): keep the loop poster URL stable through hydration
damianrosellen1 Oct 15, 2026
be6216c
refactor(media): build every still through one URL pipeline
damianrosellen1 Oct 15, 2026
e298c21
refactor(media): size each media element once
damianrosellen1 Oct 15, 2026
7e442ae
refactor(media): one renderer per media kind for modules and slides
damianrosellen1 Oct 15, 2026
b73bc50
refactor(media): resolve carousel and sound labels on the server
damianrosellen1 Oct 15, 2026
011d842
fix(error): readable retry button and shared focus ring
damianrosellen1 Oct 15, 2026
e82c8ec
fix(styles): resolve font stacks where the next/font class sits
damianrosellen1 Oct 15, 2026
fe75ecb
refactor(styles): drop tailwind v3 leftovers from theme.css
damianrosellen1 Oct 15, 2026
ac78c42
refactor(styles): drop dead tokens and repeated tier values
damianrosellen1 Oct 15, 2026
1e795fb
refactor(styles): rich text rules in the components layer
damianrosellen1 Oct 15, 2026
2b66caf
refactor(styles): list markers only in rich text
damianrosellen1 Oct 15, 2026
c0e5f38
refactor(navigation): nav link states in navLinkButtonClass
damianrosellen1 Oct 15, 2026
e2b1c2e
refactor(styles): title spacing from the containers
damianrosellen1 Oct 15, 2026
4a379fd
refactor(layout): one page container class
damianrosellen1 Oct 15, 2026
f472390
refactor(layout): style the body once
damianrosellen1 Oct 15, 2026
6d951be
fix(app): global error page uses the site tokens and error markup
damianrosellen1 Oct 15, 2026
8c6fd98
fix(styles): balance headings only, pretty-wrap running text
damianrosellen1 Oct 15, 2026
c83fc58
fix(theme): restore js-enabled with the theme after a locale switch
damianrosellen1 Oct 15, 2026
cd7abc2
docs(styles): document the layer rules and the typography files
damianrosellen1 Oct 15, 2026
19a7fba
docs(styles): point the breakpoint copies at the current media code
damianrosellen1 Oct 15, 2026
e5b49f3
style(carousel): name the thumbnail radius explicitly
damianrosellen1 Oct 15, 2026
5d98948
refactor(i18n): one plain-string policy and one plain-text helper
damianrosellen1 Oct 15, 2026
0744fae
refactor(nav): resolve both menus on the server through the link reso…
damianrosellen1 Oct 15, 2026
d1dc09c
fix(cookies): hide cookie-preferences links while the banner is off
damianrosellen1 Oct 15, 2026
d6ccb70
perf(modules): render static content-ref lists on the server
damianrosellen1 Oct 15, 2026
e0102ff
refactor(sanity): read each Sanity env value in one place
damianrosellen1 Oct 15, 2026
a373ec7
fix(seo): one production-deploy rule for robots.txt and the site origin
damianrosellen1 Oct 15, 2026
9f74a64
refactor(sanity): fix the cached-read comment and drop no-op wrappers
damianrosellen1 Oct 15, 2026
191b0e3
refactor(queries): let the route rule live only in routePathProjection
damianrosellen1 Oct 15, 2026
1d83372
style(layout): keep the skip link's 2rem radius explicit
damianrosellen1 Oct 15, 2026
471710d
refactor(web): drop what the media and content-ref changes left unused
damianrosellen1 Oct 15, 2026
1977579
refactor(navigation): header and footer on the shared container class
damianrosellen1 Oct 15, 2026
8f3bd13
refactor(studio): port the seo i18n script to a sanity migration
damianrosellen1 Oct 15, 2026
793678d
refactor(analytics): move the cookie banner switch into analytics set…
damianrosellen1 Oct 15, 2026
9daf7d2
refactor(studio): one required-when-enabled check for tracker fields
damianrosellen1 Oct 15, 2026
ad0b7c5
refactor(analytics): drop the fixed cookie-free fields of Clarity and…
damianrosellen1 Oct 15, 2026
b6b89b1
refactor(studio): static previews for the settings singletons
damianrosellen1 Oct 15, 2026
071dd2e
refactor(studio): validate a project's title media once
damianrosellen1 Oct 15, 2026
8223acc
feat(studio): links can point at the work landing and projects
damianrosellen1 Oct 15, 2026
a2fa840
refactor(studio): route facts from the presentation constants
damianrosellen1 Oct 15, 2026
6f0838b
refactor(studio): presentation locations only for routable types
damianrosellen1 Oct 15, 2026
88356a2
refactor: share the site-language rules between web and studio
damianrosellen1 Oct 15, 2026
ce1a16e
refactor(studio): load script env with node --env-file-if-exists
damianrosellen1 Oct 15, 2026
fdb5f86
docs(agents): adding a locale means UI copy, not the code fallback
damianrosellen1 Oct 15, 2026
ea3f88a
build(studio): pin the Netlify Studio's dataset per deploy context
damianrosellen1 Oct 15, 2026
66f5ce7
chore: one name per command for generate and lint
damianrosellen1 Oct 15, 2026
f4eb018
ci: say which PRs the Claude workflows reach
damianrosellen1 Oct 15, 2026
7a5439a
docs: comments that match the config next to them
damianrosellen1 Oct 15, 2026
258299e
build: biome skips the generated schema.json
damianrosellen1 Oct 15, 2026
679c12a
docs: one copy per explanation, linked from the rest
damianrosellen1 Oct 15, 2026
ad28520
refactor(tracking): resolve the cookie banner switch once in the layout
damianrosellen1 Oct 15, 2026
bdfa73d
perf(proxy): read the language query from @repo/site-languages
damianrosellen1 Oct 15, 2026
eb7a76b
docs: consent and deployment-target notes follow the current behaviour
damianrosellen1 Oct 15, 2026
2d8c0f9
fix(cache): purge link-bearing entries when work or a project changes
damianrosellen1 Oct 15, 2026
0c8d45b
fix(carousel): slides side by side are active while in view
damianrosellen1 Oct 15, 2026
5b17757
docs: rewrite the root README for the current starter
damianrosellen1 Oct 15, 2026
d966185
fix(carousel): the selected slide is active even when it is wide
damianrosellen1 Oct 15, 2026
296f944
fix: review follow-ups on migrations, consent copy and the locale guard
damianrosellen1 Oct 15, 2026
0ae39c6
fix(studio): a slug never collides with its own document's versions
damianrosellen1 Oct 15, 2026
66519d5
fix(studio): per-language singleton documents can be deleted
damianrosellen1 Oct 15, 2026
c31cd45
fix(cookies): a fallback banner speaks one language
damianrosellen1 Oct 15, 2026
48e0917
refactor(studio): presentation root route from SITE_ROOT_DOCUMENT_TYPES
damianrosellen1 Oct 15, 2026
a01da6e
fix(studio): presentation locations follow the document's language
damianrosellen1 Oct 15, 2026
88b7770
perf(web): the layout fetches languages and chrome in parallel
damianrosellen1 Oct 15, 2026
7076114
docs(studio): the favicon is read from the default language only
damianrosellen1 Oct 15, 2026
af95f89
refactor(studio): plain string labels go through plainLabel
damianrosellen1 Oct 15, 2026
2603450
docs(web): the rich text query comment describes plain blocks
damianrosellen1 Oct 15, 2026
2ba9dd2
docs(studio): tracker hints name the banner's language
damianrosellen1 Oct 15, 2026
11fcc5c
docs(scaffold): say why the component template takes locale props
damianrosellen1 Oct 15, 2026
e39bbf4
perf(web): a translation link purges only the collections it changes
damianrosellen1 Oct 15, 2026
c1ec9e7
refactor(studio): derive the translatable types from languageField
damianrosellen1 Oct 15, 2026
5e2bede
docs(agents): singleton locks and language config hold on both branches
damianrosellen1 Oct 15, 2026
0529a11
build(studio): pick the dataset at build so each Netlify site sets it…
damianrosellen1 Oct 15, 2026
5d12f53
docs: rebuild the root README from clone to production
damianrosellen1 Oct 15, 2026
a8f4716
Merge pull request #227 from backendforth/review/deep-review-fixes-va…
damianrosellen1 Oct 15, 2026
dd96408
docs(readme): spell out the revalidate webhook setup field by field
damianrosellen1 Oct 15, 2026
acf58e5
docs(readme): walk through the webhook setup without open questions
damianrosellen1 Oct 15, 2026
7b63479
Merge pull request #229 from backendforth/docs/revalidate-webhook-ste…
damianrosellen1 Oct 15, 2026
5022bc2
ci: run on pushes to a project's development branch
damianrosellen1 Oct 19, 2026
987d047
docs: ship the main + development branch model for projects
damianrosellen1 Oct 19, 2026
f3deee0
Merge pull request #233 from backendforth/docs/project-branch-model-v…
damianrosellen1 Oct 19, 2026
bc38a1e
docs(agents): fetch the starter on every sync, add the remote once
damianrosellen1 Oct 19, 2026
6c500ca
Merge pull request #234 from backendforth/docs/starter-sync-remote-va…
damianrosellen1 Oct 19, 2026
99b13cb
docs(agents): retarget PRs before deleting their base branch
damianrosellen1 Oct 20, 2026
b5fa8b8
Merge pull request #238 from backendforth/docs/retarget-before-branch…
damianrosellen1 Oct 20, 2026
21c87ce
refactor(types): derive module types from every place a module is pro…
damianrosellen1 Oct 21, 2026
f4fd6b5
refactor(tooling): let a module stay out of the page stack
damianrosellen1 Oct 21, 2026
0d40240
Merge pull request #240 from backendforth/refactor/module-page-stack-…
damianrosellen1 Oct 21, 2026
7dbe61a
fix(tooling): fail a module that only lives inside another module
damianrosellen1 Oct 21, 2026
0dd0c96
Merge pull request #242 from backendforth/fix/check-wiring-nested-onl…
damianrosellen1 Oct 21, 2026
3ff4ce2
fix(security): let the Sanity Dashboard frame the site in Presentation
damianrosellen1 Oct 22, 2026
3172ec2
fix(preview): keep draft mode inside frames while Presentation connects
damianrosellen1 Oct 22, 2026
edd3a17
Merge pull request #244 from backendforth/fix/presentation-dashboard-…
damianrosellen1 Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions .cursor/rules/core.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
description: Project-wide guardrails. Always applied.
alwaysApply: true
---

`AGENTS.md` at the repo root is canonical: read it before writing code, and change it (not these rules) when a convention changes. The per-folder `README.md` files are the deep docs it links to.

- i18n: this is `variant/document-level` — document-level i18n (one document per language), so AGENTS.md §"Branch-specific notes" wins over its field-level §i18n. Select documents in GROQ with `language == $locale`; their fields are plain and render directly. **NEVER** add `internationalizedArray*` fields or `pickLocalizedString` calls for content.
- i18n: **NEVER** edit the plugin-managed `language` field on documents.
15 changes: 15 additions & 0 deletions .cursor/rules/modules.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
description: Module pattern — schema, component and query of a content module.
globs:
- studio/schemas/objects/modules/**
- web/src/components/modules/**
- web/sanity/queries/components/modules/**
---

The wiring checklist (six points, inline opt-in) is in `AGENTS.md` §"The module pattern". Prefer `pnpm gen:module <Name>`; `pnpm check:wiring` compares every list.

- i18n: module fields are plain (`string`, `richText`, `richTextMedia`); the projection returns them as they are (the page query already selected the document's language) and the component renders them directly.
- File `module<Name>.ts`, `export const module<Name>` and `name: "module.<id>"` must match — `check:wiring` finds a module only by all three.
- Give every module schema a `preview`, or Studio lists each instance as "Untitled".
- Renaming `module.<id>` once content exists needs a data migration.
- Module components are server components. An interactive part goes behind a lazy client boundary like `web/src/components/media/LazyVideo.tsx` or `…/carousel/LazyCarouselViewport.tsx`, or it lands in every route's first-load JS.
12 changes: 12 additions & 0 deletions .cursor/rules/schemas.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
description: Sanity schema and Studio config conventions.
globs:
- studio/schemas/**
- studio/config/**
---

Canonical: `AGENTS.md` (Cookbook → "Add a document type"). Deep docs: `studio/schemas/README.md`, `studio/config/README.md`.

- i18n: document-level — a translatable document type has the hidden, read-only `language` field (`languageField` from `studio/schemas/fields/languageField.ts`; never edit it), which puts it in `TRANSLATABLE_SCHEMA_TYPES`. Content fields are plain; languages load at runtime from `siteLanguageSettings` (`studio/config/sync/supportedLanguages.ts`).
- `schemaTypes` alone shows editors nothing: a document type also needs a structure item, a routable one Presentation wiring, a singleton its role in `studio/config/singletons.ts`.
- Every `SANITY_STUDIO_*` variable ends up in the public Studio bundle — never a secret under that prefix.
13 changes: 13 additions & 0 deletions .cursor/rules/web.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
description: Next.js web app conventions — routing, i18n, components.
globs:
- web/**
---

Canonical: `AGENTS.md`. Deep docs: `web/README.md`, `web/sanity/README.md`, `web/src/i18n/README.md`.

- i18n: document-level — select per-language documents in GROQ with `language == $locale` (the design here, not `main`'s "no locale in GROQ" anti-pattern) and pass `{ locale, siteLocale }` to anything that builds hrefs. Fields arrive in the route's language and render directly.
- Server components by default; `"use client"` only for state, effects or browser APIs.
- Heavy client code that only some pages render goes behind a lazy boundary (`next/dynamic` in a `"use client"` file); CI fails a route over its first-load JS ceiling in `.github/bundle-budget.json`.
- A module gets the locale as a prop, never from the router, the URL or cookies (`AGENTS.md` §"i18n DO / DON'T").
- Build locale-prefixed URLs with the helpers in `web/src/i18n/`, never by concatenating prefixes.
11 changes: 11 additions & 0 deletions .github/bundle-budget.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$comment": "First-load JS per route, KB gzip, as `.github/scripts/bundle-report.mjs` measures it. Both branches share this file: each budget is the larger of the two measurements plus ~3 KB. Raise one only on purpose, and say why in the PR.",
"routes": {
"/[locale]": 170,
"/[locale]/[slug]": 170,
"/[locale]/work": 170,
"/[locale]/work/[slug]": 170,
"/[locale]/[...rest]": 164,
"/_not-found": 146
}
}
13 changes: 13 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# GitHub Copilot — repository instructions

`AGENTS.md` at the repo root holds this repo's guardrails: the module pattern, generated Sanity types, i18n, commands and the definition of done. Read it before suggesting a change; the per-folder `README.md` files are the deep docs it links to.

Two long-lived branches differ in one thing, where a locale lives: in the field on `main`, in the document on `variant/document-level`. Check the branch (`git rev-parse --abbrev-ref HEAD`) and read AGENTS.md → "Branch-specific notes" before touching locale code. Fixes land on `main` first and reach the variant by `git cherry-pick -x`; every PR names its `Variant-Port`. A project copied from the starter has `main` (production) and `development` (staging) instead and no `Variant-Port`; its AGENTS.md → "This repository" says so.

Copilot code review reads only this file, so the rules a review most often needs are repeated here (AGENTS.md has the reasons):

- Never edit generated files (`*.gen.*`, `studio/schema.json`); `pnpm generate` writes them and CI fails on a diff.
- Full queries go through `defineQuery`; no function calls inside a query, no `as` on Sanity data.
- Compare a fetched string to a literal only through `stegaClean` / `isStegaVariant`; never clean a whole rendered document.
- A module is wired in six places (`pnpm check:wiring` checks them); schema and component land in one commit.
- Locale handling differs by branch — judge it against AGENTS.md "Branch-specific notes", not against the other branch.
121 changes: 112 additions & 9 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,124 @@
version: 2

# Two parallel pipelines because `main` and `variant/document-level` are
# treated as independently-shipped lines of the starter, not as
# trunk + downstream. Dependabot reads this file from the default branch
# only (`main`); the `target-branch` field is what fans updates out.
#
# Each branch gets one npm and one github-actions entry:
# - monthly schedule — Dependabot's `monthly` interval always runs on the
# 1st of the month (`day` is only honored for `weekly`). The PR→Slack
# monthly digest runs on the 2nd on purpose, so the channel never gets
# bundles + digest on the same day.
# - npm: two catch-all groups per run instead of one PR per dependency:
# `all-non-major` bundles every minor + patch into one auto-mergeable
# PR (see dependabot-auto-merge.yml); `all-major` bundles every major
# into one PR that waits for manual review (e.g. Next 16→17).
# `@types/node` major bumps ignored (pinned to Node 22 LTS).
# - github-actions: every action and reusable workflow in one PR. The
# workflows pin them to commit SHAs; Dependabot moves the SHA and its
# `# vX.Y.Z` comment. Auto-merge treats the bundle like an npm one: a
# major inside it waits for review.
#
# Keep the main and variant entries in lockstep — when editing one, mirror
# the change to the other.
#
# Security advisories are NOT schedule-gated and arrive immediately on
# both branches regardless of the monthly cadence.

updates:
# ── main ───────────────────────────────────────────────────────────────────
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 10
interval: monthly
time: "13:00"
timezone: "Europe/Berlin"
open-pull-requests-limit: 5
groups:
sanity-ecosystem:
all-non-major:
patterns:
- "sanity"
- "@sanity/*"
- "sanity-plugin-*"
- "*"
update-types:
- "minor"
- "patch"
all-major:
patterns:
- "*"
update-types:
- "major"
ignore:
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]
# TypeScript 7 (the native compiler rewrite) breaks `next build` on
# Next 16: `tsc --noEmit` passes, but Next's own TypeScript detection
# fails ("do not have the required package(s) installed") because the
# package layout changed. Next's transitive @module-federation/dts-plugin
# also still pins `typescript@^4.9 || ^5`. Drop this entry once Next
# supports TS 7.
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
labels:
- "dependencies"

- package-ecosystem: github-actions
directory: "/"
schedule:
interval: monthly
time: "13:00"
timezone: "Europe/Berlin"
groups:
github-actions:
patterns:
- "*"
labels:
- "dependencies"

# ── variant/document-level ─────────────────────────────────────────────────
- package-ecosystem: npm
directory: "/"
target-branch: "variant/document-level"
schedule:
interval: monthly
time: "13:00"
timezone: "Europe/Berlin"
open-pull-requests-limit: 5
groups:
all-non-major:
patterns:
- "*"
update-types:
- "minor"
- "patch"
all-major:
patterns:
- "*"
update-types:
- "major"
ignore:
# @types/node must track the runtime (Node 22 LTS), not the latest major.
# Allow minor/patch within v22; block major bumps (e.g. 25.x).
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]
# TypeScript 7 (the native compiler rewrite) breaks `next build` on
# Next 16: `tsc --noEmit` passes, but Next's own TypeScript detection
# fails ("do not have the required package(s) installed") because the
# package layout changed. Next's transitive @module-federation/dts-plugin
# also still pins `typescript@^4.9 || ^5`. Drop this entry once Next
# supports TS 7.
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
labels:
- "dependencies"

- package-ecosystem: github-actions
directory: "/"
target-branch: "variant/document-level"
schedule:
interval: monthly
time: "13:00"
timezone: "Europe/Berlin"
groups:
github-actions:
patterns:
- "*"
labels:
- "dependencies"
8 changes: 8 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- What changes, and why. -->

<!--
`main` PR: the matching `variant/document-level` PR, or `n/a` with the reason.
Variant PR: the `main` PR it ports, or `variant-only`.
See AGENTS.md → "Porting fixes between branches".
-->
Variant-Port:
99 changes: 99 additions & 0 deletions .github/scripts/bundle-report.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// @ts-check
/**
* First-load JS per route of the web build, gzipped — the number the browser downloads before
* a page is interactive.
*
* Usage: node .github/scripts/bundle-report.mjs (after `pnpm --filter web run build`)
*
* Reads `web/.next/diagnostics/route-bundle-stats.json`, which `next build` writes with the
* chunk list of every route, and gzips each chunk (level 9). Writes a Markdown table to
* `$GITHUB_STEP_SUMMARY` when set (the `build (web)` job), to stdout otherwise.
*
* Budget gate: `.github/bundle-budget.json` maps routes to a ceiling in KB gzip. A route over
* its ceiling fails the run (exit 1) after the table is written, and so does a budget for a
* route the build doesn't have; a route without one only shows `—`.
*/

import { appendFileSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { gzipSync } from "node:zlib";

const webDir = fileURLToPath(new URL("../../web", import.meta.url));
const statsFile = join(webDir, ".next/diagnostics/route-bundle-stats.json");
const budgetFile = fileURLToPath(
new URL("../bundle-budget.json", import.meta.url),
);

/** @type {Record<string, number>} route → ceiling in KB gzip */
const budgets = JSON.parse(readFileSync(budgetFile, "utf8")).routes;

/** @type {{ route: string; firstLoadChunkPaths: string[] }[]} */
const routes = JSON.parse(readFileSync(statsFile, "utf8"));

/** @type {Map<string, { raw: number; gzip: number }>} */
const chunkSizes = new Map();
/** @param {string} chunk */
function sizeOf(chunk) {
let size = chunkSizes.get(chunk);
if (!size) {
const source = readFileSync(join(webDir, chunk));
size = { raw: source.length, gzip: gzipSync(source, { level: 9 }).length };
chunkSizes.set(chunk, size);
}
return size;
}

/** @param {number} bytes */
const kb = (bytes) => `${(bytes / 1024).toFixed(1)} KB`;

const rows = routes.map(({ route, firstLoadChunkPaths }) => {
let raw = 0;
let gzip = 0;
for (const chunk of firstLoadChunkPaths) {
const size = sizeOf(chunk);
raw += size.raw;
gzip += size.gzip;
}
const budget = budgets[route];
const over = budget !== undefined && gzip / 1024 > budget;
return { route, chunks: firstLoadChunkPaths.length, raw, gzip, budget, over };
});

const table = [
"### First-load JS per route",
"",
"| Route | Chunks | Raw | Gzip | Budget |",
"|---|---:|---:|---:|---:|",
...rows.map(
(row) =>
`| \`${row.route}\` | ${row.chunks} | ${kb(row.raw)} | **${kb(row.gzip)}** | ${
row.budget === undefined
? "—"
: `${row.over ? "❌" : "✅"} ${row.budget} KB`
} |`,
),
"",
].join("\n");

const summary = process.env.GITHUB_STEP_SUMMARY;
if (summary) appendFileSync(summary, `${table}\n`);
else console.log(table);

const overBudget = rows.filter((row) => row.over);
for (const row of overBudget) {
console.error(
`✗ ${row.route}: ${kb(row.gzip)} gzip exceeds its ${row.budget} KB budget (.github/bundle-budget.json).`,
);
}
/* A budget for a route the build no longer has would pass forever. */
const builtRoutes = new Set(rows.map((row) => row.route));
const orphanBudgets = Object.keys(budgets).filter(
(route) => !builtRoutes.has(route),
);
for (const route of orphanBudgets) {
console.error(
`✗ ${route}: has a budget in .github/bundle-budget.json but no route in the build.`,
);
}
if (overBudget.length > 0 || orphanBudgets.length > 0) process.exit(1);
Loading
Loading