Skip to content

chore(deps-dev): bump hono from 4.12.18 to 4.12.27 in /packages/stats#849

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/packages/stats/hono-4.12.23
Open

chore(deps-dev): bump hono from 4.12.18 to 4.12.27 in /packages/stats#849
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/packages/stats/hono-4.12.23

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jun 4, 2026

Copy link
Copy Markdown
Contributor

Bumps hono from 4.12.18 to 4.12.27.

Release notes

Sourced from hono's releases.

v4.12.27

Security fixes

This release includes fixes for the following security issues:

hono/jsx does not isolate context per request

Affects: hono/jsx, hono/jsx-renderer. During SSR, context was stored process-wide instead of per request, so useContext()/useRequestContext() read after an await in an async component could return another concurrent request's value — leading to cross-request data disclosure or authorization checks against the wrong request. GHSA-hvrm-45r6-mjfj

Server-Side XSS via JSX escaping bypass in cx()

Affects: hono/css. cx() marked its composed class name as already-escaped without escaping the input, so untrusted input passed as a class name could break out of the JSX class attribute during SSR and inject markup (XSS). GHSA-w62v-xxxg-mg59

API Gateway v1 adapter can drop a repeated request header value

Affects: hono/aws-lambda. The API Gateway v1 (and VPC Lattice) adapter de-duplicated repeated header values by substring instead of exact match, dropping a value that is a substring of another (e.g. 203.0.113.1 dropped when 203.0.113.10 is present) — affecting logic such as X-Forwarded-For-based IP restriction. GHSA-xgm2-5f3f-mvvc


Users of hono/jsx/hono/jsx-renderer, hono/css (cx()), or the hono/aws-lambda API Gateway v1 / VPC Lattice adapters are encouraged to upgrade.

v4.12.26

What's Changed

Full Changelog: honojs/hono@v4.12.25...v4.12.26

v4.12.25

Security fixes

This release includes fixes for the following security issues:

CORS Middleware reflects any Origin with credentials when origin defaults to the wildcard

Affects: hono/cors. Fixes the wildcard origin reflecting the request Origin and sending Access-Control-Allow-Credentials: true when credentials: true is set without an explicit origin, where any site a logged-in user visited could make credentialed cross-origin requests and read responses from cookie-authenticated endpoints. GHSA-88fw-hqm2-52qc

Body Limit Middleware can be bypassed on AWS Lambda by understating Content-Length

Affects: hono/body-limit on AWS Lambda (hono/aws-lambda, hono/lambda-edge). Fixes the request being built with the client-declared Content-Length while the body is delivered fully buffered, where a client could declare a small Content-Length with a much larger body and slip past the configured size limit. GHSA-rv63-4mwf-qqc2

Path traversal in serve-static on Windows via encoded backslash (%5C)

Affects: serveStatic on Windows (Node, Bun, Deno adapters). Fixes the path guard allowing a lone backslash, where an encoded backslash (%5C) decoded to \ was treated as a separator by the Windows path resolver, letting a single URL segment escape into a middleware-guarded subtree. GHSA-wwfh-h76j-fc44

AWS Lambda adapter merges multiple Set-Cookie headers into one value, dropping cookies on ALB single-header and Lattice

... (truncated)

