Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
652 commits
Select commit Hold shift + click to select a range
a4cb5f2
docs: rebuild developer docs + marketing, add mega-menu and mobile nav
schettn Jun 24, 2026
7883b6d
chore: keep design/plan notes (dd/) and tarballs out of git
schettn Jun 24, 2026
9d34eed
feat(pylon-db): app-scoped cross-cutting abilities; drop .models() me…
schettn Jun 24, 2026
fbfa23a
docs: teach @app.model/queue authoring; static + cross-cutting abilities
schettn Jun 24, 2026
d1dc6f4
feat(pylon-dev): pylon mcp server + stratified verify
schettn Jun 24, 2026
a28398a
feat(pylon-dev): pylon eval — A/B agent-usefulness harness
schettn Jun 24, 2026
2b13972
docs(bench): document pylon eval auth (API key / subscription)
schettn Jun 24, 2026
846875b
feat(pylon-dev): eval metrics — MCP-usage count + full report file
schettn Jun 24, 2026
ba0aa7d
docs: migrate model/queue authoring to constructor registration
schettn Jun 25, 2026
fabf4af
feat: decorator-free model/queue registration via the app constructor
schettn Jun 25, 2026
ab5dea2
build: declare @anthropic-ai/claude-agent-sdk devDependency
schettn Jun 25, 2026
95048e1
feat(pylon-queues): forward job options through the class JobManager
schettn Jun 25, 2026
db59579
fix(pylon-dev): inspect harvests queues globally + robust detection
schettn Jun 25, 2026
971c85e
feat(pylon-dev): split server/config build to share one model graph
schettn Jun 25, 2026
e678a89
fix(pylon-db): resolve + hydrate models resiliently across bundles
schettn Jun 25, 2026
6d86ac5
fix(pylon-query): non-destructive entity merge (the partial-read bug)
schettn Jun 26, 2026
3a9fcd4
wip(v3): checkpoint across packages before keyed-count work
schettn Jul 1, 2026
42ca07d
feat(pylon-db): keyed-query batching engine — Phase 1 (explicit paths…
schettn Jul 1, 2026
fb0978f
feat(pylon-db): batchKey() marker + deriver — keyed-query Phase 2
schettn Jul 1, 2026
3a8131d
test(pylon-db): integration coverage for keyed-query batching
schettn Jul 1, 2026
1d63952
feat(pylon-db): keyed-query materialize terminals — Phase 3 (.all/.fi…
schettn Jul 1, 2026
02de8e8
feat(pylon-db): missed-batch detector — keyed-query Phase 6 (dev-only)
schettn Jul 1, 2026
c297ad6
refactor(pylon-db): unify all batchers onto one primitive — keyed-que…
schettn Jul 1, 2026
771577d
test(pylon-db): demonstrate the UNION-DISTINCT gap (overlapping paths)
schettn Jul 1, 2026
d262fd1
feat(pylon-db): UNION-DISTINCT — correct keyed count for overlapping …
schettn Jul 1, 2026
a9b2aeb
feat(pylon-db): post-commit signals + onCommit primitive
schettn Jul 2, 2026
5bb5f5b
fix(pylon-pages): type useData({ tags }) authoring form
schettn Jul 2, 2026
a1771d3
fix(pylon-query): stop id-less list from clobbering a ref list (parti…
schettn Jul 2, 2026
88def1d
feat(pylon-db): renameTable migration primitive
schettn Jul 2, 2026
ad3b3ea
feat(pylon): detect table renames in db diff (--rename-table)
schettn Jul 2, 2026
101e693
test(db): round-trip migration fuzz harness + fix 5 diff-engine bugs
schettn Jul 6, 2026
c99f7f4
feat(pylon-dev): rename the `--models` CLI flag to `--entry`
schettn Jul 6, 2026
bea7a23
feat(loader): child-process project runner + zero-config per-app migr…
schettn Jul 6, 2026
e3995d4
fix(loader): stream the project runner's child output live
schettn Jul 8, 2026
d65929a
fix(loader): exit the runner child once the command completes
schettn Jul 8, 2026
19bff06
fix(loader): close the ORM pool at command end; drain naturally
schettn Jul 8, 2026
1d495d1
feat(pylon-db): N+1 advisory + batching/signals/pagination refinements
schettn Jul 8, 2026
572088f
feat(pylon-db): hidden relations + N+1 advisory batchKey hints
schettn Jul 9, 2026
d00f5b5
feat(db): pylon db rename-app — re-point the ledger after an app rename
schettn Jul 9, 2026
cdf3f48
feat(pylon-db): hasManyThrough — batched has-many-through relation
schettn Jul 9, 2026
06340d8
fix(pylon-db): paginate() honors declared + chained orderBy (was PK-o…
schettn Jul 10, 2026
4cf387e
fix(pylon-ir): a union of model types emits a valid, implementable in…
schettn Jul 10, 2026
b994049
fix(pylon-query): client typegen emits interface/union types + __type…
schettn Jul 10, 2026
1b22b45
fix(pylon-dev): abstract resolveType discriminates by `in`, not defin…
schettn Jul 10, 2026
36e34b0
fix(pylon-dev): resolveType matches ANY unique field, aligning with t…
schettn Jul 10, 2026
82f0295
fix(pylon-query): alias conflicting union-member fields, un-alias on …
schettn Jul 10, 2026
73c4845
fix(pylon-pages): stop dropping GraphQL fields named like JS builtins
schettn Jul 10, 2026
ebe80c2
feat(pylon-db): build a fresh DB by interleaving group migrations on …
schettn Jul 13, 2026
a064005
feat(pylon-db,pylon-query): native anchor seek for jump-to-position (…
schettn Jul 13, 2026
66aa121
feat(pylon-db,pylon-dev,pylon-ir,pylon-query): single-table inheritance
schettn Jul 21, 2026
3af3533
feat(pylon-db,pylon-dev,pylon): snowflake ids + Relay global ids (gid)
schettn Jul 21, 2026
1c5359b
docs: add IDs & Global IDs page + snowflake/gid config reference
schettn Jul 21, 2026
4a16be7
fix(pylon-db): don't re-validate the immutable primary key on update
schettn Jul 21, 2026
0404742
feat(pylon-pages): Gid value class for client-side global ids
schettn Jul 21, 2026
42a631c
fix(pylon-db): don't format-validate a supplied snowflake id
schettn Jul 21, 2026
b9c532f
Revert "don't format-validate a supplied snowflake id"
schettn Jul 21, 2026
7c3f5c9
fix(pylon-db): decode gid PK/FK values on the write path
schettn Jul 21, 2026
19e36e8
fix(pylon-db): accept a subclass gid where an STI base type is expected
schettn Jul 21, 2026
2eeeacf
fix(pylon-db): decode a gid on a many-to-many link write path
schettn Jul 22, 2026
ab5c514
refactor(pylon): decode gids at the GraphQL boundary + `node` option
schettn Jul 22, 2026
abc137e
fix(pylon-db): db push handles array defaults + FK cycles
schettn Jul 22, 2026
dcc3e94
refactor(pylon): move gid namespace to the `node` option
schettn Jul 22, 2026
72ae5ae
fix(pylon-dev): run the dev server in-process so restart/Ctrl-C actua…
schettn Jul 22, 2026
4e2363f
fix(pylon-query): tolerate partial GraphQL results instead of failing…
schettn Jul 23, 2026
263352c
fix(pylon-pages): stop a reconstructed hook-return prop from leaking …
schettn Jul 31, 2026
389bb68
feat(pylon-db): field-level changes in postSave payload
schettn Aug 3, 2026
64dfe14
feat(pylon-db): set-based .update()/.delete() emit signals by default
schettn Aug 3, 2026
823bdfe
feat(db): models.Struct field + prune orphan JSON-reflection types
schettn Aug 3, 2026
91996cc
feat(pylon): make useSentry an opt-in plugin owning the Sentry middle…
schettn Aug 4, 2026
9ebaee6
docs: add coverage gate and fill documented API gaps
schettn Aug 4, 2026
abd9812
fix(pylon-db): serialize jsonb column values in rowFromInstance
schettn Aug 5, 2026
1ed3098
test(pylon-db): cover set-based update() of a top-level jsonb array
schettn Aug 12, 2026
c7e855e
feat(db): pgvector dense retrieval — vector column, ANN index, .neare…
schettn Aug 12, 2026
6fc820a
ci: move docs deploys to Coolify + per-channel canary dist-tags
schettn Aug 12, 2026
3103aa2
fix: sync pnpm-lock.yaml with pylon-queues @bull-board deps
schettn Aug 12, 2026
3da2c7b
fix(docs): migrate to server.mjs self-serving model
schettn Aug 12, 2026
bc3a760
perf(docs): slim the runner image with pnpm deploy (2.36GB -> 664MB)
schettn Aug 12, 2026
d17f93d
fix(docs): harden content path-traversal guard
schettn Aug 12, 2026
4093e0f
perf(docs): distroless non-root runner, drop source (664MB -> 532MB)
schettn Aug 12, 2026
6549559
feat(docs): add container HEALTHCHECK hitting Yoga's /health
schettn Aug 12, 2026
30e71d9
ci(canary): use uniform canary-pr-<number> dist-tag
schettn Aug 12, 2026
ddc9de2
ci: use pgvector-enabled Postgres image in db-migrations
schettn Aug 12, 2026
64acaf8
fix(e2e): build pylon-queues + pylon-query before pages/dev in pretest
schettn Aug 12, 2026
98061a4
fix(ci): canary publish actually runs + reports versions on the PR
schettn Aug 12, 2026
2f934db
fix(ci): pass GITHUB_TOKEN to changeset version (changelog-github plu…
schettn Aug 12, 2026
6dad6e6
ci: switch canary + release publishing to npm OIDC trusted publishing
schettn Aug 12, 2026
1520732
docs(rfc): consolidation plan — 2-package layout, rolldown, native de…
schettn Aug 13, 2026
728f564
refactor: move pylon-db/src -> pylon/src/db (moves only, no content c…
schettn Aug 13, 2026
c887f02
refactor: move pylon-ir/src -> pylon/src/ir (moves only, no content c…
schettn Aug 13, 2026
62c9512
refactor: move pylon-query/src -> pylon/src/query (moves only, no con…
schettn Aug 13, 2026
a91fb95
refactor: move pylon-queues/src -> pylon/src/queues (moves only, no c…
schettn Aug 13, 2026
ed7db2b
refactor: move pylon-auth/src -> pylon/src/auth (moves only, no conte…
schettn Aug 13, 2026
b6a566b
refactor: move pylon-pages/src -> pylon/src/pages (moves only, no con…
schettn Aug 13, 2026
c9ad8e3
refactor: move pylon-dev/src -> pylon/src/cli (moves only, no content…
schettn Aug 13, 2026
a7793e2
refactor: rewrite cross-package imports to @getcronit/pylon/* subpaths
schettn Aug 13, 2026
999011a
refactor(pylon): merge deps + exports map + bin + self-reference paths
schettn Aug 13, 2026
3bf1973
refactor(pylon/db): establish feature split — index=API, plugin.ts=us…
schettn Aug 13, 2026
e7bc71c
refactor(pylon): split queues + auth features (index=API, plugin=useX)
schettn Aug 13, 2026
1ed2678
refactor(pylon/pages): move plugin entry index.ts -> plugin.ts (move …
schettn Aug 13, 2026
8dad6b8
refactor(pylon/pages): runtime at ./pages, plugin at ./pages/plugin, …
schettn Aug 13, 2026
fa38290
build(pylon): rework for per-feature entries + browser runtimes (unte…
schettn Aug 13, 2026
4916690
refactor: replace folded packages with transition re-export shims
schettn Aug 13, 2026
f3a0896
chore: regenerate lockfile for consolidated workspace + shims
schettn Aug 13, 2026
481324d
build(pylon): fix cli package.json path + bring in postcss config → p…
schettn Aug 13, 2026
a672586
build(pylon): package builds green — tsconfig split + relative intern…
schettn Aug 13, 2026
043488c
build(pylon): consumer build green — tsc-alias scope + cli no-split +…
schettn Aug 13, 2026
cfb0304
refactor(consumers): migrate docs/e2e off folded packages
schettn Aug 13, 2026
f9b3e36
refactor(pylon): move folded package tests into packages/pylon/test/<…
schettn Aug 13, 2026
d4b2b43
refactor(pylon): rewire folded tests to consolidated package
schettn Aug 13, 2026
34b2116
test(pylon): fix dev/CLI test paths for consolidated layout
schettn Aug 13, 2026
1a3c84c
refactor!: delete folded package dirs; finalize single-package migration
schettn Aug 13, 2026
fe42eb8
docs: migrate content + coverage tooling to consolidated package
schettn Aug 13, 2026
38e2672
feat(create-pylon)!: scaffold consolidated @getcronit/pylon projects
schettn Aug 13, 2026
abd9692
fix(docs): use pylon dev's default runner (tsx loader) for the dev se…
schettn Aug 13, 2026
cdf596f
fix(create-pylon): scaffold correct dev/start scripts for the unbundl…
schettn Aug 13, 2026
10de561
refactor(pylon)!: move root source files into src/core/
schettn Aug 13, 2026
ca49d07
refactor(pylon)!: centralize all tests under test/ mirroring src/
schettn Aug 13, 2026
38b4fc7
fix(tsconfig): stop root globbing the monorepo; add per-project typec…
schettn Aug 13, 2026
2b05d79
fix(pylon): add test/tsconfig.json so the IDE resolves test imports
schettn Aug 13, 2026
66e26f5
fix(tsconfig): drop deprecated baseUrl (removed in TS 7.0)
schettn Aug 13, 2026
eb24570
ci: merge release + canary into one publish.yml for OIDC trusted publ…
schettn Aug 13, 2026
de47755
ci(security): restrict canary publish to same-repo PRs (skip forks)
schettn Aug 13, 2026
98739fd
ci(security): gate the release publish behind a 'release' Environment
schettn Aug 13, 2026
cbeaed7
ci: split release into ungated version + gated publish; share one env
schettn Aug 13, 2026
177acc3
ci: publish via changesets/action so release git tags are pushed
schettn Aug 13, 2026
975b4a2
docs: correct build/deploy prose for the unbundled single-package model
schettn Aug 13, 2026
d605a35
docs: update _STYLE.md guidance to single-package subpath imports
schettn Aug 13, 2026
fc46749
docs(rfc): build/dev/plugin pipeline — rolldown + native dev + BuildC…
schettn Aug 13, 2026
e7b99a0
docs(rfc): finalize plugin build contract + sketch Pillar 3 dev server
schettn Aug 13, 2026
31ca8a7
refactor(pylon): Pillar 1 — bundler-agnostic BuildController/BuildCon…
schettn Aug 13, 2026
7716e10
docs(rfc): rolldown build.js spike results — defer; add rolldown devDep
schettn Aug 13, 2026
345568d
build(pylon): add check:boundaries guard enforcing the self-ref boundary
schettn Aug 13, 2026
b63df4d
refactor(pylon): port build-client + transpile-app to rolldown (Pilla…
schettn Aug 13, 2026
ab566fe
docs(rfc): record build-client + transpile-app rolldown ports (Pillar…
schettn Aug 13, 2026
86a7420
refactor(pylon): port cli/db migration-loader to rolldown (Pillar 2)
schettn Aug 13, 2026
449b927
docs(rfc): cli/db ported to rolldown; defer usePages page build
schettn Aug 13, 2026
690b378
feat(pylon)!: remove legacy @model decorator — ORM is decorator-free
schettn Aug 13, 2026
7a861e4
Merge @model removal (claude/amazing-brahmagupta-274661) into feat/v3…
schettn Aug 13, 2026
0c09d31
fix(pylon): update stale @model reference in .search() error to decor…
schettn Aug 13, 2026
a4262d4
docs(rfc): usePages page build — spiked + deferred (rolldown 1.2.4)
schettn Aug 13, 2026
c4648f9
refactor(pages): extract bundler-agnostic useData analyzer core
schettn Aug 13, 2026
779524b
feat(pages): port the usePages page build to rolldown
schettn Aug 13, 2026
57abe5b
fix(e2e): repair stale paths from the single-package consolidation
schettn Aug 13, 2026
af3eaa9
fix(e2e): remove obsolete serveLast from all serve fixtures
schettn Aug 13, 2026
c5e17a0
fix(e2e): spawn the built server as .pylon/server.mjs, not .pylon/ind…
schettn Aug 13, 2026
0d3544e
fix(e2e): blog-build imports .pylon/pylon.config.js (renamed from con…
schettn Aug 13, 2026
13c975f
fix(e2e): blog-build reads config from the default export
schettn Aug 13, 2026
9103b84
fix(e2e): update 3 stale test assertions to match deliberate v3 changes
schettn Aug 13, 2026
47dd461
feat(pages): resolve CSS url() assets in the rolldown page build
schettn Aug 13, 2026
854e243
build(pylon): transpile-only package build (rolldown), no esbuild
schettn Aug 13, 2026
c888f60
refactor(pages): remove esbuild — migrate analyzer tests to the core
schettn Aug 13, 2026
727c8ff
feat(pages): dev-server RFC + Step 0 — usePages reload seam
schettn Aug 14, 2026
e0203f8
feat(dev): Step 1 — persistent dev worker + pages hot-swap
schettn Aug 14, 2026
e4e82e6
docs(rfc): mark DEV_SERVER Steps 0-1 done
schettn Aug 14, 2026
f6b1ab1
feat(dev): server-plane hot-swap, serving-as-plugin, client Fast Refresh
schettn Aug 17, 2026
c395241
refactor(dev): retire Tier-0 SSE reload — Vite owns all browser reloa…
schettn Aug 17, 2026
1c54676
perf(dev): retire the redundant client build — one rolldown build per…
schettn Aug 17, 2026
e92c266
fix(dev): image parity guard — module-imported images match SSR & client
schettn Aug 17, 2026
a7f9af9
feat(dev): direct-execution dev server (Stage B)
schettn Aug 17, 2026
1d0ac16
refactor(dev): delete the persistent-worker machinery (Stage C)
schettn Aug 17, 2026
705b225
test(dev): dev/prod schema conformance guard + refresh dev-loop e2e (…
schettn Aug 17, 2026
93f1056
chore(dev): use @vitejs/plugin-react; drop spikes
schettn Aug 17, 2026
049b20d
feat(serve): useNodeServer serves only on genuine Node (Workstream D,…
schettn Aug 17, 2026
cd1be65
feat(build): pylon build --standalone — self-contained deploy artifac…
schettn Aug 17, 2026
d9fc5ff
fix(pages): resolve runtime artifacts from the entry, not cwd (kill c…
schettn Aug 17, 2026
23610d1
docs(deploy): document --standalone; launcher chdir's for app-level c…
schettn Aug 17, 2026
b0dad74
feat(build): standalone Docker (distroless) + module-sync trace fix +…
schettn Aug 17, 2026
1c9d50f
feat(standalone): prune build toolchain, --include runtime data, node…
schettn Aug 17, 2026
b2687dc
refactor(standalone): exclude ALL plugin build dirs, not just use-pages
schettn Aug 18, 2026
86ada16
feat(cli): general -v/--verbose flag; gate --standalone warning detai…
schettn Aug 18, 2026
ff60f4a
docs(deploy): migrator pattern for standalone deploys
schettn Aug 18, 2026
6bd936a
feat(standalone): platform-lock guard for native binaries
schettn Aug 18, 2026
fccb4d6
refactor(cli): --verbose drives consola log level, not bespoke gating
schettn Aug 18, 2026
1406c8a
refactor(cli): route remaining build-time logs through consola (Tier-1)
schettn Aug 18, 2026
35ee17c
docs(rfc): sketch the Tier-2 runtime logger design
schettn Aug 18, 2026
114bc75
docs(rfc): logger tags (+ per-tag levels) and application-code usage
schettn Aug 18, 2026
ae0641c
docs(rfc): correct the queue-logger design against Pylon's actual queues
schettn Aug 18, 2026
bca12c9
docs(rfc): fix queue-authoring API in the logger examples
schettn Aug 18, 2026
400a897
docs(rfc): use the canonical class-based Queue API (drop legacy defin…
schettn Aug 18, 2026
9b59296
docs(rfc): correct the Sentry error path against real code
schettn Aug 18, 2026
1ab331c
docs(rfc): correct installBasePipeline wiring against real code
schettn Aug 18, 2026
5cb2b0e
docs(rfc): add the Sentry plan — a sink, not a merge
schettn Aug 18, 2026
af14ceb
docs(rfc): phase-1 implementation plan for the runtime logger
schettn Aug 18, 2026
d1276e4
feat(logger): structured runtime logger — phase 1
schettn Aug 18, 2026
4f4f8d5
docs(rfc): mark logger phase 1 as implemented
schettn Aug 18, 2026
1c6a988
feat(logger): config object + per-tag levels — phase 2
schettn Aug 18, 2026
799a05d
docs(rfc): mark logger phases 1-2 as implemented in the rollout
schettn Aug 18, 2026
4c59248
feat(logger): error logging — phase 3
schettn Aug 18, 2026
545ecd0
docs(rfc): mark logger phase 3 as implemented
schettn Aug 18, 2026
c09c607
feat(logger): queue job logging with stdout+job.log fan-out — phase 4
schettn Aug 18, 2026
dad9fb9
docs(rfc): mark logger phase 4 as implemented
schettn Aug 18, 2026
8cf4e0c
feat(logger): rich pretty dev formatter, lazily loaded — phase 5
schettn Aug 18, 2026
65a4d03
docs(rfc): mark logger phase 5 done — all 5 rollout phases implemented
schettn Aug 18, 2026
9632724
feat(logger): devtools format — colored headline + expandable record …
schettn Aug 18, 2026
96605db
refactor(queues): remove the legacy functional defineQueue API
schettn Aug 18, 2026
3b3cc75
fix(dev): clean inspector debugging, quiet warnings, split devtools l…
schettn Aug 18, 2026
0ab87e2
docs: gateway logo mark + regenerated favicon set
schettn Aug 18, 2026
ae3aa1c
chore(docs): track public/logo.png — scope the gitignore `public` rule
schettn Aug 18, 2026
1c2a1ce
fix(create-pylon): build with rolldown, not the removed esbuild
schettn Aug 18, 2026
951b26d
docs: document the runtime logger public API
schettn Aug 18, 2026
f7262b4
ci(publish): add an ungated canary-preview job
schettn Aug 18, 2026
997da4f
ci(publish): fetch main in canary-preview so changeset status resolves
schettn Aug 18, 2026
550614f
ci(publish): pin npm to 11.x for OIDC (npm@latest=12 needs node 22)
schettn Aug 18, 2026
fe29a81
ci: move runners from EOL node 20 to node 22
schettn Aug 18, 2026
fcc10f5
ci: bump pnpm 10.0.0 -> 10.34.5 to fix npm-12 publish (keep npm@latest)
schettn Aug 18, 2026
16156c3
ci(publish): drop the canary-preview job — redundant
schettn Aug 18, 2026
4092690
ci(publish): repost the canary report instead of updating in place
schettn Aug 18, 2026
52a1c40
fix(pylon): declare rolldown as a runtime dependency
schettn Aug 18, 2026
5053270
fix(pages): keep node_modules external in the SSR build
schettn Aug 18, 2026
a4ca708
fix(dev): externalize all deps in the runner + JSON/CSS loader hooks
schettn Aug 18, 2026
c3d397c
feat(pages): log route-module load failures before the recovery reload
schettn Aug 18, 2026
5869eab
fix(dev): watch only source edits, not runtime file writes
schettn Aug 19, 2026
784550a
feat(dev): navigation/reload-loop tracer with caller stack
schettn Aug 19, 2026
3bd8c9e
fix(dev): skip the version-mismatch reload in dev (was looping pagina…
schettn Aug 19, 2026
1fed170
fix(dev): resolve app path aliases in the pages analyzer so node sele…
schettn Aug 19, 2026
4912ce2
fix(build): inline JSON imports instead of externalizing them
schettn Aug 19, 2026
931e146
fix: make plugin-setup failures legible (message + cause, not embedde…
schettn Aug 19, 2026
5f0dfc3
fix(standalone): classify nft trace warnings — hide benign noise, sur…
schettn Aug 19, 2026
cc45a70
fix(db): map foreign-key violations to a legible error naming the mis…
schettn Aug 19, 2026
747e56f
fix(create-pylon)!: bring the scaffold templates back to the v3 contract
schettn Aug 19, 2026
72e515b
fix(cli): add the missing node shebang so npm-installed projects can …
schettn Aug 19, 2026
cf80998
docs(rfc): plan SSR request context (cookies) and SSR i18n for usePages
schettn Aug 20, 2026
5a0ee4b
feat(pages): first-class SSR request context + re-exported cookie hel…
schettn Aug 20, 2026
278ae4d
feat(pages): useResponseCookies — set cookies from inside an SSR render
schettn Aug 20, 2026
39f392c
fix(pages): harden useResponseCookies — secure defaults, name validat…
schettn Aug 20, 2026
b25fe13
feat(pages): SSR locale negotiation — i18n P1
schettn Aug 20, 2026
959372a
docs(rfc): sequence prefix routing ahead of catalogs; demote query to…
schettn Aug 20, 2026
e85ed05
fix(queues): reach the core logger via the self-ref, not a relative i…
schettn Aug 20, 2026
c5b7731
feat(pages): prefix locale routing — one pages/ tree, every locale
schettn Aug 20, 2026
851e722
feat(pages): <Link locale> for cross-locale links, and design the URL…
schettn Aug 20, 2026
6dca01e
feat(pages): emit canonical + hreflang for localized pages
schettn Aug 20, 2026
55ed567
feat(pages): locale-aware sitemap + warn on routes shadowed by a locale
schettn Aug 20, 2026
daab68e
feat(pages): message catalogs with typed keys and placeholders — i18n P3
schettn Aug 20, 2026
f00e106
feat(pages): plural messages + opt-in ICU seam — i18n P4
schettn Aug 20, 2026
37686da
docs(frontend): document i18n, request context and response cookies
schettn Aug 20, 2026
876fca3
fix(dev): commit vite-messages, orphaned by an over-broad `git add`
schettn Aug 20, 2026
d0b09ee
feat(core): @inContext directive — per-operation locale for resolvers
schettn Aug 20, 2026
475ba7a
feat(pages): compile @inContext into useData documents — locale-corre…
schettn Aug 20, 2026
4654405
docs: replace an invented ORM method in the i18n examples
schettn Aug 20, 2026
26cdc0b
fix(dev): compile @inContext into client documents in dev too
schettn Aug 20, 2026
a206ed9
test(e2e): keep the manual refetch trigger in the i18n fixture
schettn Aug 20, 2026
8a264c5
fix(db): read the applied-migration set under the advisory lock
schettn Aug 20, 2026
9b038ca
fix(db): refuse migrations that render to no SQL
schettn Aug 20, 2026
f361692
fix(ir): require USING for casts Postgres can't perform
schettn Aug 20, 2026
5425525
fix(pages): quote the Image placeholder URL so hydration matches
schettn Aug 20, 2026
ed71353
feat(pages): Image `priority` for the LCP image
schettn Aug 21, 2026
7c7cbfa
feat(pages): responsive srcset for Image, and stop the proxy upscaling
schettn Aug 21, 2026
a82c539
feat(pages): Image `loading` override
schettn Aug 21, 2026
cd46b93
fix(pages): usePaginatedData selector analyzer sees through non-null/…
schettn Aug 21, 2026
581267e
docs(migrations): document migrations.stateOnly
schettn Aug 21, 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
  •  
  •  
  •  
15 changes: 15 additions & 0 deletions .changeset/analyzer-tolerate-nonnull-selectors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@getcronit/pylon': patch
---

Let the usePaginatedData selector analyzer see through type-only wrappers.

A connection selector on a nullable single-entity lookup —
`usePaginatedData(q => q.post({ id })!.comments)` — needs a `!` (or an `as`) to satisfy the
type-checker, since the client types a single-entity accessor as nullable. But the selector
path walker only recognized identifiers, property accesses, calls, and parentheses, so the
`NonNullExpression` made it bail with "usePaginatedData expects a connection selector".

The walker now sees through `NonNullExpression` and `AsExpression` (both erased at runtime, so
the connection PATH is unchanged), matching the non-paginated `useData` analyzer, which already
strips non-null assertions.
27 changes: 27 additions & 0 deletions .changeset/consolidate-fat-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@getcronit/pylon': major
---

Consolidate the monorepo into a single batteries-included `@getcronit/pylon` package.

The previously separate `@getcronit/pylon-{db,ir,query,queues,auth,pages,dev}`
packages are now folded into `@getcronit/pylon` and exposed as subpath exports.
The `pylon` CLI now ships from `@getcronit/pylon` itself (no separate
`@getcronit/pylon-dev`).

### Migration

- Replace feature imports with the matching subpath:
- `@getcronit/pylon-db` → `@getcronit/pylon/db`
- `@getcronit/pylon-ir` → `@getcronit/pylon/ir`
- `@getcronit/pylon-query` → `@getcronit/pylon/query`
- `@getcronit/pylon-queues` → `@getcronit/pylon/queues`
- `@getcronit/pylon-auth` → `@getcronit/pylon/auth` (`/contract`, `/zitadel` preserved)
- `@getcronit/pylon-pages` → `@getcronit/pylon/pages`
- Plugin factories now live under a per-feature `/plugin` subpath:
- `useDatabase` → `@getcronit/pylon/db/plugin`
- `useQueues` → `@getcronit/pylon/queues/plugin`
- `useIdentity` → `@getcronit/pylon/auth/plugin`
- `usePages` → `@getcronit/pylon/pages/plugin`
- Drop every `@getcronit/pylon-*` dependency from your `package.json` and depend on
`@getcronit/pylon` alone. The `pylon` binary comes from it.
9 changes: 9 additions & 0 deletions .changeset/create-pylon-rolldown-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'create-pylon': patch
---

Build `create-pylon` with rolldown instead of esbuild. esbuild was removed when the repo
standardized its build pipeline on rolldown, which left `create-pylon`'s build script
calling a binary that no longer exists (`esbuild: not found`). It now bundles the CLI via
a `rolldown.config.mjs` — same output shape (single ESM file, deps external, shebang
preserved).
60 changes: 60 additions & 0 deletions .changeset/create-pylon-v3-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
'create-pylon': major
---

Update the scaffold templates to the v3 contract — every runtime `create-pylon` produced
failed on its first `pylon build`.

The templates still emitted the pre-`Pylon`-class shape: a named `export const graphql`
plus a `serve(app, …)` side effect in the entry. Since the compiler now type-introspects
the DEFAULT export, Node projects died with "Pylon entry must export default the app",
while Bun and Cloudflare Workers projects got further on `export default app` (the empty
singleton) and then failed with the far less obvious "Query root type must be provided" —
their resolvers silently dropped.

- The entry is now `export default new Pylon({graphql})` and is identical across runtimes:
pure, with no import-time serving side effect.
- Serving is declared in `pylon.config.ts`. Node scaffolds get `useNodeServer()`, ordered
last so the port binds only after every route (including the usePages catch-all) is
mounted; Bun, Deno and workerd need no plugin — they serve the default export of the
built `.pylon/server.mjs` themselves.
- Dropped `pylon dev -c "<cmd>"` from the Bun, Deno and Cloudflare Workers scripts. `pylon
dev` is direct in-process execution now and rejects `-c` outright.
- Deno projects get a `package.json`. `pylon dev`/`pylon build` are Node processes, and
Node's TypeScript loader reads the nearest `package.json` for ESM-vs-CJS — without
`"type": "module"` the build failed on `require(esm)` loading `pylon.config.ts`. It also
becomes the single source of truth for the dependency, so `deno.json` drops its duplicate
`imports` entry. Its `start` task uses `deno serve` (`deno run` does not serve a default
export).
- Dropped the now-unneeded `@hono/node-server` dependency from Node scaffolds (the entry no
longer imports it, and `@getcronit/pylon` depends on it).
- Fixed both Dockerfiles running `{npm,bun} run pylon build`, which is not a script in the
generated `package.json`.

Pages templates: the starter Button carried a typo'd `inline-flexxx` class, which Tailwind
does not generate — the button silently lost `display: inline-flex` and the
`items-center justify-center gap-2` layout that depends on it. `components.json` also
pointed `tailwind.config` at a `tailwind.config.js` the scaffold never emits; the scaffold
is Tailwind v4 (theme in `globals.css` under `@theme`), so that field is now `""`, which is
what `npx shadcn@latest add …` reads to target the v4 component shape.

Pages templates, Tailwind v4: the theme block is now `@theme inline`. A plain `@theme`
emits `--color-background: hsl(var(--background))` into `:root`, where custom-property
substitution happens once — the resolved colour then inherits down as a literal, so a
nested `.dark` re-declaring `--background` never reached it and `bg-background` (and even
`dark:bg-card`) stayed light. That contradicted the scaffold's own
`@custom-variant dark (&:is(.dark *))`, which targets descendants of `.dark` rather than
requiring it on `<html>`. Both scopings now work.

Pages templates: the root layout is typed with the exported `LayoutProps` instead of an
ad-hoc `{children: React.ReactNode}`, so the starter shows that a layout also receives
`params`, `searchParams`, `path` and `context`.

CLI: a bare `--features` (no values) crashed with `features is not iterable`; it now means
"no features". Added `--no-install` and `-y, --yes` so the CLI can run unattended in
scripts and CI.

A new e2e (`e2e/tests/create-pylon-scaffold.e2e.test.ts`) scaffolds every runtime and
feature combination with the shipped binary, runs the shipped `pylon build` on each, and
boots the built Node artifact to query it over HTTP — so the templates cannot drift away
from the framework unnoticed again.
19 changes: 19 additions & 0 deletions .changeset/dev-alias-node-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@getcronit/pylon': patch
---

Fix `usePaginatedData`/`useData` node selections collapsing to `{ id }` in dev.

The dev pages analyzer runs a ts-morph project that maps every non-relative import to an
empty dummy (so third-party packages don't need to be parsed). That dummy also swallowed the
app's own `@/*` path aliases, so a page importing its components and the connection node type
via `@/…` (e.g. `DataGridColumn<AuditEvent>` from `@/components` + `@/.pylon/client`) left the
project too thin for the connection pass to trace inline node-field reads — the selection
collapsed to `node { id }`. On soft navigation the client then fetched partial nodes,
normalized them, and components reading the missing fields (`e.actorLabel`, `e.metadata`)
threw. The production rolldown build was unaffected because it feeds the whole module graph
through the analyzer (every file already loaded by absolute path).

Dev now hands the analyzer the app's `tsconfig.json` so `@/*` resolves to real files, and the
per-module eager-loader follows imports transitively (through aliases, stopping at
`node_modules`) — so the dev selection matches the build byte-for-byte.
24 changes: 24 additions & 0 deletions .changeset/dev-inspector-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@getcronit/pylon': patch
---

Improve `pylon dev` debugging and quiet a dev-server warning.

- **`pylon dev --inspect [port]`** (and `--inspect-brk`) opens the Node inspector on
the dev process itself — the one that runs your resolvers — so breakpoints bind and
you get a single clean debug target. It works through a package manager
(`pnpm pylon dev --inspect`) because it doesn't rely on an inherited `--inspect` flag
that the package-manager wrapper would grab first.
- When dev *is* launched with an inherited inspector (`node --inspect …/pylon dev` or
`NODE_OPTIONS=--inspect`), the flag is now stripped from what the bundler workers
inherit (`NODE_OPTIONS` + `execArgv`), so they no longer race the app for the port —
no more `Starting inspector … address already in use` stack, and DevTools attaches to
your code instead of a worker.
- Filtered a spurious `optimizeDeps.esbuildOptions … deprecated` warning from the pages
dev server. It originates in `@vitejs/plugin-react` (the version compatible with the
transitional `rolldown-vite`); rolldown-vite honors the option but warns, and the fix
upstream needs Vite 8. The warning is dropped until those majors line up.
- With a debugger attached, the dev logger now splits its output cleanly: the terminal
keeps a single pretty line per record while the Chrome DevTools console receives the
full record as an expandable, inspectable object (via `inspector.console`, which
bypasses stdout) — no more multi-line object dumps in the terminal.
19 changes: 19 additions & 0 deletions .changeset/dev-runner-loaders.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@getcronit/pylon': patch
---

`pylon dev`: externalize all node_modules in the backend runner + add JSON/CSS loader hooks.

The dev runner now sets `ssr.external: true` so every dependency loads natively through Node
instead of Vite's SSR transform — routing CJS packages through the transform ESM-ifies them and
breaks their dynamic `require`s. That native-load path then needs two Node module hooks (registered
before the app boots):

- **JSON** — Node's ESM loader requires an explicit `with { type: 'json' }` attribute, so a
dependency doing a bare `require('./x.json')` (e.g. `i18n-iso-countries`'s `langs/*.json`) threw
`ERR_IMPORT_ATTRIBUTE_MISSING`. The resolve hook stamps `type: 'json'` on `.json` URLs.
- **CSS** — a server-side `import 'pkg/x.css'` is meaningless for SSR (styles ship via the client
build) and Node can't load `.css`; the load hook returns an empty module instead of crashing.

Together with keeping node_modules external in the build, this lets apps that pull in packages with
dynamic data `require`s or bare CSS imports run `pylon dev` and `pylon build`.
12 changes: 12 additions & 0 deletions .changeset/dev-version-mismatch-reload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@getcronit/pylon': patch
---

Fix an infinite reload loop in dev on pages that refetch on mount.

The query fetcher reloads the page when a response's `X-Pylon-Version` header differs from the
client's `window.__PYLON_VERSION__` (a production signal to pull a fresh client after a deploy).
In dev the client is stamped `'dev'` while the server sends the content-hashed pages-manifest
version, so they ALWAYS differ — and any page that refetches on mount (e.g. `usePaginatedData`'s
SWR revalidation) looped: fetch → version mismatch → reload → refetch → reload. The check now
no-ops in dev (client version `'dev'`), where HMR handles updates.
12 changes: 12 additions & 0 deletions .changeset/dev-watch-source-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@getcronit/pylon': patch
---

Fix `pylon dev` reload loop: only react to source-code edits, not runtime file writes.

The dev watcher watched the whole project and full-reloaded the browser on any file change
(outside node_modules/.pylon/.git). A request whose resolver wrote a file into the project at
runtime — a media thumbnail, cache, log, upload — tripped the watcher, which reloaded the
page, which re-ran the request, which wrote again: an infinite reload loop on that route. The
watcher now reacts only to source extensions (.ts/.tsx/.js/.jsx/.mjs/.cjs/.css) and logs which
file triggered a reload, so runtime data writes no longer cause reloads.
17 changes: 17 additions & 0 deletions .changeset/gold-boxes-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@getcronit/pylon': major
---

- Integrated `@getcronit/pylon-builder` directly into `@getcronit/pylon-dev`.
- Removed the `pylon-builder` package.
- The builder now utilizes the `esbuild` watch mode for development. This is a much faster and more efficient way to build the project.
- Implemented `pm2` for process management:
- `pm2` is now used to manage the `pylon-dev` server. After files are built, the server is restarted automatically.
- The stdout and stderr logs are logged directly with `consola`.
- Now builds a cross-environment client in `.pylon/client` using `gqty`. This will be used for pylon/pages.

### Breaking Change: Removed Client Generation Feature

- **What**: The client generation feature has been removed.
- **Why**: We have decided to use `gqty` directly to streamline the development process and reduce complexity.
- **How to Update**: Consumers should now use the [GQty CLI](https://gqty.dev/api-reference/cli#basic-usage) directly to generate their clients. Update your build scripts and development workflows to integrate `gqty` as described in the GQty documentation.
44 changes: 44 additions & 0 deletions .changeset/in-context-analyzer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
'@getcronit/pylon': minor
---

Compiled documents carry `@inContext`, so resolvers are locale-correct with no per-query
wiring.

```ts
Query: {
productName: async (id: number): Promise<string> =>
(await ProductTranslation.objects
.filter({productId: id, locale: getLocale() ?? 'en'})
.first())?.name ?? (await Product.objects.get({id})).name
}
```

```tsx
const data = useData()
data.product({id}).name // German on /de, French on /fr — nothing passed at the call site
```

With `usePages({i18n})` configured, every operation the analyzer compiles is emitted as
`query page_0($__locale: String) @inContext(locale: $__locale)`, and the query client supplies
the locale. Queries and mutations both — a mutation returns localized content too.

The locale is held **per client**, not in a module-level variable: the SSR pass builds one
client per request, so concurrent renders in different locales cannot bleed into each other.
The browser has one client and one locale, because switching locale is a document navigation.

It is merged into the variables **before** the cache key is computed, at every entry point
that computes one (`fetch`, `ensure`, `revalidate`, `refetch`) — otherwise one path would read
a different slot than another wrote. Verified end to end:

```
qd09ab19da988723b~1emsiz4 → {"serverGreeting": "Server: hallo"}
qd09ab19da988723b~1emubph → {"serverGreeting": "Server: bonjour"}
```

Same document, different variables hash. With the locale in a header these would have been one
entry, and one language would have served the other's data.

Documented in the i18n guide, including why a resolver cannot simply read the page's request
context: the SSR pass reaches GraphQL through a separate in-process request, and after
hydration there is no page request at all.
25 changes: 25 additions & 0 deletions .changeset/in-context-dev-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
'@getcronit/pylon': patch
---

Fix `@inContext` missing from client documents under `pylon dev`, and drop the dead
`createPagesClient` export.

`pylon dev` builds the SSR bundle with the rolldown analyzer and the CLIENT bundle with the
Vite one. Only the rolldown path was told about `i18n`, so dev shipped a server that knew the
locale and a client that did not: SSR rendered German, then the first refetch sent a
directive-less document and the page flipped to English. Production was unaffected — the
worst place for a difference to live, since dev is where it would be seen and dismissed.

Reproduced in a browser before fixing: the wire carried `query page_0 { serverGreeting }` with
no variables, and the DOM went `Server: hallo` → `Server: hello`.

The dev server now reads the `usePages` plugin's own options — `usePages` in dev is only a
`pages/` directory check, so the plugin is the sole source of whether i18n is configured — and
`Plugin.options` exposes them, since options passed to a plugin factory are otherwise captured
in its closure.

Also removes `createPagesClient` from the generated client. It was introduced with the
pylon-query layer and never called by anything; parameterising it with a locale (as an earlier
pass did) was polish on dead code. Apps needing a per-request SSR client can use
`createPylonQueryClient` from `@getcronit/pylon/query`, which takes a locale.
37 changes: 37 additions & 0 deletions .changeset/in-context-directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
'@getcronit/pylon': minor
---

`@inContext` — per-operation request context, read by resolvers.

```graphql
query Products($__locale: String) @inContext(locale: $__locale) {
products { name }
}
```

```ts
import {getLocale} from '@getcronit/pylon'

Query: {
greeting: (): string => translations[getLocale() ?? 'en'] ?? translations.en
}
```

The directive is defined in every emitted schema, read in `onExecute`, and exposed to
resolvers through `getLocale()` / `getInContext()`. A locale may be an inline literal or a
variable; the variable form is what compiled documents will use, so one document serves every
locale.

**Why a directive rather than an HTTP header.** `pylon-query` keys its store on
`documentId ~ variablesHash(variables)` and nothing else. With the locale in a header, the
same document with the same variables would be the same cache entry — English and German
results colliding on one key, in the client store and the hydration envelope alike. Putting
the context in the document makes it part of the cache key by construction. Shopify's
Storefront API arrived at the same directive for the same reason.

`getLocale()` returns `undefined` when the operation states no locale, rather than defaulting:
the caller did not ask, so the resolver decides what neutral means.

This is the server half. Automatic injection into `useData`-compiled documents — so a pages
app gets locale-correct resolvers with no per-query wiring — is the remaining piece.
17 changes: 17 additions & 0 deletions .changeset/inline-json-imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@getcronit/pylon': patch
---

Inline JSON imports in the build instead of externalizing them.

Both the pages SSR build and the backend transpile externalized everything under
`node_modules`, including `.json`. A bundler-visible `import x from "pkg/x.json"` (e.g. the app
importing `i18n-iso-countries/langs/de.json`) therefore survived to runtime, where Node's
strict ESM loader rejects it for lacking a `with { type: 'json' }` attribute
(`ERR_IMPORT_ATTRIBUTE_MISSING`) — crashing a plain `node .pylon/server.mjs` / standalone
artifact at plugin setup.

`.json` is now inlined by rolldown (its default, and what the client bundle already did),
keeping the output runtime-agnostic (no attribute, no loose file) rather than relying on a
per-runtime Node loader hook. A dependency that dynamically `require`s its OWN data files at
runtime is unaffected: its JS stays external, so those requires still resolve from disk.
14 changes: 14 additions & 0 deletions .changeset/legible-fk-violations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@getcronit/pylon': patch
---

Surface foreign-key violations with a message a human can act on.

A manyToMany link whose owner or target row doesn't exist failed with Postgres's opaque
`insert or update … violates foreign key constraint "<hash>"` — no hint at WHICH side, WHICH id,
or WHICH relation, buried under a kysely/pg stack. `ManyToManyManager.add`/`set` now map the
23503 SQLSTATE (via the new `foreignKeyViolation` helper + `ForeignKeyViolationError`) to a
message that names the missing model and id from the driver's `detail` — e.g. *"Cannot link
ProductVariant ↔ ProductOptionValue: ProductOptionValue \"…\" does not exist. It was referenced
but not found — likely created out of order, or removed earlier in the same operation."* — with
the driver error kept as `cause`. This mirrors the existing unique-violation (23505) mapping.
15 changes: 15 additions & 0 deletions .changeset/legible-plugin-setup-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@getcronit/pylon': patch
---

Make plugin-setup failures legible.

When a plugin's `setup()` threw, the framework wrapped it by stringifying the original error's
full `.stack` into a NEW error's message. Node then printed that blob PLUS the wrapper's own
stack — a duplicated, nested wall of text with the actual one-line cause (e.g. "Module …/de.json
needs an import attribute of type: json") buried in the middle.

The wrapper now keeps its message to one human-readable line (the underlying error's `.message`)
and attaches the original error via `cause`, so Node renders a clean `[cause]:` chain with the
real stack intact. Built-in plugins that lacked a `name` (`usePages` → `pages`, `useQueues` →
`queues`) now set one, so the failing plugin is named in the message instead of shown as `#1`.
Loading
Loading