Commits
  • 97c6fe1 4.12.27
  • aa92177 Merge commit from fork
  • cd3f6f7 Merge commit from fork
  • d4853a8 fix(jsx): make merged context-isolation tests pass tsc type check (#5037)
  • 6735fea fix(jsx): cast awaitedFallback through unknown to fix Deno type check (#5036)
  • fab3b13 Merge commit from fork
  • 9f0dadf ci: use npm Staged publishing (#5035)
  • 27b7992 4.12.26
  • d29982c chore: replace arg and glob with Bun native APIs in build script
  • 16215d5 chore: remove unused devcontainer and gitpod configs (#5029)
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by GitHub Actions, a new releaser for hono since your current version.


@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Jun 4, 2026
@dependabot dependabot Bot requested a review from lane711 as a code owner June 4, 2026 20:34
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Jun 4, 2026
@dependabot @github

dependabot Bot commented on behalf of github Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Dependabot can't resolve your JavaScript dependency files. Because of this, Dependabot cannot update this pull request.

@dependabot @github

dependabot Bot commented on behalf of github Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Dependabot can't resolve your JavaScript dependency files. Because of this, Dependabot cannot update this pull request.

pull Bot pushed a commit to sternelee/sonicjs that referenced this pull request Jun 18, 2026
- hono: ^4.12.18 → ^4.12.26 (security fixes in 4.12.25: CORS, body-limit,
  serve-static path traversal, AWS Lambda Set-Cookie, Lambda@Edge header)
- vitest: ^4.0.5 → ^4.1.9 (root, packages/core)
- @vitest/coverage-v8: ^4.0.5 → ^4.1.9 (root, packages/core)
- my-sonicjs-app vitest: ^2.1.8 → ^4.1.9 (align with root)
- postcss: 8.5.6 → 8.5.15 (transitive, lockfile)
- qs: 6.15.0 → 6.15.2 (transitive, lockfile)
- shell-quote: 1.8.3 → 1.8.4 (transitive, lockfile)

Closes PRs: SonicJs-Org#918, SonicJs-Org#862, SonicJs-Org#851, SonicJs-Org#849, SonicJs-Org#843, SonicJs-Org#840, SonicJs-Org#839, SonicJs-Org#837

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dependabot dependabot Bot changed the title chore(deps-dev): bump hono from 4.12.18 to 4.12.23 in /packages/stats chore(deps-dev): bump hono from 4.12.18 to 4.12.26 in /packages/stats Jun 18, 2026
@dependabot dependabot Bot force-pushed the dependabot/npm_and_yarn/packages/stats/hono-4.12.23 branch 3 times, most recently from c2c8b6b to 5117887 Compare June 22, 2026 16:11
@dependabot dependabot Bot changed the title chore(deps-dev): bump hono from 4.12.18 to 4.12.26 in /packages/stats chore(deps-dev): bump hono from 4.12.18 to 4.12.27 in /packages/stats Jun 23, 2026
@dependabot dependabot Bot force-pushed the dependabot/npm_and_yarn/packages/stats/hono-4.12.23 branch 3 times, most recently from c65aab1 to ef65ab9 Compare June 30, 2026 02:38
@dependabot dependabot Bot force-pushed the dependabot/npm_and_yarn/packages/stats/hono-4.12.23 branch from ef65ab9 to b5c8c53 Compare July 1, 2026 19:16
Bumps [hono](https://github.com/honojs/hono) from 4.12.18 to 4.12.27.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](honojs/hono@v4.12.18...v4.12.27)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.23
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot force-pushed the dependabot/npm_and_yarn/packages/stats/hono-4.12.23 branch from b5c8c53 to ad4b7b2 Compare July 2, 2026 00:15
lane711 added a commit that referenced this pull request Jul 2, 2026
* chore(deps): consolidate all dependabot dependency updates

- hono: ^4.12.18 → ^4.12.26 (security fixes in 4.12.25: CORS, body-limit,
  serve-static path traversal, AWS Lambda Set-Cookie, Lambda@Edge header)
- vitest: ^4.0.5 → ^4.1.9 (root, packages/core)
- @vitest/coverage-v8: ^4.0.5 → ^4.1.9 (root, packages/core)
- my-sonicjs-app vitest: ^2.1.8 → ^4.1.9 (align with root)
- postcss: 8.5.6 → 8.5.15 (transitive, lockfile)
- qs: 6.15.0 → 6.15.2 (transitive, lockfile)
- shell-quote: 1.8.3 → 1.8.4 (transitive, lockfile)

Closes PRs: #918, #862, #851, #849, #843, #840, #839, #837

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: replace page_blocks with e2e_test collection

page_blocks was registered in the sample app but has no place in a base
install. Replaced with e2e_test collection that covers all field types
(string, slug, textarea, number, boolean, date, datetime, user, media,
select, radio, lexical, object/flat, array/blocks) so E2E specs have a
stable test fixture without polluting the starter experience.

Updated 10 E2E specs to reference e2e_test / E2E Test instead of
page_blocks / Page Blocks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(create-app): bump compatibility_date to 2024-09-23 to resolve crypto import error

* docs(marketing): add v3 beta Discord announcement and blog post

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(marketing): fix npm create command to sonicjs (not @beta tag)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(create-app): include blog collection by default, fix seed timestamp, simplify post-install output (#925)

- Remove prompt for example blog collection; always include it
- Fix admin seed crash: Date.now() → new Date() for Drizzle timestamp_ms columns
- Move Cloudflare resource creation to optional "Deploy to Cloudflare" section;
  only show migration/seed steps when they actually failed

* chore: release v3.0.0-beta.3

* docs(www): add v3.0.0-beta.3 to changelog and homepage

* fix(create-app): fix admin seeding and generate BETTER_AUTH_SECRET for local dev

- Rewrite generated seed-admin.ts to use raw SQL (bypass Drizzle schema
  mismatch with auth_user columns missing from migration)
- Create auth_account record so Better Auth credential login works
- Use PBKDF2 via Web Crypto for password hashing (matches auth system)
- Auto-generate .dev.vars with BETTER_AUTH_SECRET on project creation
  (missing secret caused 500 on every auth request)
- Add --admin-email / --admin-password flags for non-interactive use
- Replace wrangler resource commands in success output with npm run deploy

* chore: release v3.0.0-beta.4

* docs(www): add v3.0.0-beta.4 to changelog and homepage

* chore: untrack settings.local.json and add to gitignore

Per-developer Claude Code settings should not be shared. settings.local.json
contained machine-specific rules and shared auto-approve allowlists.
Untrack it so each dev keeps their own local copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugins): add versioning plugin to manifest registry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.5

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.5 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): fix v3 changelog placement and stale doc errors (#926)

- Move v3.0.0-beta.3 entry from under "Version 2.x" into "Version 3.x"
- Add "Version 3.x" section to changelog sidebar nav
- Fix JWT default lifetime: 24h → 30d (matches DEFAULT_JWT_EXPIRES_IN_SECONDS)
- Fix stale comment labeling v3.0.0-beta.3 entry as v2.19.0 on homepage

* fix(create-app): update blog_post collection and disable cache plugin by default (#927)

- Switch blog_post content field from quill to lexical
- Update blog_post schema: name, slug, author type (user), remove stale fields
- Set core-cache defaultActive to false (only lexical should be on by default)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(create-app): fall back to npx wrangler when not on PATH

Resource creation runs before npm install, so wrangler isn't in
node_modules/.bin/ yet. Users running via npx also won't have a
global wrangler binary. Fall back to `npx wrangler` in both cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins): disable cache plugin by default on new installs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.7 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(stats): auto-deploy stats.sonicjs.com on main merges (#932)

- Add deploy-stats.yml CI workflow (triggers on packages/stats/** or
  packages/core/src/** changes to main + workflow_dispatch)
- Build core, type-check, then wrangler deploy --env production
- Add deploy:production script to packages/stats
- Add deploy:stats convenience script to root package.json
- Update wrangler compatibility_date to 2025-05-05

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(www): resolve build errors blocking deploy (#933)

- rehype.mjs: guard parentNode.properties before set — mdxJsxFlowElement
  nodes (inline code in JSX <li>/<span>) have no properties object,
  causing "Cannot set properties of undefined (setting 'language')"
- blog/[slug]/page.tsx: pass Callout to compileMDX components map —
  blog posts using <Callout> crashed with "Expected component Callout
  to be defined"

* chore(www): upgrade next + opennextjs-cloudflare for Next 16 compat (#934)

* fix(www): resolve build errors blocking deploy

- rehype.mjs: guard parentNode.properties before set — mdxJsxFlowElement
  nodes (inline code in JSX <li>/<span>) have no properties object,
  causing "Cannot set properties of undefined (setting 'language')"
- blog/[slug]/page.tsx: pass Callout to compileMDX components map —
  blog posts using <Callout> crashed with "Expected component Callout
  to be defined"

* chore(www): upgrade next and opennextjs-cloudflare for Next 16 compat

next: 16.2.1 → 16.2.9
@opennextjs/cloudflare: 1.18.0 → 1.19.11

1.18.0 couldn't bundle next-server.js with Next 16 (missing shims for
node-environment, node-polyfill-crypto, etc). 1.19.11 adds Next 16 support
and requires next >= 16.2.3.

* docs(www): update email plugin docs to cover Resend and Cloudflare Email (#936)

Previously documented Resend only. Plugin now supports both Resend (via
RESEND_API_KEY env var or DB settings) and Cloudflare Email (via send_email
Workers binding), with a console fallback for dev.

- Add providers table with enable instructions for each option
- Document provider resolution order (env var → DB → CF binding → console)
- Split configuration into per-provider code groups
- Add setup steps for both Option A (Resend) and Option B (Cloudflare Email)
- Fix admin settings path: /admin/email/settings (was /admin/plugins/email/settings)
- Replace stale context.plugins.get('email') API with getEmailService().send()
- Update Magic Link note to not require Resend specifically

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add Why Better Auth rationale to core auth plugin docs (#935)

Explains edge compatibility, TypeScript-first design, D1/SQLite
support, pluggable auth strategies, and security posture.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): rewrite analytics plugin page to reflect actual implementation (#938)

Removes fictional endpoints (/api/analytics/*), nonexistent config
options, fake DB tables, and unimplemented features (auto page views,
sessions, bounce rates, realtime, reports UI). Documents the real API
(/api/events), correct response shapes, admin dashboard data source
(system_logs), hook limitations, and document-model storage.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): remove seed data plugin from core plugins page (#937)

Plugin was removed; clean up all references from sections nav, feature grid, and plugin section.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): update magic link auth to reflect Better Auth integration (#939)

Replace docs that described the old custom magic-link-auth plugin with
accurate setup and API docs for the Better Auth native magicLink plugin.

- Correct endpoint: POST /auth/sign-in/magic-link (was /auth/magic-link/request)
- Correct response: { status: true } (was { message: "..." })
- Remove plugin activation steps — always active when email is configured
- Remove magic_links table schema — Better Auth uses auth_verification
- Add BETTER_AUTH_SECRET prerequisite and Resend email plugin setup steps
- Add Better Auth client (magicLinkClient) usage examples
- Document extendBetterAuth for customizing expiry
- Update authentication/page.mdx magic link section to match

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(menu-plugin): data-driven admin sidebar navigation

Replaces hardcoded sidebar nav with document-model-backed menu items.
Plugins and admins can contribute, reorder, and toggle nav entries without
code changes; four system items (Content, Collections, Users, Settings) are
seeded idempotently on first boot.

Key pieces:
- `core-menu` plugin: registers `menu_item` document type with q_* generated
  columns, seeds system items via `upsertSystemItem`, reconciles plugin-declared
  menu entries via `reconcileMenuFromPlugins`
- `menu-repository.ts`: CRUD layer (raw D1 SQL, tenant-scoped, R1/R3 compliant)
- `menu.ts` middleware: post-response HTML block replacement swaps
  `<!-- ADMIN_SIDEBAR_NAV_ITEMS -->` marker with data-driven items; falls back
  to hardcoded items if DB unavailable
- Admin routes at `/admin/menu`: list, create/edit/delete user items,
  visibility toggle, move-up/move-down reorder, locked-field enforcement
- `menu-icons.ts`: consolidated 30-icon map + `resolveIcon` helper
- 7 real-SQLite unit tests (menu-repository.sqlite.test.ts)
- E2E spec 82-menu-management.spec.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(menu-plugin): make opt-in, remove from core plugin list

menuPlugin is no longer auto-registered. Developers enable it by adding
it to config.plugins.register in their app entry point. menuMiddleware is
now mounted inside the plugin's register() so it only runs when the plugin
is active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(plugins): regenerate manifest registry to include menu-plugin

Ran generate-plugin-registry.mjs to pick up the new menu-plugin manifest.json.
core-menu now appears in the /admin/plugins list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): rename id to 'menu', restore to core, redirect plugin page

- Rename plugin id core-menu → menu (no 'core' prefix)
- Re-add to corePluginsBeforeCatchAll so /admin/menu routes always mount
- Regenerate manifest-registry.ts to pick up renamed id
- /admin/plugins/menu redirects to /admin/menu (the actual editor)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): remove duplicate Menu sidebar entry

adminMenu: null in manifest stops pluginMenuMiddleware from injecting a
second 'Menu' link via <!-- DYNAMIC_PLUGIN_MENU -->. Added 'Menu' as a
5th system seed item (sortOrder 50) so it still appears in the
data-driven sidebar managed by menuMiddleware.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): add Menu to hardcoded sidebar fallback, hide orphaned plugin DB records

- Add 'Menu' item to baseMenuItems in admin-layout-catalyst so the link
  always shows even before menu_item docs are seeded (hardcoded fallback)
- Filter installed plugins whose IDs are absent from PLUGIN_REGISTRY
  (e.g. stale 'core-menu' DB records after rename to 'menu') so they
  no longer cause duplicates on the plugins list page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(menu-plugin): revert to hardcoded sidebar when plugin is disabled

menuMiddleware checks the plugins table for the 'menu' plugin status
before replacing the sidebar. If status is 'inactive' (disabled via
admin UI), the hardcoded sidebar fallback is used instead. Treats
missing DB record as active so initial seeding still works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): query documents table for plugin status, not legacy plugins table

PluginService stores plugins in documents (type_id='plugin', slug=id),
not the legacy Drizzle-schema plugins table. The previous query always
returned null → fell back to true → sidebar never reverted when disabled.
Now queries json_extract(data, '$.status') from documents correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): remove Menu from hardcoded sidebar when plugin inactive

Menu item was in baseMenuItems unconditionally. Now only appears via
the data-driven sidebar (menuMiddleware) when plugin is active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): audit and update plugin system documentation (#941)

- Remove Seed Data plugin from overview (already gone from core page)
- Fix Hot-Swappable claim: routes mount at startup, only status toggles at runtime
- Fix Workflow plugin status: inactive/coming soon (routes currently disabled)
- Add Two-Phase Boot section explaining sync register vs async onBoot split
- Add PluginRegisterMustBeSyncError callout (common authoring mistake)
- Add onBoot as lifecycle item #1; remove incorrect ctx.hooks.register from activate
- Fix hook catalog: before/after event names, typed .on() API vs deprecated .register()
- Add declarative hooks pattern (hooks map on definePlugin)
- Fix capabilities: string array + property access (ctx.cap.email), not object array + .get()
- Add full capabilities table with required strings and ctx.cap accessors
- Add Cron Support section (crons, onCronTick, wrangler.toml)
- Add Schema-Driven Settings section (configSchema)
- Fix Plugin Context interface to match DefinedPluginContext shape
- Fix DB/KV examples: use onBoot not activate, correct ctx.env access
- Fix auth core plugin: Better Auth session language, remove JWT service API
- Remove stale /admin/auth/tokens admin page reference

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(stats): stats.sonicjs.com — document model migration + dashboard plugin (#942)

* chore(stats): reset DB to document model + add migrations + migration script

- Create packages/stats/migrations/ with 0001_core + 0002_documents (byte-identical to core)
- Fix wrangler.toml: add [[env.production.d1_databases]] binding (was missing, broke --env production commands)
- Deploy CI: run d1 migrations apply before wrangler deploy
- Add db:migrate:production + db:reset:local npm scripts
- Remove unused drizzle-kit/drizzle-orm devDeps (stats uses document model via core)
- Add scripts/migrate-legacy-data.mjs: ETL tool to seed backup JSON into documents table (used to migrate 7013 events + 4033 installs from v2 content table)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(stats): add stats dashboard plugin with weekly install funnel

- Add packages/stats/src/plugins/stats-dashboard/ plugin
- Dashboard at /admin/dashboard shows weekly started vs completed vs failed installs
- Summary cards: total events, unique installations, overall completion rate
- Weekly table with completion % badge (color-coded) + volume bar
- Plugin registers menu item "Dashboard" at order 0 (top of nav)
- Export renderAdminLayoutCatalyst + AdminLayoutCatalystData from @sonicjs-cms/core index
- Wire statsDashboardPlugin into packages/stats/src/index.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(stats): add setup:db script with admin seed for local dev

- Copy seed-local.mjs from my-sonicjs-app (same auth_user schema)
- db:reset:local: wipe .wrangler D1 state, apply migrations, seed admin
- setup:db alias for db:reset:local (matches my-sonicjs-app convention)
- Admin: admin@sonicjs.com / sonicjs!

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(stats): auto-generate BETTER_AUTH_SECRET in setup:db

- Add scripts/setup-local.sh: generates .dev.vars with BETTER_AUTH_SECRET if missing, then resets D1 + seeds admin
- Without BETTER_AUTH_SECRET, login returns 500 (Better Auth can't init)
- Fixes login with admin@sonicjs.com / sonicjs! after npm run setup:db

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(stats): wire stats-dashboard plugin + RBAC seed for local dev

- Remove disableAll flag so plugin boots, mounts routes, and injects sidebar menu
- Self-inject Dashboard menu item via /admin/* middleware (plugin not in compiled manifest)
- Seed RBAC document_types + role-admin + user-roles link so requireRbac passes on first login
- Bump core to beta.7, stats dep to "*" for workspace resolution
- Regen manifest-registry with versioning + stats-dashboard entries
- Regen migrations-bundle timestamp

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugins): add settingsTabContent extension point to plugin SDK

Plugins can now self-register a custom settings tab via definePlugin's
settingsTabContent field: loadData(db) fetches server-side data, render()
produces the HTML embedded in the Settings tab of /admin/plugins/:id.

Menu plugin uses this to embed the menu editor inline, replacing the
redirect to /admin/menu.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): hide plugin menu items when plugin is deactivated

reconcileMenuFromPlugins now queries inactive plugin IDs from the
documents table and excludes them from the active set, so deactivateStalePluginRows
hides their menu items. reconcile also runs immediately on activate/deactivate
so the change takes effect without waiting for next boot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(stats): full usage + churn analytics dashboard

* feat(stats): full usage + churn analytics dashboard

Expand the stats dashboard from a single funnel table into a complete
analytics view backed entirely by the document model (events + installs):

KPIs: total installs, active (7d/30d), churned + churn rate, completion
rate, install failures, runtime errors.

Charts (Chart.js, already bundled in catalyst layout):
- Installation growth — new installs/week + cumulative (dual-axis line)
- Install funnel — started/completed/failed grouped bars, last 16 weeks
- Retention — active(≤7d)/active(8-30d)/churned doughnut
- Engagement lifespan — first→last activity buckets
- OS / Template doughnuts, Node version horizontal bar

Error analysis:
- Install failures grouped by errorType + step
- Runtime errors (error_occurred) grouped by errorType + version

Churn/retention derived in SQL via julianday() on installs first_seen/
last_seen. All queries tenant-scoped + is_published=1 (R3). User-facing
strings escaped; chart JSON neutralized against </script> breakout (R8).

Events collection gains structured error fields (error_code, error_message,
step, sonicjs_version) so future installer failures report actionable detail
instead of only a free-text errorType.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(stats): show month-day labels instead of ISO week numbers on charts

Replace strftime('%Y-%W') with Monday-keyed date expressions in SQL;
add fmtWeekDate() helper to convert YYYY-MM-DD to "Mon D" for chart
labels and the weekly breakdown table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(telemetry): project_snapshot event — collections, plugins, field types

Adds a new project_snapshot event emitted on each SonicJS instance cold
start after bootstrap. Captures: collection names + doc counts, active
plugin names, field type histogram, total doc count.

- core/types/telemetry: add project_snapshot event type
- core/services/telemetry-service: add trackProjectSnapshot() method
- core/middleware/bootstrap: fire project_snapshot after successful boot
  (fire-and-forget, never blocks; stable installation_id via KV)
- stats/collections/events: add project_snapshot + error_occurred to enum
- stats/dashboard: 3 new charts (top collections, active plugins, field
  types) + detail table; shown only when snapshot data is present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: release v3.0.0-beta.8

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): fix CSRF and visible value on visibility toggle forms

- form.submit() doesn't fire submit event so CSRF auto-injection in
  catalyst layout never ran; switch to requestSubmit() which fires it
- renderMenuSettingsContent runs in v2 layout (no CSRF script) so add
  inline CSRF injection script scoped to that embedded content
- fix visible checkbox value from '1' to 'true' to match route check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.8 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(stats): expand project_snapshot charts — version dist, doc volume, always-visible section

Always show "What People Build" section (amber banner when no data yet).
Add 2 new charts: SonicJS version distribution (doughnut) + content volume
distribution by bucket (bar). Add reporting-installations KPI counter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(menu-plugin): show plugin enabled/disabled status in menu manager list

Add fetchPluginStatuses() to query plugin status from documents table.
Plugin-sourced menu items now show an 'enabled' or 'disabled' badge
alongside the source badge in both the standalone /admin/menu page
and the embedded settings tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): fix plugin status lookup in menu list

Replace IN-clause query (which required exact pluginId→slug match)
with a query that fetches all inactive plugin slugs, then checks
membership. Avoids binding issues and ID mismatch edge cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(create-sonicjs): flush pending telemetry requests before process exit

shutdown() was a no-op so in-flight fetch() calls were abandoned when
the CLI process exited. Now pending requests are tracked and awaited
(max 3s) in shutdown(), ensuring installation events reach stats.sonicjs.com.

Bumps create-sonicjs to 3.0.0-beta.9.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): use same plugin status query as plugins list page

Was filtering WHERE status='inactive' which missed plugins whose status
field is NULL. Now fetches all plugin docs and reads status directly,
matching PluginService.getAllPlugins() logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.9

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update README files to reflect v3 current state (#945)

- Fix version references: 2.0.0-alpha.1 → 3.0.0-beta.9
- Fix auth description: JWT → Better Auth (sessions + RBAC)
- Replace stale Quick Start config (deprecated autoSync/autoLoad) with registerCollections() + plugins.register pattern
- Remove syncCollections export (removed) and collections DB table export (never exists on greenfield)
- Fix plugin example: onActivate → activate(context: PluginContext)
- Root README: replace raw SQL INSERT INTO collections block with TypeScript CollectionConfig pattern
- Root README: fix plugin hooks example to use correct Plugin interface
- Root README: remove drizzle/ from project structure (directory does not exist)
- Root README: fix GitHub links (sonicjs-ai → sonicjs)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(admin): show branch label in sidebar on localhost (#946)

Adds a colored pill badge below the logo in the admin sidebar that
displays the short branch name (after last slash) automatically when
running on localhost. Off in production — no env var needed by default.

- Template: module-level setBranchLabel() + djb2-hashed color palette
- Bootstrap: auto-detects localhost via Host header + reads GIT_BRANCH
- Dev script: injects --var GIT_BRANCH:<current-branch> into wrangler dev

To enable: run `npm run dev` (branch auto-detected); no .dev.vars change needed.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(stats): add POST /v1/events telemetry ingestion endpoint

CLI posts to /v1/events but route didn't exist (404). Added unauthenticated
handler that writes directly to D1 bypassing ACL. Also fixes wrangler.toml:
add [[env.production.d1_databases]] (not inherited from top-level) and
update core dep from ^2.3.4 to ^3.0.0-beta.9.

Also fixes pre-existing ESLint naming-convention error in otp-login-plugin
(is_active destructure rename → _isActive).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.10

- fix(stats): add POST /v1/events telemetry endpoint (was 404)
- fix(media): export MEDIA_QUERYABLE for test visibility
- feat(admin): branch label in sidebar on localhost
- docs(www): changelog + homepage updated for beta.10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(admin): sidebar plugin links now appear on all admin pages

pluginMenuMiddleware was using activeMenuItems (from manifest registry)
for HTML injection, ignoring items dynamically prepended to pluginMenuItems
by plugins like stats-dashboard that self-inject via register(app). Core
admin pages don't pass dynamicMenuItems to templates, so they emit the
DYNAMIC_PLUGIN_MENU marker — but since activeMenuItems was empty for
external plugins, injection was skipped and the marker stayed as a comment.

Fix: read c.get('pluginMenuItems') post-next for HTML injection instead
of activeMenuItems, so any pre-next middleware additions are included.

chore: release v3.0.0-beta.11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(cors): apply CORS middleware globally so /auth/* routes work from Astro/frontends

Previously CORS was scoped to /v1/* only — cross-origin POST /auth/login
always hit a CORS wall because the auth routes had no CORS headers at all.

- Move cors() middleware to global scope in app.ts (covers /auth/*, /v1/*, admin)
- Add credentials: true so session cookie flows back to the browser
- Remove now-redundant cors() from api.ts
- Default CORS_ORIGINS in wrangler.toml templates now includes localhost:4321 (Astro default)
- Fix forms plugin crash (no such table: forms) — return graceful empty list / 404 instead of 500
- Add E2E spec 82 for cross-origin auth header verification
- Docs: rewrite CORS section in authentication.md, add CORS troubleshooting to getting-started.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.12

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.12 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): clean up duplicate beta.12 entries from announce script; bump manifest version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(auth): return JWT token from POST /auth/login

Login delegated to Better Auth for session cookies but never minted a
JWT — API callers got { user } with no token field. Register already
returned { user, token }; login now matches.

After successful BA sign-in, generate a JWT via AuthManager.generateToken
and return it in the response body + set auth_token cookie (same
pattern as /auth/register).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(admin): add interactive JSON viewer for json-type fields

json fields previously rendered [object Object] in the edit form.
Edit mode now shows a collapsible tree (json-formatter-js, CDN,
3.5KB gz) with a toggle to switch to a raw textarea for editing.
Invalid JSON is caught inline before the viewer refreshes.
View-only (disabled) mode shows the tree without the edit toggle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(stats): add error logging to /v1/events, health endpoint, fix type errors

- Log insert failures to CF console instead of swallowing silently
- Add /health endpoint to verify worker liveness + D1 connectivity
- Cast statsDashboardPlugin to any (DefinedPlugin/Plugin dist/src skew)
- Use (c as any) for pluginMenuItems context access (plain Hono typing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(stats): rename health endpoint to /stats-health (avoid core/v1 conflicts)

/health is taken by core's SonicJS status endpoint.
/v1/* is taken by core API routes.
/stats-health is uncontested and works on the workers.dev URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(stats): deploy to correct worker — sonicjs-stats not sonicjs-stats-production

stats.sonicjs.com is bound to the default sonicjs-stats worker.
All previous CI deploys targeted --env production (sonicjs-stats-production)
which is a separate worker not wired to the custom domain.

- Remove --env production from deploy + migrations commands in CI
- Add comment to wrangler.toml documenting the correct deploy target

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(release): never add beta dist-tag by default; keep beta=latest

Add critical rules to release engineer agent: never publish with --tag
beta unless explicitly asked, and always sync beta dist-tag to match
latest after every publish.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(stats): sonicjs_version from compiled constant; fix json_each on serialized arrays; add country breakdown

- bootstrap.ts: replace c.env.SONICJS_VERSION (missing env var → 'unknown')
  with imported SONICJS_VERSION constant from utils/version (inlined at build time)
- dashboard SQL: wrap json_extract with json() for active_plugins, collection_counts,
  field_type_histogram — trackProjectSnapshot serializes these as JSON strings;
  json_each needs json() to parse the string into an array/object first
- /v1/events: capture CF-IPCountry header (2-letter code, no PII) into event data
- dashboard: add Country chart (horizontal bar, unique installs by country)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(auth): support configurable post-login redirect via ?redirect= param

GET /auth/login and POST /auth/login/form now accept a ?redirect=<path>
query param. After successful login the browser navigates to that path
instead of the hardcoded /admin/content fallback. External URLs are
silently ignored (open-redirect guard: value must start with /).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: sync package-lock and generated files after build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(auth): deny-by-default public access, add CollectionConfig.access, rewrite auth docs

- Collections no longer grant public read by default — matches Strapi/Payload
  secure-by-default posture. Opt in via `access: { public: ['read'] }`.
- Add `access` property to CollectionConfig (Payload-style per-collection ACL).
- Rename "Access portal" → "Admin Panel" in RBAC UI.
- Seed Authenticated + Public RBAC roles (Strapi parity).
- Rewrite docs/authentication.md for Better Auth + document ACL.
- Update docs/api-reference.md: cookie-based auth, remove legacy JWT examples.
- Update docs/collections-config.md: document access property.
- Rewrite docs/content-management.md for document model.
- Add 2 unit tests for deny-by-default + access merge.
- Add E2E spec for Admin Panel naming + seeded roles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: renumber E2E spec to 85 (avoid collision with main's 83-* specs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): fix plugin status badges, edit 404, and sidebar coverage

- fetchPluginStatuses: default missing plugins to inactive (not active)
- admin-menu-list template: remove /edit suffix from edit link (404 fix)
- app.ts: register menuMiddleware before admin routes (Hono ordering fix)
- menu-plugin/index.ts: remove duplicate menuMiddleware registration
- menu.ts: replace all marker pairs via regex (fixes mobile sidebar)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(menu-plugin): add Plugins as default system menu item

Adds /admin/plugins link (sortOrder 50) before Menu (now 60) so the
Plugins page is always present in the DB-driven sidebar by default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): add POST /:id/delete route for delete form

HTML forms can't send DELETE method; the template POSTs to /:id/delete
but only DELETE /:id existed. Adds the matching POST route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): use collection icon for Plugins menu item

Matches the icon used in the hardcoded sidebar fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(menu-plugin): clicking menu row navigates to edit page

Adds onclick to <tr> that navigates to /admin/menu/:id, skipping
clicks on interactive elements (buttons, links, inputs, forms).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): default to off when not yet activated

Fresh install has no plugin document in DB. Previously treated as active
(showing DB-driven sidebar on first boot). Now defaults to inactive so
the hardcoded sidebar shows until user explicitly activates the plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): hide plugin menu items when plugin is deactivated

DB-driven sidebar now filters plugin-sourced items by active status,
matching the hardcoded sidebar's behavior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): seed plugin items visible=false when plugin inactive

- Revert middleware plugin-status filter; visible flag drives sidebar
- Replace fetchInactivePluginIds with fetchActivePluginIds; only
  explicitly active plugins get visible=true on first insert
- deactivateStalePluginRows now uses activeIds (not byPluginId keys)
  so inactive plugins are hidden even if still in the registry
- Also sync visible column alongside data->$.visible on deactivate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(auth): API keys plugin + close auth test gaps vs Payload

Audited Payload's auth/login/rbac test suite against ours and closed the
gaps — both missing tests on existing behavior and one genuine feature hole.

API keys (new api-keys-plugin)
- better-auth 1.6.x ships no apiKey plugin, so add programmatic per-user
  keys on the document model (no new table). Plaintext sk_ secret shown
  once; only its SHA-256 hash stored (q_apikey_hash). Tenant-scoped,
  expiry + revoke (hard-erase), ownership-guarded.
- Resolve x-api-key / Authorization: Bearer sk_ in the app-wide auth chain
  (core-wired in app.ts after the BA session middleware), so existing
  requireAuth/requireRole guards work unchanged for headless clients.
- Plugin owns the management surface: admin manage UI + JSON API at
  /admin/plugins/api-keys, settings (maxKeysPerUser, defaultExpiryDays),
  sidebar menu. Mirrors the security-audit-plugin split.

Test-gap closures
- brute-force-detector: lockout detector was live in the login path with
  zero coverage — add 14 unit tests (per-IP/email lock, window expiry,
  TTL unlock, suspicious multi-email, alert threshold).
- email validation: Zod .email() accepted trailing-hyphen domains
  (user@example-.com); tighten with a refine + edge-case tests.
- session lifecycle e2e: logout destroys the session; logging out one
  session leaves a concurrent session alive (Payload parity).

Verification: type-check clean; 62 unit/integration tests; 10 e2e
(86-api-key-auth, 87-session-lifecycle) green against a real wrangler server.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(menu-plugin): alphabetize parent dropdown in menu form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): fix Save Changes on menu edit form

HTMX hx-put was silently failing (redirect + hx-target="body" race).
Switch edit form to plain POST /admin/menu/:id/update. Extract shared
handler so PUT /:id (API) and POST /:id/update (HTML form) share logic.
Also fix visible checkbox: unchecked = absent from FormData = false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): allow reparenting system/plugin menu items

parent is an organizational choice, not content — admins should always
be able to move items under a different parent. Remove parent from
lockedFields in defaults, reconcile, and the updateItem guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(menu-plugin): skip locked fields in update handler

Form always submits all fields; locked ones (url for system/plugin items)
were hitting the updateItem guard. Now skip locked fields when building
changes — parent stays always-editable as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugins): export helloWorldPlugin from core + add hello-world tutorial

- Export `helloWorldPlugin` and `createHelloWorldPlugin` from `@sonicjs-cms/core`
  so users can `import { helloWorldPlugin } from '@sonicjs-cms/core'` instead of
  hitting a broken relative path
- Fix internal import in v4-author-guide §9 test example (was pointing to
  package-internal path, now uses public API)
- Add "Using built-in plugins" section to v4-author-guide with correct import
  pattern and explicit warning against relative paths
- Add `docs/plugins/hello-world-from-scratch.md`: step-by-step tutorial building
  a plugin with configSchema `recipientName` setting that greets "Hello, Joe!"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(www): upgrade @opennextjs/cloudflare to 1.20.0 for Next.js 16 support

1.19.11 failed to bundle next-server.js under Next.js 16.2.9. 1.20.0
explicitly adds >=16.2.6 to its peer dep range and resolves the
esbuild bundling errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.15

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.15 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): apply release:announce auto-updates for v3.0.0-beta.15

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): fix plugin docs to match actual v3 API

Fix multiple inaccuracies in the plugin guide and architecture deep-dive
discovered by verifying code against the live codebase:

- configSchema is a plain ConfigSchema record (type/label/default), not Zod
- Hook names corrected to typed catalog: content:after:create/update/delete/publish,
  auth:registration:completed; legacy bus hooks (auth:login, auth:logout,
  request:start) accessed via (ctx.raw as any)?.hooks.register()
- Remove ctx.logger (doesn't exist), ctx.documentTypeRegistry, ctx.documentService,
  ctx.getConfig() — none are on DefinedPluginContext
- D1 access in onBoot is ctx.env?.DB, not ctx.db
- c.get('documentRepository') and c.get('pluginConfig') never set on context;
  replaced with actual D1 patterns
- Fix registerPlugins arg order: (app, plugins, hostContext) not (plugins, app, ctx)
- Add deprecated alias table so readers know content:save/content:publish still
  work but emit warnings

Add tests/e2e/88-plugin-system.spec.ts (8 tests) verifying plugin system
endpoints, auth enforcement, hook paths, and configSchema form — all green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(admin): persist plugin detail tab selection via URL hash

Tab clicks update window.location.hash; page load and hashchange restore
the active tab so refreshing lands on the same tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugins): hello-cruel-world demo plugin + nav active-only fix

- Add hello-cruel-world demo plugin with extensive comments explaining
  every v3 extension point: routes, menu, hooks, configSchema, lifecycle,
  onBoot self-registration via PluginServiceClass.ensurePlugin()
- Plugin self-registers in DB on boot so it appears in /admin/plugins
- Routes at /hello-cruel-world/* (not /api/*) — explains catch-all ordering
- Info tab shows description + auto-rendered clickable route list from
  plugin.settings._routes (merged into DB settings on each boot)
- Fix admin-plugin-settings.template.ts settings form to skip _-prefixed
  keys so _routes metadata doesn't render as an editable input field
- Add npm run killall to root package.json (pkill workerd/wrangler)
- Fix pluginMenuMiddleware to only show active plugins in sidebar nav:
  - Singleton (setPluginMenu) now populated with user plugins only,
    excluding any plugin that also ships a manifest.json (e.g. redirects)
  - Core/registry plugins continue to use manifest + DB active-status gate
  - User plugins without a manifest always appear (always mounted)
- Add globe-alt icon to plugin-menu-singleton MENU_ICON_MAP
- 6 E2E tests in tests/e2e/89-hello-cruel-world-plugin.spec.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugins): enhance hello-cruel-world plugin UX and settings tab visibility

- Add defaultName configSchema setting; base route greets it when no :name in URL
- Seed all configSchema defaults in onBoot so Settings tab appears on fresh install
- Use shared mutable pluginOptions so saved settings reach route handlers at request time
- Reorder plugin settings tabs: Information → Settings → Activity Log; default to Info
- Hide Settings tab when plugin has only _-prefixed metadata keys (no user-configurable settings)
- Fix tab JS: VALID_TABS conditional on hasUserSettings; null-guard missing tab elements
- Fix View Page E2E selector to use getByRole instead of href (avoids sidebar collision)
- Admin page shows Current Config panel with greeting + defaultName + live preview link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugins): rename hello-cruel-world → example plugin + collection

- Plugin id/name/routes: hello-cruel-world → example
- Collection type id: hello-cruel-world-moods → example
- Manage Moods links to /admin/content?model=example&page=1
- Info tab route paths now clickable for all GET routes (auth or not)
- No local E2E runs — CI validates Playwright specs
- CLAUDE.md updated: skip local E2E, unit+integration only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins): preserve _routes/_adminPath when saving configSchema settings

configSchema form POST was calling updatePluginSettings(pluginId, parsed)
with only the form fields, overwriting non-schema keys like _routes and
_adminPath that plugins store for the Info tab. Now merges parsed values
into existing settings so those keys survive a Settings tab save.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: regenerate migrations bundle timestamp

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(create-app): ship example plugin with new installs

Adds the example plugin to the starter template so every new SonicJS
project bootstrapped via create-sonicjs-app includes a working demo of
the plugin system out of the box.

Ships: public API routes, admin page, moods collection, hook subscriptions,
configSchema settings form, and self-registration via onBoot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.16

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.16 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): fix announce script duplicate entries for v3.0.0-beta.16

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add example plugin docs + blog post walkthrough

- plugins/page.mdx: add Example Plugin under Core Plugins section
- plugins/development/page.mdx: callout + reference section for example plugin
- blog: building-your-first-sonicjs-plugin tutorial covering all extension points

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add example plugin dedicated page + core plugins entry

- New /plugins/example page: full reference covering file structure,
  two-phase boot, /example/* routing rationale, factory pattern,
  configSchema settings, moods collection, hooks, data seeding,
  customization, and removal
- plugins/core/page.mdx: Example Plugin section in feature grid,
  sections array, and body with key patterns table + removal snippet

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): wire cross-links between example plugin pages + blog post

- plugins/page.mdx: add buttons → /plugins/example + blog post
- plugins/development/page.mdx: add buttons → /plugins/example + blog post
- blog post: replace plain /plugins/development link with /plugins/example first

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(www): correct blog post URLs from /content/blog/tutorials/... to /blog/...

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins): ensure document_type row exists before seeding in onBoot

Root cause: plugin wirePlugins() has no bootstrapComplete guard, so a
static-asset request (favicon, etc.) can fire onBoot before
bootstrapMiddleware has run autoRegisterCollectionDocumentTypes. D1
local enforces the FK on documents.type_id — the seed INSERT fails
with SQLITE_CONSTRAINT_FOREIGNKEY, gets swallowed by the catch, and
the once-guard means it never retries.

Fix: INSERT OR IGNORE the example document_type row inside the seed
try-block before the COUNT check. Idempotent when the row already
exists; races bootstrap safely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(admin): reorder plugin tabs to Settings → Info → Activity

Settings tab now defaults as active when a plugin has user-configurable
settings. Info and Activity follow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.17

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.17 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins): prune stale configSchema keys from stored settings (#973)

Removing a field from configSchema had no effect on stored settings —
the field persisted in the DB blob and kept appearing in the admin UI.

Two fixes:
1. POST /admin/plugins/:id/configure: replace the naive spread-merge
   with schema-aware merge that only carries forward non-schema keys
   (internal `_`-prefixed keys like `_routes`) from existing settings.
   Schema-removed fields are pruned on next save.
2. GET /admin/plugins/:id (legacy settings page): filter `enrichedSettings`
   to schema-defined + `_`-prefixed keys before passing to the template,
   so stale stored keys never render in the UI.

Fixes: #972

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.18

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.18 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): release announcement updates for v3.0.0-beta.18

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins): example plugin settings not reflected in API output (#975)

onBoot populated pluginOptions once at isolate warm-up; admin UI saves
updated settings to DB but the in-memory cache was never refreshed, so
GET /example always returned the stale boot-time values.

Fix: read greeting/defaultName from DB per-request via PluginService.getPlugin
in a getPluginSettings() helper, parallel with getRandomMood. The stale
options param is removed from createExampleApiRoutes.

Also fixes greeting being declared in configSchema but never used in the
response (was hardcoded). Now uses ${settings.greeting}.

Applies to both my-sonicjs-app and packages/create-app starter template.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(plugins): bump example plugin version to 1.0.1

Reflects the settings-not-saved bug fix shipped in #975.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.19

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.19 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): release announcement updates for v3.0.0-beta.19

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(scripts): add update:plugin script to pull latest plugin files from GitHub

Scaffolded plugin files are ejected copies — npm install updates
@sonicjs-cms/core but leaves plugin source untouched. This script
fetches the latest files from the canonical starter template on
GitHub main and overwrites the local copies.

Usage:
  npm run update:plugin                 # update example plugin
  npm run update:plugin -- example      # explicit
  npm run update:plugin -- example --dry-run

Ships in both my-sonicjs-app and packages/create-app starter template
so new projects get the script at scaffold time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.20

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): add v3.0.0-beta.20 to changelog and homepage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): release announcement updates for v3.0.0-beta.20

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(www): document update:plugin script on plugins and example plugin pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(plugins): preserve _routes when saving settings via legacy tab (#977)

POST /:id/settings was overwriting the full settings blob without merging,
wiping _routes and _adminPath written by onBoot on every settings save.
Now merges with existing settings before writing, matching the /configure handler.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(database-tools): add search and JSON field browser to table viewer (#976)

- Search bar filters all columns including JSON data fields via LIKE
- Search term persists across pagination, sort, and page-size changes
- JSON/long text cells render as clickable links opening a modal viewer
- Modal shows syntax-highlighted pretty-printed JSON with copy button
- Closes on backdrop click, close button, or Escape key
- E2E spec 91 covers search input, URL params, clear, modal open/close

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(media): real total count + media selector search (#888, #890) (#978)

- MediaDocumentService.list() now runs a parallel COUNT(*) with the
  same WHERE filters and returns `total` in MediaListResult
- Admin media route uses `total` for totalFiles and fixes hasNextPage
  (was capped at page size 24 instead of real library total)
- Media selector search input: add missing `name="search"` attr so the
  typed term is actually sent with the HTMX request
- Add hx-swap="outerHTML" + hx-select="#media-selector-grid" to prevent
  the full panel (search box + grid) from nesting inside the grid on
  each keystroke

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(mcp): add MCP plugin implementation plan for issue #784 (#981)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(media): regression tests for #889 upload date display + MCP plugin spec (#982)

* docs(mcp): add MCP plugin implementation plan for issue #784

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(media): add regression tests for #889 upload date display + sort

Adds 4 unit tests and 1 E2E spec to guard against re-introduction of the
1970-date bug: createFromUpload must produce a valid non-1970 uploadedAt
via mediaDocToFile (seconds * 1000), and list() must return newest files
first (updated_at DESC).

Closes #889

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(api-keys): show new key until user copies, no auto-redirect (#984)

Remove 1.5s auto-reload that fired before user could copy the secret.
New key row now injected into the DOM immediately. Banner stays visible
until user clicks "Copy & dismiss" or the × close button.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(oauth-providers): show Settings tab and render provider fields (#987)

- hasUserSettings now returns true when a custom renderer is registered,
  so Settings tab is visible even with empty/null plugin DB settings
- Adds renderOAuthProvidersSettingsContent with GitHub + Google
  credential fields (clientId, clientSecret, enabled toggle)
- Custom saveSettings reconstructs nested providers structure before POST
- Registers oauth-providers in pluginSettingsComponents

Fixes: Settings tab hidden until user manually saves a dummy entry

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release v3.0.0-beta.22

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* docs(www): add v3.0.0-beta.22 to changelog and homepage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

* chore: sync migrations-bundle timestamp after merge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: upgrade Node.js to 22 for wrangler 4.x compatibility

* fix(menu): address code review findings

- XSS: reject javascript:/data: URL schemes in create and update handlers
- visible flag on create: form.has() instead of !== 'false' (always-true bug)
- isMenuPluginActive catch: return false on error, not true
- label double-encode: store raw, escape only at render time
- move-up/down: filter by same parent before swap (was crossing parent boundaries)
- move-up/down: only redirect with 'Reordered' toast when write actually happened
- reconcile UPDATE path: also write visible column when re-activating plugin items
- toggleVisibility/updateItem: add type_id='menu_item' guard to WHERE clause

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: sync package-lock.json with updated dependencies

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants