@sonicjs-cms/core 3.0.0-beta.24 → 3.0.0-beta.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/db-reset.js +39 -22
- package/dist/admin-documents-form.template-JWV5GITD.js +6 -0
- package/dist/{admin-documents-form.template-55V2ZGWT.js.map → admin-documents-form.template-JWV5GITD.js.map} +1 -1
- package/dist/admin-documents-form.template-UWKGVAMW.cjs +19 -0
- package/dist/{admin-layout-catalyst.template-6UQGLCKI.js.map → admin-documents-form.template-UWKGVAMW.cjs.map} +1 -1
- package/dist/admin-layout-catalyst.template-MOQ3BBXA.cjs +21 -0
- package/dist/{admin-layout-catalyst.template-3XZXDGF7.cjs.map → admin-layout-catalyst.template-MOQ3BBXA.cjs.map} +1 -1
- package/dist/admin-layout-catalyst.template-WCYMQVTZ.js +7 -0
- package/dist/{admin-documents-form.template-YNAVMJCV.cjs.map → admin-layout-catalyst.template-WCYMQVTZ.js.map} +1 -1
- package/dist/{app-01M0dIwt.d.ts → app-BP5IlK94.d.ts} +7 -7
- package/dist/{app-aj39TGHB.d.cts → app-fdq4K3vw.d.cts} +7 -7
- package/dist/catalog-56DR6OXI.cjs +41 -0
- package/dist/catalog-56DR6OXI.cjs.map +1 -0
- package/dist/catalog-6MB37KY7.js +4 -0
- package/dist/catalog-6MB37KY7.js.map +1 -0
- package/dist/{chunk-QCJHCSOK.cjs → chunk-4DHPR7KR.cjs} +77 -17
- package/dist/chunk-4DHPR7KR.cjs.map +1 -0
- package/dist/{chunk-UIHIXTIS.js → chunk-5YSFQO7X.js} +40 -13
- package/dist/chunk-5YSFQO7X.js.map +1 -0
- package/dist/{chunk-GMMCCIQO.cjs → chunk-6UAI4SGN.cjs} +3 -3
- package/dist/{chunk-GMMCCIQO.cjs.map → chunk-6UAI4SGN.cjs.map} +1 -1
- package/dist/{chunk-2XAQTPAE.js → chunk-6X22VFEV.js} +8 -2
- package/dist/chunk-6X22VFEV.js.map +1 -0
- package/dist/{chunk-URIBTX27.js → chunk-73RTFFMX.js} +3 -3
- package/dist/{chunk-URIBTX27.js.map → chunk-73RTFFMX.js.map} +1 -1
- package/dist/{chunk-QJUJNTIU.cjs → chunk-A22RBHO7.cjs} +43 -12
- package/dist/chunk-A22RBHO7.cjs.map +1 -0
- package/dist/{chunk-QZGABF2M.js → chunk-AMP4BSJE.js} +18 -7
- package/dist/chunk-AMP4BSJE.js.map +1 -0
- package/dist/{chunk-37V3RET3.cjs → chunk-BRKXMRMB.cjs} +99 -98
- package/dist/chunk-BRKXMRMB.cjs.map +1 -0
- package/dist/{chunk-36Z3AKX3.cjs → chunk-C5LDQ3SH.cjs} +8 -2
- package/dist/chunk-C5LDQ3SH.cjs.map +1 -0
- package/dist/{chunk-BBEKHLTW.cjs → chunk-CN3T3TDI.cjs} +9 -9
- package/dist/{chunk-BBEKHLTW.cjs.map → chunk-CN3T3TDI.cjs.map} +1 -1
- package/dist/{chunk-BHND5PEV.js → chunk-E3TQG7AA.js} +3 -3
- package/dist/{chunk-BHND5PEV.js.map → chunk-E3TQG7AA.js.map} +1 -1
- package/dist/{chunk-FHPEM77Y.js → chunk-EEA476CJ.js} +88 -83
- package/dist/chunk-EEA476CJ.js.map +1 -0
- package/dist/chunk-F6BTBE4S.js +119 -0
- package/dist/chunk-F6BTBE4S.js.map +1 -0
- package/dist/chunk-FZGIU5AZ.cjs +128 -0
- package/dist/chunk-FZGIU5AZ.cjs.map +1 -0
- package/dist/{chunk-BJROXF4N.js → chunk-HSDJDWLV.js} +19 -4
- package/dist/chunk-HSDJDWLV.js.map +1 -0
- package/dist/{chunk-VGEZNFWH.js → chunk-JD7BZU3A.js} +5 -4
- package/dist/chunk-JD7BZU3A.js.map +1 -0
- package/dist/{chunk-SOZUG52O.js → chunk-JG5J434C.js} +3 -3
- package/dist/{chunk-SOZUG52O.js.map → chunk-JG5J434C.js.map} +1 -1
- package/dist/{chunk-BP52BFIV.cjs → chunk-KWQNB6UM.cjs} +6 -5
- package/dist/chunk-KWQNB6UM.cjs.map +1 -0
- package/dist/{chunk-UYJQDDEE.cjs → chunk-MI2YXCK4.cjs} +14 -8
- package/dist/chunk-MI2YXCK4.cjs.map +1 -0
- package/dist/{chunk-VC5XVPLM.js → chunk-OJFIQGZB.js} +512 -148
- package/dist/chunk-OJFIQGZB.js.map +1 -0
- package/dist/{chunk-LTZ5WYOK.cjs → chunk-OLEOKHDX.cjs} +25 -2
- package/dist/chunk-OLEOKHDX.cjs.map +1 -0
- package/dist/{chunk-E66UVONV.cjs → chunk-OUBUS74I.cjs} +24 -9
- package/dist/chunk-OUBUS74I.cjs.map +1 -0
- package/dist/{chunk-A2SN3RVL.cjs → chunk-PAS7GZBS.cjs} +40 -13
- package/dist/chunk-PAS7GZBS.cjs.map +1 -0
- package/dist/{chunk-RJ2O5IIE.js → chunk-RVOX2WH2.js} +74 -18
- package/dist/chunk-RVOX2WH2.js.map +1 -0
- package/dist/{chunk-3TMTYJAJ.js → chunk-RXLI3ADU.js} +25 -2
- package/dist/chunk-RXLI3ADU.js.map +1 -0
- package/dist/{chunk-I767LVN7.cjs → chunk-S2QN4LFD.cjs} +881 -515
- package/dist/chunk-S2QN4LFD.cjs.map +1 -0
- package/dist/{chunk-IXUHXTHW.cjs → chunk-SFGYZUWU.cjs} +18 -7
- package/dist/chunk-SFGYZUWU.cjs.map +1 -0
- package/dist/{chunk-THFZSDIU.js → chunk-SXC4HPK6.js} +14 -8
- package/dist/chunk-SXC4HPK6.js.map +1 -0
- package/dist/{chunk-JMJM7DMM.cjs → chunk-TT3TQSCZ.cjs} +5 -5
- package/dist/{chunk-JMJM7DMM.cjs.map → chunk-TT3TQSCZ.cjs.map} +1 -1
- package/dist/{chunk-LNI3FYSI.js → chunk-UB5QSRO7.js} +9 -14
- package/dist/chunk-UB5QSRO7.js.map +1 -0
- package/dist/{chunk-VYQVDL2M.js → chunk-UKIDWRA3.js} +36 -5
- package/dist/chunk-UKIDWRA3.js.map +1 -0
- package/dist/{chunk-KKNEYIZJ.cjs → chunk-XZUEGFXE.cjs} +9 -14
- package/dist/chunk-XZUEGFXE.cjs.map +1 -0
- package/dist/{collection-config-BTzAvKt2.d.cts → collection-config-U2dydD_O.d.cts} +14 -0
- package/dist/{collection-config-BTzAvKt2.d.ts → collection-config-U2dydD_O.d.ts} +14 -0
- package/dist/config-DOAWXZBG.js +6 -0
- package/dist/{config-6FB2BXMP.js.map → config-DOAWXZBG.js.map} +1 -1
- package/dist/{config-OCH7S4WK.cjs → config-VLU73ZW7.cjs} +5 -5
- package/dist/{config-OCH7S4WK.cjs.map → config-VLU73ZW7.cjs.map} +1 -1
- package/dist/{define-plugin-D_6D9vVZ.d.ts → define-plugin-DDe8a_bh.d.ts} +3 -0
- package/dist/{define-plugin-DMcvEy_C.d.cts → define-plugin-rjXmGOgu.d.cts} +3 -0
- package/dist/index.cjs +698 -392
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -12
- package/dist/index.d.ts +27 -12
- package/dist/index.js +340 -35
- package/dist/index.js.map +1 -1
- package/dist/middleware.cjs +47 -47
- package/dist/middleware.d.cts +10 -10
- package/dist/middleware.d.ts +10 -10
- package/dist/middleware.js +6 -6
- package/dist/migrations-6M6772UC.cjs +13 -0
- package/dist/{migrations-FGXZPKDC.cjs.map → migrations-6M6772UC.cjs.map} +1 -1
- package/dist/migrations-GZ2HIB4I.js +4 -0
- package/dist/{migrations-Z6D27NAW.js.map → migrations-GZ2HIB4I.js.map} +1 -1
- package/dist/{plugin-bootstrap-BHWQ2df8.d.cts → plugin-bootstrap-CfVILQeP.d.ts} +1 -1
- package/dist/{plugin-bootstrap-Bj4fxX1q.d.ts → plugin-bootstrap-RY5pkoOT.d.cts} +1 -1
- package/dist/plugins.cjs +40 -40
- package/dist/plugins.d.cts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +3 -3
- package/dist/rbac-TFUOYIVN.cjs +14 -0
- package/dist/{rbac-7QZ4JZ2E.cjs.map → rbac-TFUOYIVN.cjs.map} +1 -1
- package/dist/rbac-XSNP76CV.js +5 -0
- package/dist/{rbac-Y43IEQOP.js.map → rbac-XSNP76CV.js.map} +1 -1
- package/dist/routes.cjs +33 -32
- package/dist/routes.d.cts +3 -5
- package/dist/routes.d.ts +3 -5
- package/dist/routes.js +13 -12
- package/dist/services.cjs +30 -30
- package/dist/services.d.cts +10 -3
- package/dist/services.d.ts +10 -3
- package/dist/services.js +3 -3
- package/dist/{telemetry-Cku1ax74.d.cts → telemetry-QFlTv9f8.d.cts} +1 -1
- package/dist/{telemetry-Cku1ax74.d.ts → telemetry-QFlTv9f8.d.ts} +1 -1
- package/dist/templates.cjs +17 -17
- package/dist/templates.js +3 -3
- package/dist/types.d.cts +2 -2
- package/dist/types.d.ts +2 -2
- package/dist/utils.cjs +12 -12
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -2
- package/package.json +7 -12
- package/dist/admin-documents-form.template-55V2ZGWT.js +0 -6
- package/dist/admin-documents-form.template-YNAVMJCV.cjs +0 -19
- package/dist/admin-layout-catalyst.template-3XZXDGF7.cjs +0 -21
- package/dist/admin-layout-catalyst.template-6UQGLCKI.js +0 -7
- package/dist/chunk-2XAQTPAE.js.map +0 -1
- package/dist/chunk-36Z3AKX3.cjs.map +0 -1
- package/dist/chunk-37V3RET3.cjs.map +0 -1
- package/dist/chunk-3TMTYJAJ.js.map +0 -1
- package/dist/chunk-A2SN3RVL.cjs.map +0 -1
- package/dist/chunk-BJROXF4N.js.map +0 -1
- package/dist/chunk-BP52BFIV.cjs.map +0 -1
- package/dist/chunk-E66UVONV.cjs.map +0 -1
- package/dist/chunk-FHPEM77Y.js.map +0 -1
- package/dist/chunk-I767LVN7.cjs.map +0 -1
- package/dist/chunk-IXUHXTHW.cjs.map +0 -1
- package/dist/chunk-KKNEYIZJ.cjs.map +0 -1
- package/dist/chunk-LNI3FYSI.js.map +0 -1
- package/dist/chunk-LTZ5WYOK.cjs.map +0 -1
- package/dist/chunk-QCJHCSOK.cjs.map +0 -1
- package/dist/chunk-QJUJNTIU.cjs.map +0 -1
- package/dist/chunk-QZGABF2M.js.map +0 -1
- package/dist/chunk-RJ2O5IIE.js.map +0 -1
- package/dist/chunk-THFZSDIU.js.map +0 -1
- package/dist/chunk-UIHIXTIS.js.map +0 -1
- package/dist/chunk-UYJQDDEE.cjs.map +0 -1
- package/dist/chunk-VC5XVPLM.js.map +0 -1
- package/dist/chunk-VGEZNFWH.js.map +0 -1
- package/dist/chunk-VYQVDL2M.js.map +0 -1
- package/dist/config-6FB2BXMP.js +0 -6
- package/dist/migrations-FGXZPKDC.cjs +0 -13
- package/dist/migrations-Z6D27NAW.js +0 -4
- package/dist/rbac-7QZ4JZ2E.cjs +0 -14
- package/dist/rbac-Y43IEQOP.js +0 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth/config.ts"],"names":["baHashPassword","baVerifyPassword"],"mappings":"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","file":"chunk-SOZUG52O.js","sourcesContent":["/**\n * Better Auth configuration for SonicJS — via the better-auth-cloudflare shim.\n *\n * A fresh auth instance is built per request (Workers lifecycle). The existing\n * `auth_user` table is used as Better Auth's user model. Legacy SonicJS PBKDF2\n * hashes are verified and transparently upgraded to scrypt on first login. KV\n * (CACHE_KV) is used as session secondary storage so getSession does not hit D1\n * on every request.\n *\n * Extend via config.auth.extendBetterAuth in createSonicJSApp() to add social\n * providers, magic link, 2FA, etc.\n */\n/** Send an email via the SonicJS email plugin (Resend).\n * Loads apiKey/fromEmail/fromName from the `plugins` table at runtime.\n * Falls back to console.log when the plugin is unconfigured (local dev). */\nasync function sendViaEmailPlugin(\n db: D1Database,\n to: string,\n subject: string,\n html: string\n): Promise<void> {\n try {\n const row = (await db\n .prepare(\"SELECT settings FROM plugins WHERE id = 'email'\")\n .first()) as { settings: string } | null\n if (row?.settings) {\n const { apiKey, fromEmail, fromName } = JSON.parse(row.settings) as {\n apiKey?: string; fromEmail?: string; fromName?: string\n }\n if (apiKey && fromEmail) {\n await fetch('https://api.resend.com/emails', {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n from: `${fromName ?? 'SonicJS'} <${fromEmail}>`,\n to: [to],\n subject,\n html,\n }),\n })\n return\n }\n }\n } catch { /* fall through to dev log */ }\n console.log(`[email-dev] To:${to} | Subject:${subject}`)\n}\n\nimport { betterAuth } from 'better-auth'\nimport { withCloudflare } from 'better-auth-cloudflare'\nimport { hashPassword as baHashPassword, verifyPassword as baVerifyPassword } from 'better-auth/crypto'\nimport { APIError } from 'better-auth/api'\nimport { magicLink } from 'better-auth/plugins/magic-link'\nimport { emailOTP } from 'better-auth/plugins/email-otp'\nimport { organization } from 'better-auth/plugins/organization'\nimport { drizzle } from 'drizzle-orm/d1'\nimport { authUser, authSession, authAccount, authVerification, authTenant, authTenantMember, authTenantInvitation, authTenantTeam } from '../db/schema'\nimport { isRegistrationEnabled, isFirstUserRegistration } from '../services/auth-validation'\nimport type { Bindings } from '../app'\n\n/**\n * Verify a password against a SonicJS legacy PBKDF2 hash:\n * pbkdf2:<iterations>:<saltHex>:<hashHex> (PBKDF2-SHA256, 256-bit)\n * Mirrors AuthManager.verifyPassword in middleware/auth.ts.\n */\nasync function verifyLegacyPbkdf2(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(':')\n if (parts.length !== 4) return false\n const iterations = parseInt(parts[1]!, 10)\n const saltBytes = parts[2]!.match(/.{2}/g)\n if (!saltBytes || !Number.isFinite(iterations)) return false\n const salt = new Uint8Array(saltBytes.map((b) => parseInt(b, 16)))\n const km = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits'])\n const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' }, km, 256)\n const actual = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, '0')).join('')\n const expected = parts[3]!\n if (actual.length !== expected.length) return false\n let diff = 0\n for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i)\n return diff === 0\n}\n\n/**\n * Build the default Better Auth options used by SonicJS (through the CF shim).\n * Exported so apps can extend via config.auth.extendBetterAuth.\n */\nexport function getDefaultAuthOptions(env: Bindings, requestBaseURL?: string) {\n const db = drizzle(env.DB)\n\n return {\n secret: env.BETTER_AUTH_SECRET,\n baseURL: env.BETTER_AUTH_URL || requestBaseURL,\n appName: 'SonicJS',\n ...withCloudflare(\n {\n autoDetectIpAddress: true,\n geolocationTracking: false,\n cf: {},\n d1: {\n db,\n options: {\n // Keys MUST match modelName values — BA resolves by modelName, not by JS variable name.\n schema: { auth_user: authUser, auth_session: authSession, auth_account: authAccount, auth_verification: authVerification, auth_tenant: authTenant, auth_tenant_member: authTenantMember, auth_tenant_invitation: authTenantInvitation, auth_tenant_team: authTenantTeam },\n },\n },\n kv: env.CACHE_KV, // session secondary storage → getSession skips D1\n },\n {\n basePath: '/auth',\n emailAndPassword: {\n enabled: true,\n autoSignIn: true,\n // Transparent migration of SonicJS legacy PBKDF2 hashes: verify against\n // the old format on login, then re-hash to scrypt and persist. No\n // mass-rehash, no forced password resets.\n password: {\n verify: async ({ hash, password }: { hash: string; password: string }) => {\n if (hash.startsWith('pbkdf2:')) {\n const ok = await verifyLegacyPbkdf2(password, hash)\n if (ok) {\n const upgraded = await baHashPassword(password)\n await env.DB.prepare(\n \"UPDATE auth_account SET password = ?, updated_at = ? WHERE password = ? AND provider_id = 'credential'\"\n )\n .bind(upgraded, Math.floor(Date.now() / 1000), hash)\n .run()\n }\n return ok\n }\n return baVerifyPassword({ hash, password })\n },\n },\n },\n user: {\n modelName: 'auth_user',\n // Field-mapping values are Drizzle *property keys* (camelCase), which\n // already match Better Auth's defaults for emailVerified/createdAt/\n // updatedAt. Only `image` differs (SonicJS uses `avatar`).\n fields: {\n image: 'avatar',\n },\n additionalFields: {\n role: { type: 'string', required: false, defaultValue: 'viewer', input: false },\n firstName: { type: 'string', required: false, defaultValue: '', input: true },\n lastName: { type: 'string', required: false, defaultValue: '', input: true },\n isSuperAdmin: { type: 'boolean', required: false, defaultValue: false, input: false },\n },\n },\n session: {\n modelName: 'auth_session',\n // Drizzle property keys already match Better Auth defaults (userId,\n // expiresAt, ipAddress, …) — no field overrides needed.\n expiresIn: 60 * 60 * 24 * 7, // 7 days\n updateAge: 60 * 60 * 24, // refresh once per day\n },\n account: { modelName: 'auth_account' },\n verification: { modelName: 'auth_verification' },\n databaseHooks: {\n user: {\n create: {\n before: async (userData: Record<string, unknown>) => {\n const isFirst = await isFirstUserRegistration(env.DB)\n if (!isFirst) {\n const enabled = await isRegistrationEnabled(env.DB)\n if (!enabled) {\n throw new APIError('BAD_REQUEST', { message: 'Registration is currently disabled.' })\n }\n }\n const d = userData as {\n name?: string; email?: string; firstName?: string; lastName?: string\n }\n const name = (d.name ?? 'User').toString()\n const parts = name.trim().split(/\\s+/)\n // Prefer explicitly-provided fields (registration form); fall back\n // to values derived from name/email.\n const firstName = d.firstName || parts[0] || 'User'\n const lastName = d.lastName || parts.slice(1).join(' ') || firstName\n return { data: { ...userData, name, firstName, lastName, role: 'viewer' } }\n },\n after: async (user: { id: string }) => {\n // Assign dynamic RBAC membership. The first real user receives\n // Administrator so fresh installs can enter the portal; later\n // self-registered users receive Viewer.\n try {\n // RBAC roles/assignments are document-backed (services/rbac.ts).\n // First real portal admin → Administrator; later users → Viewer.\n // setUserRoles (via addUserRoleByName) also projects auth_user.role.\n const { RbacService } = await import('../services/rbac')\n const rbac = new RbacService(env.DB)\n const roleName = (await rbac.countPortalAdmins(user.id)) === 0 ? 'admin' : 'viewer'\n await rbac.addUserRoleByName(user.id, roleName)\n } catch {\n /* rbac docs may not be seeded yet on older schemas — non-fatal */\n }\n },\n },\n },\n },\n }\n ),\n\n // ── Phase 4: BA-native login methods ─────────────────────────────────────\n // Magic-link and Email-OTP replace the standalone SonicJS plugins that\n // minted JWT cookies. Social providers replace the bespoke oauth-providers\n // plugin. All are gated on the relevant env vars / email service config\n // so they activate only when configured.\n\n plugins: [\n // Magic-link passwordless auth. Sends a one-time link to the user's inbox;\n // the link resolves to a BA session. Requires a working email service.\n magicLink({\n sendMagicLink: async ({ email, url }: { email: string; url: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, email,\n 'Your sign-in link',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Sign in to SonicJS</h2>\n <p>Click the link below to sign in. Expires in 15 minutes.</p>\n <p><a href=\"${url}\" style=\"background:#465FFF;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none\">Sign in</a></p>\n <p style=\"color:#666;font-size:12px\">Or copy: ${url}</p>\n </div>`\n )\n },\n expiresIn: 15 * 60,\n }),\n\n // Email OTP — 6-digit code sent to inbox. Replaces the otp-login-plugin.\n emailOTP({\n sendVerificationOTP: async (params: { email: string; otp: string; type: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, params.email,\n 'Your sign-in code',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Your one-time code</h2>\n <p style=\"font-size:36px;font-weight:bold;letter-spacing:8px;color:#465FFF\">${params.otp}</p>\n <p style=\"color:#666\">Expires in 10 minutes. Do not share this code.</p>\n </div>`\n )\n },\n otpLength: 6,\n expiresIn: 10 * 60,\n }),\n\n organization({\n schema: {\n organization: {\n modelName: 'auth_tenant',\n additionalFields: {\n status: { type: 'string', required: false, defaultValue: 'active', input: true },\n domain: { type: 'string', required: false, input: true },\n notes: { type: 'string', required: false, defaultValue: '', input: true },\n },\n },\n member: {\n modelName: 'auth_tenant_member',\n fields: { organizationId: 'tenant_id' },\n },\n invitation: {\n modelName: 'auth_tenant_invitation',\n fields: { organizationId: 'tenant_id' },\n },\n team: {\n modelName: 'auth_tenant_team',\n fields: { organizationId: 'tenant_id' },\n },\n },\n }),\n ],\n\n // ── Phase 4: Social providers ─────────────────────────────────────────\n // Activated when the relevant env vars are set. Replaces the bespoke\n // oauth-providers SonicJS plugin. Set via wrangler secret put / .dev.vars.\n socialProviders: {\n ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET\n ? { github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET } }\n : {}),\n ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET\n ? { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET } }\n : {}),\n },\n }\n}\n\nexport type BetterAuthDefaultOptions = ReturnType<typeof getDefaultAuthOptions>\nexport type ExtendBetterAuth = (opts: BetterAuthDefaultOptions) => BetterAuthDefaultOptions\n\n/** Create a Better Auth instance for this request. */\nexport function createAuth(env: Bindings, extendBetterAuth?: ExtendBetterAuth, requestBaseURL?: string) {\n // Hard-fail rather than sign sessions with an undefined/blank secret. The\n // secret must be provided via `wrangler secret put BETTER_AUTH_SECRET`\n // (prod/preview) or a gitignored `.dev.vars` (local) — never committed.\n if (!env.BETTER_AUTH_SECRET || env.BETTER_AUTH_SECRET.length < 16) {\n throw new Error(\n 'BETTER_AUTH_SECRET is missing or too short. Set it as a Wrangler secret ' +\n '(wrangler secret put BETTER_AUTH_SECRET) or in a gitignored .dev.vars for local dev. ' +\n 'Refusing to initialize auth without a strong signing secret.'\n )\n }\n const defaults = getDefaultAuthOptions(env, requestBaseURL)\n const options = extendBetterAuth ? extendBetterAuth(defaults) : defaults\n return betterAuth(options as Parameters<typeof betterAuth>[0])\n}\n\nexport type SonicJSAuth = ReturnType<typeof createAuth>\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/auth/config.ts"],"names":["baHashPassword","baVerifyPassword"],"mappings":"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","file":"chunk-JG5J434C.js","sourcesContent":["/**\n * Better Auth configuration for SonicJS — via the better-auth-cloudflare shim.\n *\n * A fresh auth instance is built per request (Workers lifecycle). The existing\n * `auth_user` table is used as Better Auth's user model. Legacy SonicJS PBKDF2\n * hashes are verified and transparently upgraded to scrypt on first login. KV\n * (CACHE_KV) is used as session secondary storage so getSession does not hit D1\n * on every request.\n *\n * Extend via config.auth.extendBetterAuth in createSonicJSApp() to add social\n * providers, magic link, 2FA, etc.\n */\n/** Send an email via the SonicJS email plugin (Resend).\n * Loads apiKey/fromEmail/fromName from the `plugins` table at runtime.\n * Falls back to console.log when the plugin is unconfigured (local dev). */\nasync function sendViaEmailPlugin(\n db: D1Database,\n to: string,\n subject: string,\n html: string\n): Promise<void> {\n try {\n const row = (await db\n .prepare(\"SELECT settings FROM plugins WHERE id = 'email'\")\n .first()) as { settings: string } | null\n if (row?.settings) {\n const { apiKey, fromEmail, fromName } = JSON.parse(row.settings) as {\n apiKey?: string; fromEmail?: string; fromName?: string\n }\n if (apiKey && fromEmail) {\n await fetch('https://api.resend.com/emails', {\n method: 'POST',\n headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n from: `${fromName ?? 'SonicJS'} <${fromEmail}>`,\n to: [to],\n subject,\n html,\n }),\n })\n return\n }\n }\n } catch { /* fall through to dev log */ }\n console.log(`[email-dev] To:${to} | Subject:${subject}`)\n}\n\nimport { betterAuth } from 'better-auth'\nimport { withCloudflare } from 'better-auth-cloudflare'\nimport { hashPassword as baHashPassword, verifyPassword as baVerifyPassword } from 'better-auth/crypto'\nimport { APIError } from 'better-auth/api'\nimport { magicLink } from 'better-auth/plugins/magic-link'\nimport { emailOTP } from 'better-auth/plugins/email-otp'\nimport { organization } from 'better-auth/plugins/organization'\nimport { drizzle } from 'drizzle-orm/d1'\nimport { authUser, authSession, authAccount, authVerification, authTenant, authTenantMember, authTenantInvitation, authTenantTeam } from '../db/schema'\nimport { isRegistrationEnabled, isFirstUserRegistration } from '../services/auth-validation'\nimport type { Bindings } from '../app'\n\n/**\n * Verify a password against a SonicJS legacy PBKDF2 hash:\n * pbkdf2:<iterations>:<saltHex>:<hashHex> (PBKDF2-SHA256, 256-bit)\n * Mirrors AuthManager.verifyPassword in middleware/auth.ts.\n */\nasync function verifyLegacyPbkdf2(password: string, stored: string): Promise<boolean> {\n const parts = stored.split(':')\n if (parts.length !== 4) return false\n const iterations = parseInt(parts[1]!, 10)\n const saltBytes = parts[2]!.match(/.{2}/g)\n if (!saltBytes || !Number.isFinite(iterations)) return false\n const salt = new Uint8Array(saltBytes.map((b) => parseInt(b, 16)))\n const km = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveBits'])\n const bits = await crypto.subtle.deriveBits({ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' }, km, 256)\n const actual = Array.from(new Uint8Array(bits)).map((b) => b.toString(16).padStart(2, '0')).join('')\n const expected = parts[3]!\n if (actual.length !== expected.length) return false\n let diff = 0\n for (let i = 0; i < actual.length; i++) diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i)\n return diff === 0\n}\n\n/**\n * Build the default Better Auth options used by SonicJS (through the CF shim).\n * Exported so apps can extend via config.auth.extendBetterAuth.\n */\nexport function getDefaultAuthOptions(env: Bindings, requestBaseURL?: string) {\n const db = drizzle(env.DB)\n\n return {\n secret: env.BETTER_AUTH_SECRET,\n baseURL: env.BETTER_AUTH_URL || requestBaseURL,\n appName: 'SonicJS',\n ...withCloudflare(\n {\n autoDetectIpAddress: true,\n geolocationTracking: false,\n cf: {},\n d1: {\n db,\n options: {\n // Keys MUST match modelName values — BA resolves by modelName, not by JS variable name.\n schema: { auth_user: authUser, auth_session: authSession, auth_account: authAccount, auth_verification: authVerification, auth_tenant: authTenant, auth_tenant_member: authTenantMember, auth_tenant_invitation: authTenantInvitation, auth_tenant_team: authTenantTeam },\n },\n },\n kv: env.CACHE_KV, // session secondary storage → getSession skips D1\n },\n {\n basePath: '/auth',\n emailAndPassword: {\n enabled: true,\n autoSignIn: true,\n // Transparent migration of SonicJS legacy PBKDF2 hashes: verify against\n // the old format on login, then re-hash to scrypt and persist. No\n // mass-rehash, no forced password resets.\n password: {\n verify: async ({ hash, password }: { hash: string; password: string }) => {\n if (hash.startsWith('pbkdf2:')) {\n const ok = await verifyLegacyPbkdf2(password, hash)\n if (ok) {\n const upgraded = await baHashPassword(password)\n await env.DB.prepare(\n \"UPDATE auth_account SET password = ?, updated_at = ? WHERE password = ? AND provider_id = 'credential'\"\n )\n .bind(upgraded, Math.floor(Date.now() / 1000), hash)\n .run()\n }\n return ok\n }\n return baVerifyPassword({ hash, password })\n },\n },\n },\n user: {\n modelName: 'auth_user',\n // Field-mapping values are Drizzle *property keys* (camelCase), which\n // already match Better Auth's defaults for emailVerified/createdAt/\n // updatedAt. Only `image` differs (SonicJS uses `avatar`).\n fields: {\n image: 'avatar',\n },\n additionalFields: {\n role: { type: 'string', required: false, defaultValue: 'viewer', input: false },\n firstName: { type: 'string', required: false, defaultValue: '', input: true },\n lastName: { type: 'string', required: false, defaultValue: '', input: true },\n isSuperAdmin: { type: 'boolean', required: false, defaultValue: false, input: false },\n },\n },\n session: {\n modelName: 'auth_session',\n // Drizzle property keys already match Better Auth defaults (userId,\n // expiresAt, ipAddress, …) — no field overrides needed.\n expiresIn: 60 * 60 * 24 * 7, // 7 days\n updateAge: 60 * 60 * 24, // refresh once per day\n },\n account: { modelName: 'auth_account' },\n verification: { modelName: 'auth_verification' },\n databaseHooks: {\n user: {\n create: {\n before: async (userData: Record<string, unknown>) => {\n const isFirst = await isFirstUserRegistration(env.DB)\n if (!isFirst) {\n const enabled = await isRegistrationEnabled(env.DB)\n if (!enabled) {\n throw new APIError('BAD_REQUEST', { message: 'Registration is currently disabled.' })\n }\n }\n const d = userData as {\n name?: string; email?: string; firstName?: string; lastName?: string\n }\n const name = (d.name ?? 'User').toString()\n const parts = name.trim().split(/\\s+/)\n // Prefer explicitly-provided fields (registration form); fall back\n // to values derived from name/email.\n const firstName = d.firstName || parts[0] || 'User'\n const lastName = d.lastName || parts.slice(1).join(' ') || firstName\n return { data: { ...userData, name, firstName, lastName, role: 'viewer' } }\n },\n after: async (user: { id: string }) => {\n // Assign dynamic RBAC membership. The first real user receives\n // Administrator so fresh installs can enter the portal; later\n // self-registered users receive Viewer.\n try {\n // RBAC roles/assignments are document-backed (services/rbac.ts).\n // First real portal admin → Administrator; later users → Viewer.\n // setUserRoles (via addUserRoleByName) also projects auth_user.role.\n const { RbacService } = await import('../services/rbac')\n const rbac = new RbacService(env.DB)\n const roleName = (await rbac.countPortalAdmins(user.id)) === 0 ? 'admin' : 'viewer'\n await rbac.addUserRoleByName(user.id, roleName)\n } catch {\n /* rbac docs may not be seeded yet on older schemas — non-fatal */\n }\n },\n },\n },\n },\n }\n ),\n\n // ── Phase 4: BA-native login methods ─────────────────────────────────────\n // Magic-link and Email-OTP replace the standalone SonicJS plugins that\n // minted JWT cookies. Social providers replace the bespoke oauth-providers\n // plugin. All are gated on the relevant env vars / email service config\n // so they activate only when configured.\n\n plugins: [\n // Magic-link passwordless auth. Sends a one-time link to the user's inbox;\n // the link resolves to a BA session. Requires a working email service.\n magicLink({\n sendMagicLink: async ({ email, url }: { email: string; url: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, email,\n 'Your sign-in link',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Sign in to SonicJS</h2>\n <p>Click the link below to sign in. Expires in 15 minutes.</p>\n <p><a href=\"${url}\" style=\"background:#465FFF;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none\">Sign in</a></p>\n <p style=\"color:#666;font-size:12px\">Or copy: ${url}</p>\n </div>`\n )\n },\n expiresIn: 15 * 60,\n }),\n\n // Email OTP — 6-digit code sent to inbox. Replaces the otp-login-plugin.\n emailOTP({\n sendVerificationOTP: async (params: { email: string; otp: string; type: string }, _request: any) => {\n await sendViaEmailPlugin(\n env.DB, params.email,\n 'Your sign-in code',\n `<div style=\"font-family:sans-serif;max-width:600px\">\n <h2>Your one-time code</h2>\n <p style=\"font-size:36px;font-weight:bold;letter-spacing:8px;color:#465FFF\">${params.otp}</p>\n <p style=\"color:#666\">Expires in 10 minutes. Do not share this code.</p>\n </div>`\n )\n },\n otpLength: 6,\n expiresIn: 10 * 60,\n }),\n\n organization({\n schema: {\n organization: {\n modelName: 'auth_tenant',\n additionalFields: {\n status: { type: 'string', required: false, defaultValue: 'active', input: true },\n domain: { type: 'string', required: false, input: true },\n notes: { type: 'string', required: false, defaultValue: '', input: true },\n },\n },\n member: {\n modelName: 'auth_tenant_member',\n fields: { organizationId: 'tenant_id' },\n },\n invitation: {\n modelName: 'auth_tenant_invitation',\n fields: { organizationId: 'tenant_id' },\n },\n team: {\n modelName: 'auth_tenant_team',\n fields: { organizationId: 'tenant_id' },\n },\n },\n }),\n ],\n\n // ── Phase 4: Social providers ─────────────────────────────────────────\n // Activated when the relevant env vars are set. Replaces the bespoke\n // oauth-providers SonicJS plugin. Set via wrangler secret put / .dev.vars.\n socialProviders: {\n ...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET\n ? { github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET } }\n : {}),\n ...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET\n ? { google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET } }\n : {}),\n },\n }\n}\n\nexport type BetterAuthDefaultOptions = ReturnType<typeof getDefaultAuthOptions>\nexport type ExtendBetterAuth = (opts: BetterAuthDefaultOptions) => BetterAuthDefaultOptions\n\n/** Create a Better Auth instance for this request. */\nexport function createAuth(env: Bindings, extendBetterAuth?: ExtendBetterAuth, requestBaseURL?: string) {\n // Hard-fail rather than sign sessions with an undefined/blank secret. The\n // secret must be provided via `wrangler secret put BETTER_AUTH_SECRET`\n // (prod/preview) or a gitignored `.dev.vars` (local) — never committed.\n if (!env.BETTER_AUTH_SECRET || env.BETTER_AUTH_SECRET.length < 16) {\n throw new Error(\n 'BETTER_AUTH_SECRET is missing or too short. Set it as a Wrangler secret ' +\n '(wrangler secret put BETTER_AUTH_SECRET) or in a gitignored .dev.vars for local dev. ' +\n 'Refusing to initialize auth without a strong signing secret.'\n )\n }\n const defaults = getDefaultAuthOptions(env, requestBaseURL)\n const options = extendBetterAuth ? extendBetterAuth(defaults) : defaults\n return betterAuth(options as Parameters<typeof betterAuth>[0])\n}\n\nexport type SonicJSAuth = ReturnType<typeof createAuth>\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkJ6JTWD2A_cjs = require('./chunk-J6JTWD2A.cjs');
|
|
4
|
-
var
|
|
4
|
+
var chunkXZUEGFXE_cjs = require('./chunk-XZUEGFXE.cjs');
|
|
5
5
|
var chunkMNWKYY5E_cjs = require('./chunk-MNWKYY5E.cjs');
|
|
6
6
|
|
|
7
7
|
// src/plugins/singletons/service-singleton.ts
|
|
@@ -205,7 +205,7 @@ function definePlugin(input) {
|
|
|
205
205
|
);
|
|
206
206
|
}
|
|
207
207
|
if (input.sonicjsVersionRange) {
|
|
208
|
-
const coreVersion =
|
|
208
|
+
const coreVersion = chunkXZUEGFXE_cjs.getCoreVersion();
|
|
209
209
|
if (!semverSatisfies(coreVersion, input.sonicjsVersionRange)) {
|
|
210
210
|
console.warn(
|
|
211
211
|
`[plugins] Plugin "${input.id}" declares sonicjsVersionRange "${input.sonicjsVersionRange}" but running core version is "${coreVersion}". The plugin may not work correctly. Consider updating the plugin or the version range.`
|
|
@@ -414,7 +414,8 @@ function resolvePluginMenuItems(user) {
|
|
|
414
414
|
label: m.label,
|
|
415
415
|
path: m.path,
|
|
416
416
|
icon: resolveIcon(m.icon),
|
|
417
|
-
order: m.order ?? 100
|
|
417
|
+
order: m.order ?? 100,
|
|
418
|
+
pluginId: m.pluginId
|
|
418
419
|
}));
|
|
419
420
|
}
|
|
420
421
|
|
|
@@ -453,5 +454,5 @@ exports.resolvePluginMenuItems = resolvePluginMenuItems;
|
|
|
453
454
|
exports.setPluginDefinitions = setPluginDefinitions;
|
|
454
455
|
exports.setPluginMenu = setPluginMenu;
|
|
455
456
|
exports.validateCapabilities = validateCapabilities;
|
|
456
|
-
//# sourceMappingURL=chunk-
|
|
457
|
-
//# sourceMappingURL=chunk-
|
|
457
|
+
//# sourceMappingURL=chunk-KWQNB6UM.cjs.map
|
|
458
|
+
//# sourceMappingURL=chunk-KWQNB6UM.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugins/singletons/service-singleton.ts","../src/plugins/capabilities.ts","../src/plugins/sdk/define-plugin.ts","../src/plugins/sdk/config-schema.ts","../src/services/plugin-menu-singleton.ts","../src/services/plugin-definition-registry.ts"],"names":["createTypedHooks","getCoreVersion","escapeHtml"],"mappings":"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","file":"chunk-KWQNB6UM.cjs","sourcesContent":["/**\n * Generic service-singleton factory\n *\n * Generalizes the hook-system-singleton pattern: a process-wide slot for a\n * service that code outside the HTTP request context — most importantly cron /\n * `scheduled()` handlers, which have no per-request `c.env` — can reach without\n * threading it through every call.\n *\n * Contract (same as the hook-system singleton):\n * - `get()` throws if read before `set()` (throw-before-get) so wiring-order\n * bugs surface loudly rather than silently no-oping.\n * - `set()` is idempotent (last write wins) so constructing multiple apps in one\n * process (e.g. across tests) never throws.\n * - `reset()` clears the slot for test isolation.\n */\n\nexport interface ServiceSingleton<T> {\n /** Set the process-wide instance. Last write wins. */\n set(instance: T): void\n /** Get the instance; throws if not yet set. */\n get(): T\n /** True if an instance has been set. */\n has(): boolean\n /** Clear the slot (test isolation). */\n reset(): void\n}\n\n/**\n * Create a service singleton.\n *\n * @param label Human-readable name used in the throw-before-get error message\n * (e.g. `'EmailService'`).\n */\nexport function createServiceSingleton<T>(label: string): ServiceSingleton<T> {\n let current: T | undefined\n return {\n set(instance: T) {\n current = instance\n },\n get(): T {\n if (current === undefined) {\n throw new Error(\n `${label} has not been initialized. Its setter must be called (the app factory does this at construction) before reading it.`\n )\n }\n return current\n },\n has(): boolean {\n return current !== undefined\n },\n reset() {\n current = undefined\n },\n }\n}\n","/**\n * Plugin capabilities\n *\n * A plugin declares the capabilities it needs (`capabilities: ['email:send']`).\n * The host then hands it a context whose powerful accessors are *gated* by those\n * declarations: reaching `ctx.email` without having declared `email:send` throws\n * `SonicCapabilityError` immediately, instead of failing deep inside a send.\n *\n * This is the isolation boundary that Strapi (namespacing only) and Payload (full\n * config access) don't have: capabilities make a plugin's blast radius explicit\n * and enforceable, and double as documentation of what a plugin can touch.\n *\n * Phase 1 vocabulary. Payment/queue/scheduled-fetch capabilities are intentionally\n * deferred to Phase 2 (see the overhaul plan §8.4 / open question 2).\n */\n\n// Type-only import (no runtime coupling) so a const-narrowed `ctx.cap.email`\n// resolves to the real EmailService type instead of `unknown`.\nimport type { EmailService } from '../services/email/email-service'\nimport type { HookEventName } from './hooks/catalog'\n\n/**\n * Fixed capability names. `db:<table>` is parameterized (a plugin owns specific\n * tables), so it is matched by pattern rather than listed here.\n */\nexport const FIXED_CAPABILITIES = [\n 'email:send',\n 'cache:read',\n 'cache:write',\n 'media:read',\n 'media:write',\n 'http:fetch',\n 'cron:register',\n 'admin:menu',\n 'hooks.auth:subscribe',\n 'hooks.content:subscribe',\n // Reserved: gates subscription to the email event family once those events are\n // dispatched. Declared now so the canonical vocabulary is stable and the\n // Infowall rename target resolves.\n 'hooks.email:subscribe',\n] as const\n\nexport type FixedCapability = (typeof FIXED_CAPABILITIES)[number]\n\n/** A scoped database capability, e.g. `db:email_log`. */\nexport type DbCapability = `db:${string}`\n\n/** Any declarable capability. */\nexport type Capability = FixedCapability | DbCapability\n\nconst DB_CAPABILITY_RE = /^db:[a-zA-Z_][a-zA-Z0-9_]*$/\n\n/** True if `name` is a recognized capability (fixed name or a valid `db:<table>`). */\nexport function isKnownCapability(name: string): name is Capability {\n return (FIXED_CAPABILITIES as readonly string[]).includes(name) || DB_CAPABILITY_RE.test(name)\n}\n\n/**\n * Thrown when a plugin uses a capability it did not declare.\n */\nexport class SonicCapabilityError extends Error {\n readonly capability: string\n readonly plugin?: string\n /** The API surface the plugin tried to access (e.g. 'ctx.cap.email'). Optional. */\n readonly accessedApi?: string\n constructor(capability: string, plugin?: string, accessedApi?: string) {\n super(\n `${plugin ? `Plugin \"${plugin}\"` : 'Plugin'} used capability \"${capability}\" without declaring it. ` +\n `Add \"${capability}\" to the plugin's capabilities.`\n )\n this.name = 'SonicCapabilityError'\n this.capability = capability\n this.plugin = plugin\n this.accessedApi = accessedApi\n }\n}\n\n/** True if `capability` is in the granted set. */\nexport function hasCapability(granted: readonly string[], capability: string): boolean {\n return granted.includes(capability)\n}\n\n/**\n * Assert a capability is granted, throwing {@link SonicCapabilityError} otherwise.\n */\nexport function assertCapability(granted: readonly string[], capability: string, plugin?: string): void {\n if (!hasCapability(granted, capability)) {\n throw new SonicCapabilityError(capability, plugin)\n }\n}\n\n/**\n * Validate a plugin's declared capability list. Returns the unknown entries (empty\n * array = all valid). Callers decide whether to warn or hard-fail.\n *\n * Apply {@link normalizeCapabilities} first if the input may contain deprecated\n * spellings (e.g. from an older SDK or a sibling fork's manifest).\n */\nexport function validateCapabilities(declared: readonly string[]): string[] {\n return declared.filter((c) => !isKnownCapability(c))\n}\n\n// ── Capability rename / normalization (cross-version & cross-fork portability) ─\n//\n// Lets a plugin authored against a different/older SDK spelling load against the\n// canonical vocabulary without code changes. The map is deprecated→canonical; it\n// is applied before the known-capability check. Seeded with the sibling fork's\n// (Infowall) spellings so plugins built there are portable here.\n\nexport const CAPABILITY_RENAMES = {\n // storage:* → media:* (this fork scopes the media library as `media`)\n 'storage:read': 'media:read',\n 'storage:write': 'media:write',\n // cron is a direct registration, not a hook subscription, here\n 'hooks.cron:register': 'cron:register',\n // `:register` → `:subscribe` verb; content read/write granularity lives in the\n // event name (before/after), so both collapse to one subscription capability\n 'hooks.auth:register': 'hooks.auth:subscribe',\n 'hooks.content-read:register': 'hooks.content:subscribe',\n 'hooks.content-write:register': 'hooks.content:subscribe',\n 'hooks.email-events:register': 'hooks.email:subscribe',\n // NOTE: Infowall's `request:intercept` has no canonical target — there is no\n // middleware-insertion surface to gate yet, so it is intentionally absent\n // (a plugin declaring it will surface as unknown rather than silently no-op).\n} as const satisfies Record<string, Capability>\n\n/**\n * Resolve a (possibly deprecated) capability string to its canonical form, or\n * `null` if it is unknown after rename resolution. Renames apply first, then the\n * result is checked against the known vocabulary.\n */\nexport function normalizeCapability(input: string): Capability | null {\n const renamed: string = (CAPABILITY_RENAMES as Record<string, Capability>)[input] ?? input\n return isKnownCapability(renamed) ? (renamed as Capability) : null\n}\n\n/**\n * Normalize a list of capability strings. Returns the canonical capabilities plus\n * the inputs that remained unknown after rename resolution, so the caller can warn\n * (production) or reject (strict).\n */\nexport function normalizeCapabilities(declared: readonly string[]): {\n capabilities: Capability[]\n unknown: string[]\n} {\n const capabilities: Capability[] = []\n const unknown: string[] = []\n for (const raw of declared) {\n const canonical = normalizeCapability(raw)\n if (canonical) {\n if (!capabilities.includes(canonical)) capabilities.push(canonical)\n } else {\n unknown.push(raw)\n }\n }\n return { capabilities, unknown }\n}\n\n// ── Capability-gated context ─────────────────────────────────────────────────\n\n/** Provider factories for capability-backed accessors. Each is called lazily. */\nexport interface CapabilityProviders {\n email?: () => EmailService\n cache?: () => unknown\n http?: () => typeof fetch\n}\n\n// ── Type-level capability narrowing ──────────────────────────────────────────\ndeclare const CAP_NOT_DECLARED: unique symbol\n/**\n * The type of a capability accessor the plugin did NOT declare. Deliberately a\n * branded type (not `never`, which is assignable to everything): using it where a\n * service is expected is a compile error, so `ctx.cap.email` without `email:send`\n * fails to type-check instead of silently passing.\n */\nexport type CapabilityNotDeclared<C extends string = string> = {\n readonly [CAP_NOT_DECLARED]: C\n}\n\n// `T` if the declared tuple `Caps` contains `C`, else the branded not-declared type.\ntype WhenGranted<Caps extends readonly Capability[], C extends string, T> =\n C extends Caps[number] ? T : CapabilityNotDeclared<C>\n// `T` if `Caps` contains ANY of the union `C`, else the branded not-declared type.\ntype WhenGrantedAny<Caps extends readonly Capability[], C extends string, T> =\n Extract<Caps[number], C> extends never ? CapabilityNotDeclared<C> : T\n\n/**\n * The gated context handed to a plugin. Accessors throw at runtime unless the\n * backing capability was declared; with a const-narrowed `Caps` tuple they are\n * also *typed* to the service (or `never`) at compile time, so `ctx.cap.email`\n * is `EmailService` only when `'email:send'` was declared.\n */\nexport interface CapabilityContext<Caps extends readonly Capability[] = readonly Capability[]> {\n /** The capabilities granted to this plugin. */\n readonly capabilities: readonly string[]\n /** True if the plugin declared `capability`. */\n has(capability: string): boolean\n /** Throw {@link SonicCapabilityError} unless `capability` was declared. */\n require(capability: string): void\n /** Email service. `EmailService` when `email:send` is declared, else `never`. */\n readonly email: WhenGranted<Caps, 'email:send', EmailService>\n /** Cache service. Present when `cache:read` or `cache:write` is declared. */\n readonly cache: WhenGrantedAny<Caps, 'cache:read' | 'cache:write', unknown>\n /** Outbound fetch. Present when `http:fetch` is declared. */\n readonly http: WhenGranted<Caps, 'http:fetch', typeof fetch>\n}\n\n/** The un-narrowed gated context (every accessor typed as its service). */\nexport type PluginCapabilityContext = CapabilityContext</** all */ readonly Capability[]>\n\n\n/**\n * Build a capability-gated context.\n *\n * Accessors are lazy getters: they check the grant (and that a provider was\n * supplied) at access time, so merely constructing the context is cheap and\n * holding a reference to a capability you never use costs nothing.\n *\n * @param granted Capabilities the plugin declared.\n * @param providers Backing service factories (only the granted ones get called).\n * @param plugin Plugin name, for clearer errors.\n */\nexport function createCapabilityContext<const Caps extends readonly Capability[]>(\n granted: Caps,\n providers: CapabilityProviders = {},\n plugin?: string\n): CapabilityContext<Caps> {\n // `any` so the lazy getters satisfy the narrowed accessor types (the provider\n // returns the real service at runtime; gating is enforced dynamically below).\n const gate = (capability: string, anyOf: string[], provider: (() => unknown) | undefined): any => {\n const ok = anyOf.some((c) => hasCapability(granted, c))\n if (!ok) throw new SonicCapabilityError(capability, plugin)\n if (!provider) {\n throw new Error(\n `Capability \"${capability}\" is declared by ${plugin ?? 'the plugin'} but no provider was supplied by the host.`\n )\n }\n return provider()\n }\n\n const ctx: CapabilityContext<Caps> = {\n capabilities: [...granted],\n has: (capability) => hasCapability(granted, capability),\n require: (capability) => assertCapability(granted, capability, plugin),\n get email() {\n return gate('email:send', ['email:send'], providers.email)\n },\n get cache() {\n return gate('cache:read', ['cache:read', 'cache:write'], providers.cache)\n },\n get http() {\n return gate('http:fetch', ['http:fetch'], providers.http)\n },\n }\n return ctx\n}\n\n// ── Hook-subscription capability map ────────────────────────────────────────\n//\n// Maps each catalog event to the capability a plugin must declare in order to\n// subscribe to it. Used by the wire phase (Phase A) to gate declarative hook\n// registrations. Plugins that don't declare the required capability have the\n// offending hook skipped (warn in prod, error in strict).\n\nexport const HOOK_CAPABILITY_MAP: Record<HookEventName, Capability> = {\n 'content:read': 'hooks.content:subscribe',\n 'content:before:create': 'hooks.content:subscribe',\n 'content:before:update': 'hooks.content:subscribe',\n 'content:before:delete': 'hooks.content:subscribe',\n 'content:after:create': 'hooks.content:subscribe',\n 'content:after:update': 'hooks.content:subscribe',\n 'content:after:delete': 'hooks.content:subscribe',\n 'content:after:publish': 'hooks.content:subscribe',\n 'auth:registration:completed': 'hooks.auth:subscribe',\n 'auth:password-reset:requested': 'hooks.auth:subscribe',\n 'auth:password-reset:completed': 'hooks.auth:subscribe',\n 'auth:magic-link:consumed': 'hooks.auth:subscribe',\n 'auth:otp:verified': 'hooks.auth:subscribe',\n}\n","/**\n * definePlugin() — the v3 plugin authoring entry point\n *\n * A plugin is authored as a single typed declaration and consumed, unchanged, by\n * every part of the runtime:\n *\n * export const emailPlugin = definePlugin({\n * id: 'email',\n * version: '3.0.0',\n * capabilities: ['email:send', 'hooks.auth:subscribe', 'cron:register'],\n * register(app) { app.route('/admin/plugins/email', emailRoutes) }, // SYNC\n * async onBoot(ctx) { // ASYNC\n * ctx.hooks.on('auth:registration:completed', (p) => { ... }) // typed\n * ctx.cap.email // gated\n * },\n * crons: [{ schedule: '*\\/15 * * * *', hookFamily: 'email-reconciliation' }],\n * async onCronTick(event, ctx) { ... },\n * })\n *\n * The object it returns satisfies the structural contracts the runtime already\n * uses — `MountablePlugin` (mount.ts), `WirablePlugin` (wire.ts), `CronablePlugin`\n * (cron.ts) — plus the legacy `Plugin` metadata fields the admin/registry read.\n * No adapters: a defined plugin drops straight into `plugins.register` or the core\n * plugin list.\n *\n * The value definePlugin adds over a hand-written object is the enriched context:\n * inside `onBoot`/`onCronTick` the author gets a *typed* hook facade (`ctx.hooks`)\n * and the *capability-gated* service context (`ctx.cap`), instead of the raw\n * string-keyed hook system. See the two-phase boot contract: `register` is\n * synchronous (routes only); everything env-dependent lives in `onBoot`.\n */\n\nimport type { Hono } from 'hono'\nimport type { Capability } from '../capabilities'\nimport {\n createCapabilityContext,\n normalizeCapabilities,\n type CapabilityProviders,\n type CapabilityContext,\n} from '../capabilities'\nimport { createTypedHooks, type TypedHooks, type TypedHookHandler } from '../hooks/typed-hooks'\nimport type { HookEventName } from '../hooks/catalog'\nimport type { PluginBootContext, WirableHook } from '../wire'\nimport type { CronContext, CronDeclaration, CronTickEvent } from '../cron'\nimport type { MountableRoute } from '../mount'\nimport { getCoreVersion } from '../../utils/version'\nimport type { ConfigSchema } from './config-schema'\nimport type { PluginMenuEntry } from '../../services/plugin-menu-singleton'\n\n// ── Minimal semver helpers (no external dep — Workers are bundle-size constrained) ─\n\n/** True if `v` is a valid semver string (X.Y.Z with optional pre-release). */\nfunction isSemver(v: string): boolean {\n return /^\\d+\\.\\d+\\.\\d+(-[\\w.]+)?(\\+[\\w.]+)?$/.test(v.trim())\n}\n\n/**\n * Very lightweight semver range satisfier. Handles the most common range forms:\n * exact (`1.2.3`), caret (`^1.2.3` = compatible major), tilde (`~1.2.3` = compatible\n * minor), comparators (`>=1.0.0`, `>1`, `<2.0.0`), and space-separated AND chains.\n * Not a full semver implementation — use the `semver` npm package if you need\n * full range syntax in a non-Workers environment.\n */\nfunction semverSatisfies(version: string, range: string): boolean {\n try {\n const [major, minor, patch] = version.trim().split('-')[0]!.split('.').map(Number)\n const v = major! * 1_000_000 + (minor ?? 0) * 1_000 + (patch ?? 0)\n\n const toInt = (s: string) => {\n const [a, b, c] = s.split('.').map(Number)\n return a! * 1_000_000 + (b ?? 0) * 1_000 + (c ?? 0)\n }\n\n // AND chain: all clauses must pass.\n return range\n .trim()\n .split(/\\s+(?=[><=^~])|\\s+(?=\\d)/)\n .filter(Boolean)\n .every((clause) => {\n const c = clause.trim()\n if (c.startsWith('^')) {\n const base = toInt(c.slice(1))\n const nextMajor = Math.floor(base / 1_000_000 + 1) * 1_000_000\n return v >= base && v < nextMajor\n }\n if (c.startsWith('~')) {\n const base = toInt(c.slice(1))\n const nextMinor = Math.floor(base / 1_000 + 1) * 1_000\n return v >= base && v < nextMinor\n }\n if (c.startsWith('>=')) return v >= toInt(c.slice(2))\n if (c.startsWith('<=')) return v <= toInt(c.slice(2))\n if (c.startsWith('>')) return v > toInt(c.slice(1))\n if (c.startsWith('<')) return v < toInt(c.slice(1))\n return v === toInt(c) // exact\n })\n } catch {\n return true // fail open — don't block a plugin on a parse error\n }\n}\n\n/**\n * Declarative typed hook subscriptions: a map of canonical event name → handler,\n * each narrowed to that event's payload. Flattened into the plugin's `hooks[]`\n * and subscribed during the wire phase. Use `onBoot`'s `ctx.hooks.on()` instead\n * for dynamic/conditional subscriptions.\n */\nexport type DeclarativeHooks = {\n [E in HookEventName]?: TypedHookHandler<E>\n}\n\n/**\n * Context handed to a defined plugin's `onBoot` / `onCronTick`.\n *\n * Enriches the raw boot/cron context with a typed hook facade and the gated\n * capability context, while keeping `raw` and `env` available as an escape hatch.\n */\nexport interface DefinedPluginContext<Caps extends readonly Capability[] = readonly Capability[]> {\n /** Typed hook facade — `ctx.hooks.on('auth:registration:completed', …)`. */\n hooks: TypedHooks\n /**\n * Capability-gated services. With a const-narrowed `Caps`, `ctx.cap.email` is\n * typed `EmailService` only when `'email:send'` was declared (else `never`),\n * and throws `SonicCapabilityError` at runtime if accessed undeclared.\n */\n cap: CapabilityContext<Caps>\n /** Runtime bindings, when available (absent during construction). */\n env?: Record<string, unknown>\n /** The unwrapped context the runtime passed. */\n raw: PluginBootContext | CronContext\n}\n\n/** Input to {@link definePlugin}. */\nexport interface DefinePluginInput<Caps extends readonly Capability[] = readonly []> {\n /** Unique, stable plugin id (kebab-case). Becomes the plugin `name`. */\n id: string\n /** Human-readable display name. Defaults to `id`. */\n name?: string\n /**\n * Semantic version of this plugin (e.g. `'1.2.3'`). Must be a valid semver\n * string — invalid values emit a console.warn at definition time.\n */\n version: string\n /**\n * A semver range expressing which SonicJS core versions this plugin supports\n * (e.g. `'^3.0.0'` or `'>=3.1.0 <4.0.0'`). Checked against the running\n * core version at registration; a mismatch logs a compatibility warning but\n * does not block activation (plugins remain resilient by default).\n */\n sonicjsVersionRange?: string\n description?: string\n author?: { name: string; email?: string; url?: string }\n /** Other plugin ids this one needs (load-order / activation). */\n dependencies?: string[]\n /**\n * Capabilities this plugin declares. The gated `ctx.cap` accessors throw for\n * anything not listed here. Pass as a literal (`['email:send'] as const` is not\n * needed — the `const` type param infers the tuple) to get `ctx.cap` narrowed.\n * Unknown/deprecated names are normalized then warned about at definition.\n */\n capabilities?: Caps\n\n // ── Synchronous registration (routes only) ──\n /** Declarative routes (mounted before the /admin catch-all). */\n routes?: MountableRoute[]\n /**\n * Imperative route registration. MUST be synchronous (Hono's router locks after\n * the first request). Async work belongs in `onBoot`.\n */\n register?: (app: Hono) => void\n\n // ── Asynchronous wiring ──\n /**\n * Declarative typed hook subscriptions (`{ 'content:after:create': (p) => … }`).\n * Subscribed during the wire phase; each handler is narrowed to its event payload.\n */\n hooks?: DeclarativeHooks\n /**\n * Run once on first request, after every plugin has registered. The place for\n * dynamic hook subscriptions (`ctx.hooks.on(...)`) and env-dependent setup.\n */\n onBoot?: (context: DefinedPluginContext<Caps>) => void | Promise<void>\n\n // ── Cron ──\n /** Scheduled-work declarations (also list the expressions in wrangler.toml). */\n crons?: CronDeclaration[]\n /** Handler for a fired cron; branch on `event.hookFamily`. */\n onCronTick?: (event: CronTickEvent, context: DefinedPluginContext<Caps>) => void | Promise<void>\n\n // ── Lifecycle (DB/schema only — never touches routes) ──\n install?: (context: unknown) => void | Promise<void>\n uninstall?: (context: unknown) => void | Promise<void>\n activate?: (context: unknown) => void | Promise<void>\n deactivate?: (context: unknown) => void | Promise<void>\n\n // ── Declarative admin surface ──\n /**\n * Declarative admin-sidebar entries collected by registerPlugins. The catalyst\n * sidebar renders them automatically; no per-plugin `addMenuItem(...)` call\n * required.\n */\n menu?: PluginMenuEntry[]\n /**\n * Schema-driven settings. Declaring this auto-renders the admin form at\n * `/admin/settings/plugins/:id`, parses FormData back into typed values, and\n * persists them via the plugin-service. Authors no longer hand-roll settings\n * routes/templates.\n */\n configSchema?: ConfigSchema\n /**\n * Custom settings-tab renderer for the plugin detail page (`/admin/plugins/:id`).\n * `loadData` is called server-side with the D1 database before the page renders;\n * its result is forwarded to `render` as `data`. Plugins that need DB access\n * (e.g. fetching their own document records) declare `loadData`; static content\n * can omit it and read only from `plugin` / `settings`.\n */\n settingsTabContent?: {\n loadData?: (db: any) => Promise<any>\n render: (props: { plugin: any; settings: any; data?: any }) => string\n }\n}\n\n/**\n * The runtime shape produced by {@link definePlugin}. Carries the legacy metadata\n * fields plus the v3 surfaces; structurally satisfies `MountablePlugin`,\n * `WirablePlugin`, and `CronablePlugin`.\n */\nexport interface DefinedPlugin {\n id: string\n name: string\n version: string\n /** The semver range for SonicJS core compatibility declared by the author. */\n sonicjsVersionRange?: string\n description?: string\n author?: { name: string; email?: string; url?: string }\n dependencies?: string[]\n capabilities: Capability[]\n routes?: MountableRoute[]\n register?: (app: Hono) => void\n /** Declarative hook subscriptions, flattened for the wire phase. */\n hooks?: WirableHook[]\n /** Wrapped onBoot accepting the runtime's raw boot context. */\n onBoot?: (context: PluginBootContext) => void | Promise<void>\n crons?: CronDeclaration[]\n /** Wrapped onCronTick accepting the runtime's raw cron context. */\n onCronTick?: (event: CronTickEvent, context: CronContext) => void | Promise<void>\n install?: (context: unknown) => void | Promise<void>\n uninstall?: (context: unknown) => void | Promise<void>\n activate?: (context: unknown) => void | Promise<void>\n deactivate?: (context: unknown) => void | Promise<void>\n /** Declarative admin sidebar entries. registerPlugins collects + sets the menu singleton. */\n menu?: PluginMenuEntry[]\n /** Schema-driven settings. Renders the admin settings form for this plugin. */\n configSchema?: ConfigSchema\n /** Custom settings-tab renderer. loadData runs server-side; render produces the tab HTML. */\n settingsTabContent?: {\n loadData?: (db: any) => Promise<any>\n render: (props: { plugin: any; settings: any; data?: any }) => string\n }\n /** Marker so tooling/tests can detect a v3-defined plugin. */\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n readonly __sonicV3: true\n}\n\n/**\n * Build the enriched context from whatever raw context the runtime passed.\n *\n * Capability providers ride on the raw context (`raw.providers`) — the host\n * supplies real `email`/`cache`/`http` factories there. When none are supplied, a\n * declared-but-used capability throws \"no provider supplied by host\", which is the\n * correct signal during early bring-up.\n */\nfunction enrich<Caps extends readonly Capability[]>(\n raw: PluginBootContext | CronContext,\n runtimeCaps: readonly Capability[],\n pluginName: string\n): DefinedPluginContext<Caps> {\n const providers = (raw as { providers?: CapabilityProviders }).providers ?? {}\n return {\n hooks: createTypedHooks(raw.hooks),\n // Runtime gating uses the normalized capability set; the context TYPE reflects\n // the declared `Caps` tuple (the narrowing the author sees).\n cap: createCapabilityContext(runtimeCaps as unknown as Caps, providers, pluginName),\n env: raw.env,\n raw,\n }\n}\n\n/**\n * Define a v3 plugin. Validates declared capabilities (warns on unknown), then\n * returns a runtime-ready plugin whose `onBoot`/`onCronTick` receive the enriched,\n * typed, capability-gated context. The `const Caps` type parameter captures the\n * declared capability tuple so `ctx.cap` is narrowed at the author's call site.\n */\nexport function definePlugin<const Caps extends readonly Capability[] = readonly []>(\n input: DefinePluginInput<Caps>\n): DefinedPlugin {\n if (!input.id) throw new Error('definePlugin: `id` is required')\n if (!input.version) throw new Error(`definePlugin: \\`version\\` is required (plugin \"${input.id}\")`)\n\n // Semver validation: warn if the plugin's own version is not a valid semver string.\n if (!isSemver(input.version)) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" has an invalid version: \"${input.version}\". ` +\n `Use a valid semver string (e.g. \"1.0.0\") to participate in version-range checks.`\n )\n }\n\n // SonicJS core compatibility range check.\n if (input.sonicjsVersionRange) {\n const coreVersion = getCoreVersion()\n if (!semverSatisfies(coreVersion, input.sonicjsVersionRange)) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" declares sonicjsVersionRange \"${input.sonicjsVersionRange}\" ` +\n `but running core version is \"${coreVersion}\". ` +\n `The plugin may not work correctly. Consider updating the plugin or the version range.`\n )\n }\n }\n\n // Normalize declared capabilities to canonical names first (resolves deprecated\n // / cross-fork spellings like `storage:write` → `media:write`), then warn on any\n // that remain unknown. Strict-reject at registration is layered in Phase 5d.\n const { capabilities, unknown } = normalizeCapabilities(input.capabilities ?? [])\n if (unknown.length > 0) {\n // eslint-disable-next-line no-console\n console.warn(\n `[plugins] Plugin \"${input.id}\" declares unknown capabilities: ${unknown.join(', ')}. ` +\n `These will gate nothing. Check for typos or update the capability vocabulary.`\n )\n }\n\n const name = input.id\n\n const onBoot = input.onBoot\n ? (raw: PluginBootContext) => input.onBoot!(enrich(raw, capabilities, name))\n : undefined\n\n const onCronTick = input.onCronTick\n ? (event: CronTickEvent, raw: CronContext) => input.onCronTick!(event, enrich(raw, capabilities, name))\n : undefined\n\n // Flatten the declarative typed `hooks` map into the wirable `hooks[]` array.\n // Each handler is wrapped to the raw (data, context) shape, coalescing a void\n // return back to the incoming payload (matching createTypedHooks().on()).\n const hooks: WirableHook[] | undefined = input.hooks\n ? Object.entries(input.hooks)\n .filter(([, h]) => typeof h === 'function')\n .map(([eventName, h]) => ({\n name: eventName,\n handler: async (data: any, context: any) => {\n const result = await (h as TypedHookHandler<HookEventName>)(data, context ?? {})\n return result === undefined ? data : result\n },\n }))\n : undefined\n\n return {\n id: input.id,\n name,\n version: input.version,\n sonicjsVersionRange: input.sonicjsVersionRange,\n description: input.description,\n author: input.author,\n dependencies: input.dependencies,\n capabilities,\n routes: input.routes,\n register: input.register,\n hooks,\n onBoot,\n crons: input.crons,\n onCronTick,\n install: input.install,\n uninstall: input.uninstall,\n activate: input.activate,\n deactivate: input.deactivate,\n menu: input.menu,\n configSchema: input.configSchema,\n settingsTabContent: input.settingsTabContent,\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n __sonicV3: true,\n }\n}\n\n/** True if `plugin` was produced by {@link definePlugin}. */\nexport function isDefinedPlugin(plugin: unknown): plugin is DefinedPlugin {\n // eslint-disable-next-line @typescript-eslint/naming-convention -- intentional internal marker\n return !!plugin && typeof plugin === 'object' && (plugin as { __sonicV3?: unknown }).__sonicV3 === true\n}\n","/**\n * Schema-driven plugin settings.\n *\n * Plugins declare `configSchema: { key: ConfigSchemaField }` on definePlugin.\n * The host renders the admin settings UI from the schema, parses FormData back\n * into typed values, and exposes the resulting record via `ctx.settings.load()`.\n *\n * Field kinds: 'string' | 'number' | 'boolean' | 'select'. New kinds get added\n * here once and every consumer picks them up.\n *\n * Phase 1 — settings UI only. The renderer emits plain HTML strings designed to\n * compose into the existing admin layout (no client-side JS). FormData parsing\n * coalesces unchecked-checkbox omission into `false` (the only browser quirk\n * the renderer must account for).\n */\n\nimport { escapeHtml } from '../../utils/sanitize'\n\n// ── Field model ──────────────────────────────────────────────────────────────\n\ninterface BaseField {\n label: string\n description?: string\n required?: boolean\n}\n\nexport interface StringField extends BaseField {\n type: 'string'\n default?: string\n format?: 'email' | 'url' | 'password'\n /** Render as `<input type=\"password\">`. Implied when `format === 'password'`. */\n sensitive?: boolean\n placeholder?: string\n minLength?: number\n maxLength?: number\n}\n\nexport interface NumberField extends BaseField {\n type: 'number'\n default?: number\n min?: number\n max?: number\n step?: number\n}\n\nexport interface BooleanField extends BaseField {\n type: 'boolean'\n default?: boolean\n}\n\nexport interface SelectField extends BaseField {\n type: 'select'\n default?: string\n /** Either `['us','eu']` shorthand or `[{ value, label }]` for distinct display strings. */\n options: readonly string[] | readonly { value: string; label: string }[]\n}\n\nexport type ConfigSchemaField = StringField | NumberField | BooleanField | SelectField\n\nexport type ConfigSchema = Record<string, ConfigSchemaField>\n\n/** Parsed shape — typed record inferred from the schema. */\nexport type SettingsFor<S extends ConfigSchema> = {\n [K in keyof S]: S[K] extends StringField\n ? string\n : S[K] extends NumberField\n ? number\n : S[K] extends BooleanField\n ? boolean\n : S[K] extends SelectField\n ? string\n : never\n}\n\n// ── Parse ────────────────────────────────────────────────────────────────────\n\nexport interface ParsedField {\n key: string\n field: ConfigSchemaField\n}\n\n/** Stable, ordered field list (preserves declaration order). */\nexport function parseConfigSchema(schema: ConfigSchema): ParsedField[] {\n return Object.entries(schema).map(([key, field]) => ({ key, field }))\n}\n\n// ── Render ───────────────────────────────────────────────────────────────────\n\n/**\n * Render all fields as HTML controls. Output is a sequence of `<div class=\"field\">…</div>`\n * blocks designed to drop into the admin form template (no surrounding `<form>` or\n * submit button — those belong to the page that calls this).\n */\nexport function renderSchemaFields(\n schema: ConfigSchema,\n currentValues: Record<string, unknown> = {}\n): string {\n return parseConfigSchema(schema)\n .map(({ key, field }) => {\n const value = key in currentValues ? currentValues[key] : (field as { default?: unknown }).default\n return renderField(key, field, value)\n })\n .join('\\n')\n}\n\nfunction renderField(key: string, field: ConfigSchemaField, value: unknown): string {\n const id = `field-${key}`\n const requiredAttr = field.required ? ' required' : ''\n const labelHtml = `${escapeHtml(field.label)}${field.required ? ' *' : ''}`\n const helpHtml = field.description ? `<p class=\"help text-sm text-gray-400 mt-1\">${escapeHtml(field.description)}</p>` : ''\n\n switch (field.type) {\n case 'string': {\n const isPassword = field.sensitive === true || field.format === 'password'\n const inputType = isPassword ? 'password' : field.format === 'email' ? 'email' : field.format === 'url' ? 'url' : 'text'\n const placeholder = field.placeholder ? ` placeholder=\"${escapeHtml(field.placeholder)}\"` : ''\n const min = field.minLength != null ? ` minlength=\"${field.minLength}\"` : ''\n const max = field.maxLength != null ? ` maxlength=\"${field.maxLength}\"` : ''\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <input id=\"${id}\" name=\"${escapeHtml(key)}\" type=\"${inputType}\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"\n value=\"${escapeHtml(String(value ?? ''))}\"${placeholder}${min}${max}${requiredAttr} />\n ${helpHtml}\n</div>`.trim()\n }\n case 'number': {\n const min = field.min != null ? ` min=\"${field.min}\"` : ''\n const max = field.max != null ? ` max=\"${field.max}\"` : ''\n const step = field.step != null ? ` step=\"${field.step}\"` : ''\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <input id=\"${id}\" name=\"${escapeHtml(key)}\" type=\"number\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"\n value=\"${value == null ? '' : escapeHtml(String(value))}\"${min}${max}${step}${requiredAttr} />\n ${helpHtml}\n</div>`.trim()\n }\n case 'boolean': {\n const checked = value === true ? ' checked' : ''\n return `\n<div class=\"field mb-4\">\n <label class=\"flex items-center gap-2 text-sm font-medium\">\n <input name=\"${escapeHtml(key)}\" type=\"checkbox\" class=\"rounded\"${checked} />\n ${labelHtml}\n </label>\n ${helpHtml}\n</div>`.trim()\n }\n case 'select': {\n const optionsHtml = field.options\n .map((o) => {\n const opt = typeof o === 'string' ? { value: o, label: o } : o\n const selected = opt.value === value ? ' selected' : ''\n return `<option value=\"${escapeHtml(opt.value)}\"${selected}>${escapeHtml(opt.label)}</option>`\n })\n .join('')\n return `\n<div class=\"field mb-4\">\n <label for=\"${id}\" class=\"block text-sm font-medium mb-1\">${labelHtml}</label>\n <select id=\"${id}\" name=\"${escapeHtml(key)}\"\n class=\"w-full rounded border border-gray-700 bg-gray-800 px-3 py-2 text-sm\"${requiredAttr}>\n ${optionsHtml}\n </select>\n ${helpHtml}\n</div>`.trim()\n }\n }\n}\n\n// ── FormData → settings ──────────────────────────────────────────────────────\n\n/**\n * Coerce FormData entries into typed settings per the schema. Notably:\n * - Unchecked checkboxes are omitted by the browser; we coalesce to `false`.\n * - Numbers parse via `Number()` and fall back to the field's default if invalid.\n * - Strings preserve empty string (NOT default) so an admin can intentionally clear.\n */\nexport function parseFormDataToSettings(schema: ConfigSchema, form: FormData): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n for (const { key, field } of parseConfigSchema(schema)) {\n switch (field.type) {\n case 'string': {\n const raw = form.get(key)\n result[key] = raw == null ? field.default ?? '' : String(raw)\n break\n }\n case 'number': {\n const raw = form.get(key)\n if (raw == null || raw === '') {\n result[key] = field.default\n } else {\n const n = Number(raw)\n result[key] = Number.isFinite(n) ? n : field.default\n }\n break\n }\n case 'boolean': {\n // Unchecked checkbox → not present in FormData → false.\n result[key] = form.get(key) != null\n break\n }\n case 'select': {\n const raw = form.get(key)\n result[key] = raw == null ? field.default ?? '' : String(raw)\n break\n }\n }\n }\n return result\n}\n\n// ── Defaults ─────────────────────────────────────────────────────────────────\n\n/** Fill missing keys with field defaults. Existing keys (incl. `false`/`0`/'') win. */\nexport function applySchemaDefaults<S extends ConfigSchema>(\n schema: S,\n stored: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...stored }\n for (const { key, field } of parseConfigSchema(schema)) {\n if (result[key] === undefined) {\n const def = (field as { default?: unknown }).default\n if (def !== undefined) result[key] = def\n }\n }\n return result\n}\n","/**\n * Plugin admin-sidebar menu registry.\n *\n * Plugins declare `menu: PluginMenuEntry[]` on definePlugin. registerPlugins\n * collects every entry into this module singleton; the admin layout reads via\n * `resolvePluginMenuItems(user)` to render the sidebar.\n *\n * Phase 1 — strings + paths only. No custom React/HTMX components in menu\n * entries (icons are name strings looked up by the sidebar's icon map).\n */\n\nexport interface PluginMenuEntry {\n /** Display label. */\n label: string\n /** Admin URL the entry navigates to (e.g. `/admin/email`). */\n path: string\n /** Icon name from the admin icon map. Default: `puzzle-piece`. */\n icon?: string\n /** Sort key (ASC). Default 100. */\n order?: number\n /** Required permission slugs; if any matches the user's permissions, the entry is shown. */\n permissions?: readonly string[]\n /** Plugin ID — set automatically by registerPlugins. Used to DB-check active status. */\n pluginId?: string\n}\n\n/**\n * Resolved entry — guaranteed `icon` + `order`, ready to render. Same shape\n * the catalyst sidebar consumes via `dynamicMenuItems`. `icon` is the rendered\n * SVG HTML string (resolved via {@link MENU_ICON_MAP} when the entry supplied\n * a name; passed through verbatim when the entry supplied raw SVG/HTML).\n */\nexport interface ResolvedPluginMenuEntry {\n label: string\n path: string\n icon: string\n order: number\n pluginId?: string\n}\n\n// ── Icon name → SVG map ──────────────────────────────────────────────────────\n//\n// Plugins declare icons by name (`icon: 'envelope'`); the layout never has to\n// import a per-plugin SVG. Names match heroicons-mini style. Unknown names\n// fall through to the generic puzzle-piece. Names containing `<svg` are\n// treated as raw SVG and passed through unchanged (escape hatch for one-off\n// custom icons).\n\nconst PUZZLE_PIECE = `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M14.5 2A1.5 1.5 0 0013 3.5V4H8.5A1.5 1.5 0 007 5.5V10h-.5a1.5 1.5 0 100 3H7v4.5A1.5 1.5 0 008.5 19H13v-.5a1.5 1.5 0 113 0V19h.5a1.5 1.5 0 001.5-1.5V13h-.5a1.5 1.5 0 110-3h.5V5.5A1.5 1.5 0 0017 4h-.5v-.5A1.5 1.5 0 0014.5 2z\"/></svg>`\n\nconst MENU_ICON_MAP: Readonly<Record<string, string>> = {\n 'puzzle-piece': PUZZLE_PIECE,\n envelope: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z\"/><path d=\"M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z\"/></svg>`,\n cog: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path fill-rule=\"evenodd\" d=\"M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z\" clip-rule=\"evenodd\"/></svg>`,\n chart: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z\"/></svg>`,\n sparkles: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path d=\"M9 4.804A7.968 7.968 0 005.5 4c-1.255 0-2.443.29-3.5.804v10A7.969 7.969 0 015.5 14c1.669 0 3.218.51 4.5 1.385A7.962 7.962 0 0114.5 14c1.255 0 2.443.29 3.5.804v-10A7.968 7.968 0 0014.5 4c-1.255 0-2.443.29-3.5.804V12a1 1 0 11-2 0V4.804z\"/></svg>`,\n bolt: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path fill-rule=\"evenodd\" d=\"M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z\" clip-rule=\"evenodd\"/></svg>`,\n document: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path fill-rule=\"evenodd\" d=\"M4 4a2 2 0 012-2h8a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm3 1h6v2H7V5zm0 4h6v2H7V9zm0 4h4v2H7v-2z\" clip-rule=\"evenodd\"/></svg>`,\n lock: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path fill-rule=\"evenodd\" d=\"M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z\" clip-rule=\"evenodd\"/></svg>`,\n photo: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\"><path fill-rule=\"evenodd\" d=\"M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z\" clip-rule=\"evenodd\"/></svg>`,\n 'globe-alt': `<svg class=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9\"/></svg>`,\n}\n\nfunction resolveIcon(name: string | undefined): string {\n if (!name) return PUZZLE_PIECE\n if (name.startsWith('<svg')) return name // raw SVG escape hatch\n return MENU_ICON_MAP[name] ?? PUZZLE_PIECE\n}\n\nlet menuItems: readonly PluginMenuEntry[] = []\n\nexport function setPluginMenu(items: readonly PluginMenuEntry[]): void {\n menuItems = [...items]\n}\n\nexport function getPluginMenu(): readonly PluginMenuEntry[] {\n return menuItems\n}\n\nexport function resetPluginMenu(): void {\n menuItems = []\n}\n\n/**\n * Filter entries by user permissions, sort by `order` ASC, project to render-ready\n * shape with default icon. Pure function — same inputs, same output.\n *\n * Permission semantics: an entry with no `permissions` is always visible. An entry\n * with `permissions: ['x','y']` is visible if the user has ANY of `x` or `y`.\n * Users with `role: 'admin'` bypass all permission checks (admins see everything).\n */\nexport function resolvePluginMenuItems(\n user?: { permissions?: readonly string[]; role?: string }\n): ResolvedPluginMenuEntry[] {\n const isAdmin = user?.role === 'admin'\n const userPerms = new Set(user?.permissions ?? [])\n return menuItems\n .filter((m) => {\n if (!m.permissions || m.permissions.length === 0) return true\n if (isAdmin) return true\n return m.permissions.some((p) => userPerms.has(p))\n })\n .slice()\n .sort((a, b) => (a.order ?? 100) - (b.order ?? 100))\n .map((m) => ({\n label: m.label,\n path: m.path,\n icon: resolveIcon(m.icon),\n order: m.order ?? 100,\n pluginId: m.pluginId,\n }))\n}\n","/**\n * In-memory registry of registered plugin definitions, indexed by id.\n *\n * Populated by `registerPlugins()` at app construction. The admin settings\n * route uses it to look up a plugin's `configSchema` so the schema-driven\n * settings form can be auto-rendered without per-plugin glue code.\n *\n * Read by the admin layer; written ONLY by registerPlugins.\n */\n\nimport type { RegisterablePlugin } from '../plugins/sdk/register-plugins'\n\nlet registry: ReadonlyMap<string, RegisterablePlugin> = new Map()\n\nexport function setPluginDefinitions(plugins: ReadonlyArray<RegisterablePlugin>): void {\n const next = new Map<string, RegisterablePlugin>()\n for (const p of plugins) next.set(p.id, p)\n registry = next\n}\n\nexport function getPluginDefinition(id: string): RegisterablePlugin | undefined {\n return registry.get(id)\n}\n\nexport function getAllPluginDefinitions(): ReadonlyArray<RegisterablePlugin> {\n return Array.from(registry.values())\n}\n\nexport function resetPluginDefinitions(): void {\n registry = new Map()\n}\n"]}
|
|
@@ -541,6 +541,12 @@ function renderAdminLayoutCatalyst(data) {
|
|
|
541
541
|
}
|
|
542
542
|
function renderCatalystSidebar(currentPath = "", user, dynamicMenuItems, isMobile = false, version, enableExperimentalFeatures) {
|
|
543
543
|
const resolvedMenuItems = dynamicMenuItems ?? [];
|
|
544
|
+
const userRole = user?.role ?? "viewer";
|
|
545
|
+
const isAdmin = userRole === "admin";
|
|
546
|
+
const isEditor = userRole === "editor";
|
|
547
|
+
const canManageUsers = isAdmin;
|
|
548
|
+
const canAccessSettings = isAdmin || isEditor;
|
|
549
|
+
const canAccessPlugins = isAdmin;
|
|
544
550
|
let baseMenuItems = [
|
|
545
551
|
{
|
|
546
552
|
label: "Content",
|
|
@@ -556,13 +562,13 @@ function renderCatalystSidebar(currentPath = "", user, dynamicMenuItems, isMobil
|
|
|
556
562
|
<path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"/>
|
|
557
563
|
</svg>`
|
|
558
564
|
},
|
|
559
|
-
{
|
|
565
|
+
...canManageUsers ? [{
|
|
560
566
|
label: "Users",
|
|
561
567
|
path: "/admin/users",
|
|
562
568
|
icon: `<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
563
569
|
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
|
|
564
570
|
</svg>`
|
|
565
|
-
}
|
|
571
|
+
}] : []
|
|
566
572
|
];
|
|
567
573
|
const pluginsIcon = `<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
568
574
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
|
@@ -644,7 +650,7 @@ function renderCatalystSidebar(currentPath = "", user, dynamicMenuItems, isMobil
|
|
|
644
650
|
</span>
|
|
645
651
|
`;
|
|
646
652
|
}).join("")}
|
|
647
|
-
|
|
653
|
+
${canAccessPlugins ? `<!-- Plugins accordion -->
|
|
648
654
|
<div data-plugins-accordion class="relative">
|
|
649
655
|
${isPluginsActive ? '<span class="absolute inset-y-2 -left-4 w-0.5 rounded-full bg-cyan-500 dark:bg-cyan-400"></span>' : ""}
|
|
650
656
|
<div class="flex w-full items-center">
|
|
@@ -671,7 +677,7 @@ function renderCatalystSidebar(currentPath = "", user, dynamicMenuItems, isMobil
|
|
|
671
677
|
<div data-plugins-submenu class="pl-6 mt-0.5 flex flex-col gap-0.5">
|
|
672
678
|
${pluginsSubItems}
|
|
673
679
|
</div>
|
|
674
|
-
</div
|
|
680
|
+
</div>` : ""}
|
|
675
681
|
<!-- /ADMIN_SIDEBAR_NAV_ITEMS -->
|
|
676
682
|
</div>
|
|
677
683
|
</div>
|
|
@@ -717,7 +723,7 @@ function renderCatalystSidebar(currentPath = "", user, dynamicMenuItems, isMobil
|
|
|
717
723
|
</div>
|
|
718
724
|
</div>
|
|
719
725
|
|
|
720
|
-
|
|
726
|
+
${canAccessSettings ? `<!-- Settings Menu Item (Bottom) -->
|
|
721
727
|
<div class="border-t border-zinc-950/5 p-4 dark:border-white/5">
|
|
722
728
|
${(() => {
|
|
723
729
|
const isActive = currentPath === settingsMenuItem.path || currentPath?.startsWith(settingsMenuItem.path);
|
|
@@ -739,7 +745,7 @@ function renderCatalystSidebar(currentPath = "", user, dynamicMenuItems, isMobil
|
|
|
739
745
|
</span>
|
|
740
746
|
`;
|
|
741
747
|
})()}
|
|
742
|
-
</div
|
|
748
|
+
</div>` : ""}
|
|
743
749
|
|
|
744
750
|
<!-- Sidebar Footer (User) -->
|
|
745
751
|
${user ? `
|
|
@@ -814,5 +820,5 @@ exports.renderAdminLayoutCatalyst = renderAdminLayoutCatalyst;
|
|
|
814
820
|
exports.renderCatalystCheckbox = renderCatalystCheckbox;
|
|
815
821
|
exports.renderLogo = renderLogo;
|
|
816
822
|
exports.setBranchLabel = setBranchLabel;
|
|
817
|
-
//# sourceMappingURL=chunk-
|
|
818
|
-
//# sourceMappingURL=chunk-
|
|
823
|
+
//# sourceMappingURL=chunk-MI2YXCK4.cjs.map
|
|
824
|
+
//# sourceMappingURL=chunk-MI2YXCK4.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/templates/components/logo.template.ts","../src/templates/layouts/admin-layout-catalyst.template.ts"],"names":["__esm","__export"],"mappings":"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","file":"chunk-MI2YXCK4.cjs","sourcesContent":["export interface LogoData {\n size?: 'sm' | 'md' | 'lg' | 'xl'\n variant?: 'default' | 'white' | 'dark'\n showText?: boolean\n showVersion?: boolean\n version?: string\n className?: string\n href?: string // Optional link URL\n}\n\nconst sizeClasses = {\n sm: 'h-6 w-auto',\n md: 'h-8 w-auto',\n lg: 'h-12 w-auto',\n xl: 'h-16 w-auto'\n}\n\n\nexport function renderLogo(data: LogoData = {}): string {\n const {\n size = 'md',\n variant = 'default',\n showText = true,\n showVersion = true,\n version,\n className = '',\n href\n } = data\n\n const sizeClass = sizeClasses[size]\n\n // Official SonicJS logo SVG from sonicjs.com\n const logoSvg = `\n <svg class=\"${sizeClass} ${className}\" viewBox=\"380 1300 2250 400\" aria-hidden=\"true\">\n <path fill=\"${variant === 'white' ? '#ffffff' : variant === 'dark' ? '#1f2937' : '#F1F2F2'}\" d=\"M476.851,1404.673h168.536c4.714,0,8.695-1.618,11.944-4.866c3.241-3.241,4.866-7.222,4.866-11.943 c0-2.357-0.443-4.569-1.327-6.636c-0.885-2.06-2.067-3.829-3.539-5.308c-1.479-1.472-3.249-2.654-5.308-3.538 c-2.067-0.885-4.279-1.327-6.635-1.327H476.851c-20.057,0-37.158,7.154-51.313,21.454c-14.155,14.308-21.233,31.483-21.233,51.534 c0,20.058,7.078,37.234,21.233,51.534c14.155,14.308,31.255,21.454,51.313,21.454h112.357c10.907,0,20.196,3.837,27.868,11.502 c7.666,7.672,11.502,16.885,11.502,27.646c0,10.769-3.836,19.982-11.502,27.647c-7.672,7.673-16.961,11.502-27.868,11.502H421.115 c-4.721,0-8.702,1.624-11.944,4.865c-3.248,3.249-4.866,7.23-4.866,11.944c0,3.248,0.733,6.123,2.212,8.626 c1.472,2.509,3.462,4.499,5.971,5.972c2.502,1.472,5.378,2.212,8.626,2.212h168.094c20.052,0,37.227-7.078,51.534-21.234 c14.3-14.155,21.454-31.331,21.454-51.534c0-20.196-7.154-37.379-21.454-51.534c-14.308-14.156-31.483-21.234-51.534-21.234 H476.851c-10.616,0-19.76-3.905-27.426-11.721c-7.672-7.811-11.501-17.101-11.501-27.87c0-10.761,3.829-19.975,11.501-27.647 C457.091,1408.508,466.235,1404.673,476.851,1404.673z\"></path>\n <path fill=\"${variant === 'white' ? '#ffffff' : variant === 'dark' ? '#1f2937' : '#F1F2F2'}\" d=\"M974.78,1398.211c-5.016,6.574-10.034,13.146-15.048,19.721c-1.828,2.398-3.657,4.796-5.487,7.194 c1.994,1.719,3.958,3.51,5.873,5.424c18.724,18.731,28.089,41.216,28.089,67.459c0,26.251-9.366,48.658-28.089,67.237 c-18.731,18.579-41.215,27.868-67.459,27.868c-9.848,0-19.156-1.308-27.923-3.923l-4.185,3.354 c-8.587,6.885-17.154,13.796-25.725,20.702c17.52,8.967,36.86,13.487,58.054,13.487c35.533,0,65.91-12.608,91.124-37.821 c25.214-25.215,37.821-55.584,37.821-91.125c0-35.534-12.607-65.911-37.821-91.126 C981.004,1403.663,977.926,1400.854,974.78,1398.211z\"></path>\n <path fill=\"${variant === 'white' ? '#ffffff' : variant === 'dark' ? '#1f2937' : '#F1F2F2'}\" d=\"M1364.644,1439.619c-4.72,0-8.702,1.624-11.943,4.865c-3.249,3.249-4.866,7.23-4.866,11.944v138.014 l-167.651-211.003c-0.297-0.586-0.74-1.03-1.327-1.326c-4.721-4.714-10.249-7.742-16.588-9.069 c-6.346-1.326-12.608-0.732-18.801,1.77c-6.192,2.509-11.059,6.49-14.598,11.944c-3.539,5.46-5.308,11.577-5.308,18.357v208.348 c0,4.721,1.618,8.703,4.866,11.944c3.241,3.241,7.222,4.865,11.943,4.865c2.945,0,5.751-0.738,8.405-2.211 c2.654-1.472,4.713-3.463,6.193-5.971c1.473-2.503,2.212-5.378,2.212-8.627v-205.251l166.325,209.675 c2.06,2.654,4.423,4.865,7.078,6.635c5.308,3.829,11.349,5.75,18.137,5.75c5.308,0,10.464-1.182,15.482-3.538 c3.539-1.769,6.56-4.127,9.069-7.078c2.502-2.945,4.491-6.338,5.971-10.175c1.473-3.829,2.212-7.664,2.212-11.501v-141.552 c0-4.714-1.624-8.695-4.865-11.944C1373.339,1441.243,1369.359,1439.619,1364.644,1439.619z\"></path>\n <path fill=\"${variant === 'white' ? '#ffffff' : variant === 'dark' ? '#1f2937' : '#F1F2F2'}\" d=\"M1508.406,1432.983c-2.654-1.472-5.46-2.212-8.404-2.212c-4.721,0-8.703,1.7-11.944,5.087 c-3.249,3.395-4.865,7.3-4.865,11.723v163.228c0,4.721,1.616,8.702,4.865,11.943c3.241,3.249,7.223,4.866,11.944,4.866 c2.944,0,5.751-0.732,8.404-2.212c2.655-1.472,4.714-3.539,6.193-6.194c1.473-2.654,2.213-5.453,2.213-8.404V1447.58 c0-2.945-0.74-5.75-2.213-8.405C1513.12,1436.522,1511.06,1434.462,1508.406,1432.983z\"></path>\n <path fill=\"${variant === 'white' ? '#ffffff' : variant === 'dark' ? '#1f2937' : '#F1F2F2'}\" d=\"M1499.78,1367.957c-4.575,0-8.481,1.625-11.722,4.866c-3.249,3.249-4.865,7.23-4.865,11.943 c0,2.951,0.732,5.75,2.212,8.405c1.472,2.654,3.463,4.721,5.971,6.193c2.503,1.479,5.378,2.212,8.627,2.212 c4.423,0,8.328-1.618,11.721-4.865c3.387-3.243,5.088-7.224,5.088-11.944c0-4.713-1.701-8.694-5.088-11.943 C1508.33,1369.582,1504.349,1367.957,1499.78,1367.957z\"></path>\n <path fill=\"${variant === 'white' ? '#ffffff' : variant === 'dark' ? '#1f2937' : '#F1F2F2'}\" d=\"M1859.627,1369.727H1747.27c-35.388,0-65.69,12.607-90.904,37.821 c-25.213,25.215-37.82,55.591-37.82,91.125c0,35.54,12.607,65.911,37.82,91.125c25.215,25.215,55.516,37.821,90.904,37.821h56.178 c4.714,0,8.695-1.618,11.944-4.866c3.241-3.241,4.865-7.222,4.865-11.943c0-4.714-1.624-8.695-4.865-11.943 c-3.249-3.243-7.23-4.866-11.944-4.866h-56.178c-26.251,0-48.659-9.359-67.237-28.09c-18.579-18.723-27.868-41.207-27.868-67.459 c0-26.243,9.29-48.659,27.868-67.237c18.579-18.579,40.987-27.868,67.237-27.868h112.357c4.714,0,8.696-1.693,11.944-5.087 c3.241-3.387,4.865-7.368,4.865-11.943c0-4.569-1.624-8.475-4.865-11.723C1868.322,1371.351,1864.341,1369.727,1859.627,1369.727z \"></path>\n <path fill=\"#06b6d4\" d=\"M2219.256,1371.054h-112.357c-4.423,0-8.336,1.624-11.723,4.865c-3.393,3.249-5.087,7.23-5.087,11.944 c0,4.721,1.694,8.702,5.087,11.943c3.387,3.249,7.3,4.866,11.723,4.866h95.547v95.105c0,26.251-9.365,48.659-28.088,67.237 c-18.731,18.579-41.215,27.868-67.459,27.868c-26.251,0-48.659-9.289-67.237-27.868c-18.579-18.579-27.868-40.987-27.868-67.237 c0-4.713-1.701-8.771-5.088-12.165c-3.393-3.387-7.374-5.087-11.943-5.087c-4.575,0-8.481,1.7-11.722,5.087 c-3.249,3.393-4.865,7.451-4.865,12.165c0,35.388,12.607,65.69,37.82,90.904c25.215,25.213,55.584,37.82,91.126,37.82 c35.532,0,65.91-12.607,91.125-37.82c25.214-25.215,37.82-55.516,37.82-90.904v-111.915c0-4.714-1.624-8.695-4.865-11.944 C2227.951,1372.678,2223.971,1371.054,2219.256,1371.054z\"></path>\n <path fill=\"#06b6d4\" d=\"M2574.24,1502.875c-14.306-14.156-31.483-21.234-51.533-21.234H2410.35 c-10.617,0-19.762-3.829-27.426-11.501c-7.672-7.664-11.501-16.954-11.501-27.868c0-10.907,3.829-20.196,11.501-27.868 c7.664-7.664,16.809-11.501,27.426-11.501h112.357c4.714,0,8.695-1.617,11.944-4.866c3.241-3.241,4.865-7.222,4.865-11.943 c0-4.714-1.624-8.695-4.865-11.944c-3.249-3.241-7.23-4.865-11.944-4.865H2410.35c-20.058,0-37.158,7.154-51.313,21.454 c-14.156,14.308-21.232,31.483-21.232,51.534c0,20.058,7.077,37.234,21.232,51.534c14.156,14.308,31.255,21.454,51.313,21.454 h112.357c7.078,0,13.637,1.77,19.684,5.308c6.042,3.539,10.838,8.336,14.377,14.377c3.538,6.047,5.307,12.607,5.307,19.685 c0,10.616-3.835,19.76-11.501,27.425c-7.672,7.673-16.961,11.502-27.868,11.502h-168.094c-4.721,0-8.703,1.7-11.944,5.087 c-3.249,3.393-4.865,7.374-4.865,11.943c0,4.576,1.616,8.481,4.865,11.723c3.241,3.249,7.223,4.866,11.944,4.866h168.094 c20.051,0,37.227-7.078,51.533-21.234c14.302-14.155,21.454-31.331,21.454-51.534 C2595.695,1534.213,2588.542,1517.03,2574.24,1502.875z\"></path>\n <path fill=\"#06b6d4\" d=\"M854.024,1585.195l20.001-16.028c16.616-13.507,33.04-27.265,50.086-40.251 c1.13-0.861,2.9-1.686,2.003-3.516c-0.843-1.716-2.481-2.302-4.484-2.123c-8.514,0.765-17.016-0.538-25.537-0.353 c-1.124,0.024-2.768,0.221-3.163-1.25c-0.371-1.369,1.088-2.063,1.919-2.894c6.26-6.242,12.574-12.43,18.816-18.691 c9.303-9.327,18.565-18.714,27.851-28.066c1.848-1.859,3.701-3.713,5.549-5.572c2.655-2.661,5.309-5.315,7.958-7.982 c0.574-0.579,1.259-1.141,1.246-1.94c-0.004-0.257-0.078-0.538-0.254-0.853c-0.556-0.981-1.441-1.1-2.469-0.957 c-0.658,0.096-1.315,0.185-1.973,0.275c-3.844,0.538-7.689,1.076-11.533,1.608c-3.641,0.505-7.281,1.02-10.922,1.529 c-4.162,0.582-8.324,1.158-12.486,1.748c-1.142,0.161-2.409,1.662-3.354,0.508c-0.419-0.508-0.431-1.028-0.251-1.531 c0.269-0.741,0.957-1.441,1.387-2.021c3.414-4.58,6.882-9.124,10.356-13.662c1.74-2.272,3.48-4.544,5.214-6.822 c4.682-6.141,9.369-12.281,14.051-18.422c0.09-0.119,0.181-0.237,0.271-0.355c6.848-8.98,13.7-17.958,20.553-26.936 c0.488-0.64,0.977-1.28,1.465-1.92c2.159-2.828,4.315-5.658,6.476-8.486c4.197-5.501,8.454-10.954,12.67-16.442 c0.263-0.347,0.538-0.718,0.717-1.106c0.269-0.586,0.299-1.196-0.335-1.776c-0.825-0.753-1.8-0.15-2.595,0.419 c-0.67,0.472-1.333,0.957-1.955,1.489c-2.206,1.889-4.401,3.797-6.595,5.698c-3.958,3.438-7.922,6.876-11.976,10.194 c-2.443,2.003-4.865,4.028-7.301,6.038c-18.689-10.581-39.53-15.906-62.549-15.906c-35.54,0-65.911,12.607-91.125,37.82 c-25.214,25.215-37.821,55.592-37.821,91.126c0,35.54,12.607,65.91,37.821,91.125c4.146,4.146,8.445,7.916,12.87,11.381 c-9.015,11.14-18.036,22.277-27.034,33.429c-1.208,1.489-3.755,3.151-2.745,4.891c0.078,0.144,0.173,0.281,0.305,0.425 c1.321,1.429,3.492-1.303,4.933-2.457c6.673-5.333,13.333-10.685,19.982-16.042c3.707-2.984,7.417-5.965,11.124-8.952 c1.474-1.188,2.951-2.373,4.425-3.561c6.41-5.164,12.816-10.333,19.238-15.481L854.024,1585.195z M797.552,1498.009 c0-26.243,9.29-48.728,27.868-67.459c18.579-18.723,40.987-28.089,67.238-28.089c12.273,0,23.712,2.075,34.34,6.171 c-3.37,2.905-6.734,5.816-10.069,8.762c-6.075,5.351-12.365,10.469-18.667,15.564c-4.179,3.378-8.371,6.744-12.514,10.164 c-7.54,6.23-15.037,12.52-22.529,18.804c-7.091,5.955-14.182,11.904-21.19,17.949c-1.136,0.974-3.055,1.907-2.135,3.94 c0.831,1.836,2.774,1.417,4.341,1.578l12.145-0.599l14.151-0.698c1.031-0.102,2.192-0.257,2.89,0.632 c0.034,0.044,0.073,0.078,0.106,0.127c1.017,1.561-0.67,2.105-1.387,2.942c-6.308,7.318-12.616,14.637-18.978,21.907 c-8.161,9.339-16.353,18.649-24.544,27.958c-2.146,2.433-4.275,4.879-6.422,7.312c-1.034,1.172-2.129,2.272-1.238,3.922 c0.933,1.728,2.685,1.752,4.323,1.602c4.134-0.367,8.263-0.489,12.396-0.492c0.242,0,0.485-0.005,0.728-0.004 c2.711,0.009,5.422,0.068,8.134,0.145c2.582,0.074,5.166,0.165,7.752,0.249c0.275,1.62-0.879,2.356-1.62,3.259 c-1.333,1.626-2.667,3.247-4,4.867c-4.315,5.252-8.62,10.514-12.928,15.772c-3.562-2.725-7.007-5.733-10.324-9.051 C806.842,1546.667,797.552,1524.26,797.552,1498.009z\"></path>\n </svg>\n `\n\n const versionBadge = showVersion && version ? `\n <span class=\"inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ring-1 ring-inset ${\n variant === 'white'\n ? 'bg-white/10 text-white/80 ring-white/20'\n : 'bg-cyan-50 text-cyan-700 ring-cyan-700/10 dark:bg-cyan-500/10 dark:text-cyan-400 dark:ring-cyan-500/20'\n }\">\n ${version}\n </span>\n ` : ''\n\n const logoContent = showText ? `\n <div class=\"flex items-center gap-2 ${className}\">\n ${logoSvg}\n ${versionBadge}\n </div>\n ` : logoSvg\n\n // Wrap in link if href is provided\n if (href) {\n return `<a href=\"${href}\" class=\"inline-block hover:opacity-80 transition-opacity\">${logoContent}</a>`\n }\n\n return logoContent\n}","import { HtmlEscapedString } from \"hono/utils/html\";\nimport { renderLogo } from \"../components/logo.template\";\n\n// Dev-only branch label — set via setBranchLabel() at bootstrap when BRANCH_LABEL env var is present\nlet _branchLabel: string | undefined;\n\nexport function setBranchLabel(label: string | undefined): void {\n _branchLabel = label;\n}\n\nconst BRANCH_COLORS = [\n { bg: \"bg-rose-500\", text: \"text-white\" },\n { bg: \"bg-orange-500\", text: \"text-white\" },\n { bg: \"bg-amber-400\", text: \"text-zinc-900\" },\n { bg: \"bg-lime-500\", text: \"text-zinc-900\" },\n { bg: \"bg-emerald-500\", text: \"text-white\" },\n { bg: \"bg-teal-500\", text: \"text-white\" },\n { bg: \"bg-cyan-500\", text: \"text-zinc-900\" },\n { bg: \"bg-sky-500\", text: \"text-white\" },\n { bg: \"bg-blue-600\", text: \"text-white\" },\n { bg: \"bg-violet-500\", text: \"text-white\" },\n { bg: \"bg-fuchsia-500\", text: \"text-white\" },\n { bg: \"bg-pink-500\", text: \"text-white\" },\n];\n\nfunction branchColor(label: string): { bg: string; text: string } {\n let hash = 5381;\n for (let i = 0; i < label.length; i++) {\n hash = ((hash << 5) + hash) ^ label.charCodeAt(i);\n }\n return BRANCH_COLORS[Math.abs(hash) % BRANCH_COLORS.length] ?? { bg: \"bg-zinc-500\", text: \"text-white\" };\n}\n\n// Catalyst Checkbox Component (HTML implementation)\nexport interface CatalystCheckboxProps {\n id: string;\n name: string;\n checked?: boolean;\n disabled?: boolean;\n label?: string;\n description?: string;\n color?:\n | \"dark/zinc\"\n | \"dark/white\"\n | \"white\"\n | \"dark\"\n | \"zinc\"\n | \"blue\"\n | \"green\"\n | \"red\";\n className?: string;\n}\n\nexport function renderCatalystCheckbox(props: CatalystCheckboxProps): string {\n const {\n id,\n name,\n checked = false,\n disabled = false,\n label,\n description,\n color = \"dark/zinc\",\n className = \"\",\n } = props;\n\n const colorConfig = {\n \"dark/zinc\": {\n bg: \"#18181b\",\n border: \"#09090b\",\n check: \"#ffffff\",\n darkBg: \"#52525b\",\n },\n \"dark/white\": {\n bg: \"#18181b\",\n border: \"#09090b\",\n check: \"#ffffff\",\n darkBg: \"#ffffff\",\n darkCheck: \"#18181b\",\n },\n white: { bg: \"#ffffff\", border: \"#09090b\", check: \"#18181b\" },\n dark: { bg: \"#18181b\", border: \"#09090b\", check: \"#ffffff\" },\n zinc: { bg: \"#52525b\", border: \"#3f3f46\", check: \"#ffffff\" },\n blue: { bg: \"#2563eb\", border: \"#1d4ed8\", check: \"#ffffff\" },\n green: { bg: \"#16a34a\", border: \"#15803d\", check: \"#ffffff\" },\n red: { bg: \"#dc2626\", border: \"#b91c1c\", check: \"#ffffff\" },\n };\n\n const _config = colorConfig[color] || colorConfig[\"dark/zinc\"];\n\n const colorClasses = {\n \"dark/zinc\":\n \"peer-checked:bg-zinc-900 peer-checked:before:bg-zinc-900 dark:peer-checked:bg-zinc-600\",\n \"dark/white\":\n \"peer-checked:bg-zinc-900 peer-checked:before:bg-zinc-900 dark:peer-checked:bg-white\",\n white: \"peer-checked:bg-white peer-checked:before:bg-white\",\n dark: \"peer-checked:bg-zinc-900 peer-checked:before:bg-zinc-900\",\n zinc: \"peer-checked:bg-zinc-600 peer-checked:before:bg-zinc-600\",\n blue: \"peer-checked:bg-blue-600 peer-checked:before:bg-blue-600\",\n green: \"peer-checked:bg-green-600 peer-checked:before:bg-green-600\",\n red: \"peer-checked:bg-red-600 peer-checked:before:bg-red-600\",\n };\n\n const checkColor =\n color === \"dark/white\" ? \"dark:text-zinc-900\" : \"text-white\";\n\n const baseClasses = `\n relative isolate flex w-4 h-4 items-center justify-center rounded-[0.3125rem]\n before:absolute before:inset-0 before:-z-10 before:rounded-[calc(0.3125rem-1px)] before:bg-white before:shadow-sm\n dark:before:hidden\n dark:bg-white/5\n border border-zinc-950/15 peer-checked:border-transparent\n dark:border-white/15 dark:peer-checked:border-white/5\n peer-focus:outline peer-focus:outline-2 peer-focus:outline-offset-2 peer-focus:outline-blue-500\n peer-disabled:opacity-50\n peer-disabled:border-zinc-950/25 peer-disabled:bg-zinc-950/5\n dark:peer-disabled:border-white/20 dark:peer-disabled:bg-white/2.5\n `\n .trim()\n .replace(/\\s+/g, \" \");\n\n const checkIconClasses = `\n w-4 h-4 opacity-0 peer-checked:opacity-100 pointer-events-none\n `\n .trim()\n .replace(/\\s+/g, \" \");\n\n if (description) {\n // Field layout with description\n return `\n <div class=\"grid grid-cols-[1.125rem_1fr] gap-x-4 gap-y-1 sm:grid-cols-[1rem_1fr] ${className}\">\n <div class=\"col-start-1 row-start-1 mt-0.75 sm:mt-1\">\n <input\n type=\"checkbox\"\n id=\"${id}\"\n name=\"${name}\"\n ${checked ? \"checked\" : \"\"}\n ${disabled ? \"disabled\" : \"\"}\n class=\"peer sr-only\"\n />\n <label for=\"${id}\" class=\"inline-flex cursor-pointer\">\n <span class=\"${baseClasses} ${colorClasses[color] || colorClasses[\"dark/zinc\"]}\">\n <svg class=\"${checkIconClasses} ${checkColor}\" viewBox=\"0 0 14 14\" fill=\"none\" stroke=\"currentColor\">\n <path d=\"M3 8L6 11L11 3.5\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </label>\n </div>\n ${label ? `<label for=\"${id}\" class=\"col-start-2 row-start-1 text-sm/6 font-medium text-zinc-950 dark:text-white cursor-pointer\">${label}</label>` : \"\"}\n ${description ? `<p class=\"col-start-2 row-start-2 text-sm/6 text-zinc-500 dark:text-zinc-400\">${description}</p>` : \"\"}\n </div>\n `;\n } else {\n // Simple checkbox with optional label\n return `\n <label class=\"inline-flex items-center gap-3 cursor-pointer ${className}\">\n <input\n type=\"checkbox\"\n id=\"${id}\"\n name=\"${name}\"\n ${checked ? \"checked\" : \"\"}\n ${disabled ? \"disabled\" : \"\"}\n class=\"peer sr-only\"\n />\n <span class=\"${baseClasses} ${colorClasses[color] || colorClasses[\"dark/zinc\"]}\">\n <svg class=\"${checkIconClasses} ${checkColor}\" viewBox=\"0 0 14 14\" fill=\"none\" stroke=\"currentColor\">\n <path d=\"M3 8L6 11L11 3.5\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n ${label ? `<span class=\"text-sm/6 font-medium text-zinc-950 dark:text-white\">${label}</span>` : \"\"}\n </label>\n `;\n }\n}\n\nexport interface AdminLayoutCatalystData {\n title: string;\n pageTitle?: string;\n currentPath?: string;\n version?: string;\n enableExperimentalFeatures?: boolean;\n user?: {\n name: string;\n email: string;\n role: string;\n };\n scripts?: string[];\n styles?: string[];\n content: string | HtmlEscapedString;\n dynamicMenuItems?: Array<{\n label: string;\n path: string;\n icon: string;\n }>;\n}\n\nexport function renderAdminLayoutCatalyst(\n data: AdminLayoutCatalystData\n): string {\n return `<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${data.title} - SonicJS AI Admin</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\">\n\n <!-- Tailwind CSS -->\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <script>\n tailwind.config = {\n darkMode: 'class',\n theme: {\n extend: {\n colors: {\n zinc: {\n 50: '#fafafa',\n 100: '#f4f4f5',\n 200: '#e4e4e7',\n 300: '#d4d4d8',\n 400: '#a1a1aa',\n 500: '#71717a',\n 600: '#52525b',\n 700: '#3f3f46',\n 800: '#27272a',\n 900: '#18181b',\n 950: '#09090b'\n }\n }\n }\n }\n }\n </script>\n\n <!-- Additional Styles -->\n <style>\n @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');\n\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: 'Inter', system-ui, -apple-system, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n\n /* Custom scrollbar */\n ::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n }\n\n ::-webkit-scrollbar-track {\n background: #27272a;\n }\n\n ::-webkit-scrollbar-thumb {\n background: #52525b;\n border-radius: 4px;\n }\n\n ::-webkit-scrollbar-thumb:hover {\n background: #71717a;\n }\n\n /* Smooth transitions */\n * {\n transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;\n transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n transition-duration: 150ms;\n }\n .plugins-closed [data-plugins-submenu] { display: none; }\n </style>\n <!-- Restore plugins submenu state before first paint (no flash) -->\n <script>(function(){try{var c=document.cookie.split(';').reduce(function(v,p){var t=p.trim().split('=');return t[0]==='plugins_menu_open'?t[1]:v;},null);if(c==='0'){document.documentElement.classList.add('plugins-closed');document.addEventListener('DOMContentLoaded',function(){var ch=document.querySelector('[data-plugins-chevron]');if(ch)ch.classList.remove('rotate-180');});};}catch(e){}})();</script>\n\n <!-- Scripts -->\n <script src=\"https://unpkg.com/htmx.org@2.0.3\"></script>\n <script src=\"https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js\" defer></script>\n <script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js\"></script>\n\n <!-- CSRF: Auto-attach token to all HTMX and fetch requests -->\n <script>\n function getCsrfToken() {\n var cookie = document.cookie.split('; ')\n .find(function(row) { return row.startsWith('csrf_token='); });\n return cookie ? cookie.substring(cookie.indexOf('=') + 1) : '';\n }\n\n document.addEventListener('htmx:configRequest', function(event) {\n var token = getCsrfToken();\n if (token) {\n event.detail.headers['X-CSRF-Token'] = token;\n }\n });\n\n (function() {\n var originalFetch = window.fetch;\n window.fetch = function(url, options) {\n options = options || {};\n var method = (options.method || 'GET').toUpperCase();\n if (method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {\n options.headers = options.headers || {};\n if (options.headers instanceof Headers) {\n if (!options.headers.has('X-CSRF-Token')) {\n options.headers.set('X-CSRF-Token', getCsrfToken());\n }\n } else if (!Array.isArray(options.headers) && !options.headers['X-CSRF-Token']) {\n options.headers['X-CSRF-Token'] = getCsrfToken();\n }\n }\n return originalFetch.call(this, url, options);\n };\n })();\n\n // Inject _csrf hidden field into regular form submissions (non-HTMX)\n document.addEventListener('submit', function(event) {\n var form = event.target;\n if (!form || !form.tagName || form.tagName !== 'FORM') return;\n var method = (form.method || 'GET').toUpperCase();\n if (method === 'GET') return;\n if (form.hasAttribute('hx-post') || form.hasAttribute('hx-put') ||\n form.hasAttribute('hx-delete') || form.hasAttribute('hx-patch')) return;\n if (!form.querySelector('input[name=\"_csrf\"]')) {\n var input = document.createElement('input');\n input.type = 'hidden';\n input.name = '_csrf';\n input.value = getCsrfToken();\n form.appendChild(input);\n }\n });\n </script>\n\n ${\n data.styles\n ? data.styles\n .map((style) => `<link rel=\"stylesheet\" href=\"${style}\">`)\n .join(\"\\n \")\n : \"\"\n }\n ${\n data.scripts\n ? data.scripts\n .map((script) => `<script src=\"${script}\"></script>`)\n .join(\"\\n \")\n : \"\"\n }\n</head>\n<body class=\"min-h-screen bg-white dark:bg-zinc-900\">\n <div class=\"relative isolate flex min-h-svh w-full max-lg:flex-col lg:bg-zinc-100 dark:lg:bg-zinc-950\">\n <!-- Sidebar on desktop -->\n <div class=\"fixed inset-y-0 left-0 w-64 max-lg:hidden\">\n ${renderCatalystSidebar(\n data.currentPath,\n data.user,\n data.dynamicMenuItems,\n false,\n data.version,\n data.enableExperimentalFeatures\n )}\n </div>\n\n <!-- Mobile sidebar (hidden by default) -->\n <div id=\"mobile-sidebar-overlay\" class=\"fixed inset-0 bg-black/30 lg:hidden hidden z-40\" onclick=\"closeMobileSidebar()\"></div>\n <div id=\"mobile-sidebar\" class=\"fixed inset-y-0 left-0 w-80 transform -translate-x-full transition-transform duration-300 ease-in-out lg:hidden z-50\">\n ${renderCatalystSidebar(\n data.currentPath,\n data.user,\n data.dynamicMenuItems,\n true,\n data.version,\n data.enableExperimentalFeatures\n )}\n </div>\n\n <!-- Main content area -->\n <main class=\"flex flex-1 flex-col pb-2 lg:min-w-0 lg:pr-2 lg:pl-64\">\n <!-- Mobile header with menu toggle -->\n <header class=\"flex items-center px-4 py-2.5 lg:hidden border-b border-zinc-950/5 dark:border-white/5\">\n <button onclick=\"openMobileSidebar()\" class=\"relative flex items-center justify-center rounded-lg p-2 text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\" aria-label=\"Open navigation\">\n <svg class=\"h-5 w-5\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path d=\"M2 6.75C2 6.33579 2.33579 6 2.75 6H17.25C17.6642 6 18 6.33579 18 6.75C18 7.16421 17.6642 7.5 17.25 7.5H2.75C2.33579 7.5 2 7.16421 2 6.75ZM2 13.25C2 12.8358 2.33579 12.5 2.75 12.5H17.25C17.6642 12.5 18 12.8358 18 13.25C18 13.6642 17.6642 14 17.25 14H2.75C2.33579 14 2 13.6642 2 13.25Z\" />\n </svg>\n </button>\n <div class=\"ml-4 flex-1\">\n ${renderLogo({ size: \"sm\", showText: true, variant: \"white\", version: data.version, href: \"/admin/content\" })}\n </div>\n </header>\n\n <!-- Content -->\n <div class=\"grow p-6 lg:rounded-lg lg:bg-white lg:p-10 lg:shadow-sm lg:ring-1 lg:ring-zinc-950/5 dark:lg:bg-zinc-900 dark:lg:ring-white/10\">\n ${data.content}\n </div>\n </main>\n </div>\n\n <!-- Notification Container -->\n <div id=\"notification-container\" class=\"fixed top-4 right-4 z-50 space-y-2\"></div>\n\n <!-- Migration Warning Banner (hidden by default) -->\n <div id=\"migration-banner\" class=\"hidden fixed top-0 left-0 right-0 z-50 bg-amber-500 dark:bg-amber-600 shadow-lg\">\n <div class=\"max-w-7xl mx-auto px-4 py-3 sm:px-6 lg:px-8\">\n <div class=\"flex items-center justify-between flex-wrap\">\n <div class=\"flex items-center flex-1\">\n <span class=\"flex p-2 rounded-lg bg-amber-600 dark:bg-amber-700\">\n <svg class=\"h-5 w-5 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"/>\n </svg>\n </span>\n <div class=\"ml-3\">\n <p class=\"text-sm font-medium text-white\">\n <span id=\"migration-count\"></span> pending database migration(s) detected\n </p>\n <p class=\"text-xs text-amber-100 dark:text-amber-200 mt-1\">\n Run: <code class=\"bg-amber-700 dark:bg-amber-800 px-2 py-0.5 rounded font-mono text-white\">wrangler d1 migrations apply DB --local</code>\n </p>\n </div>\n </div>\n <div class=\"flex items-center gap-2\">\n <a href=\"/admin/settings/migrations\" class=\"text-xs font-semibold text-white hover:text-amber-100 underline\">\n View Details\n </a>\n <button onclick=\"closeMigrationBanner()\" class=\"p-1 rounded-md text-white hover:bg-amber-600 dark:hover:bg-amber-700 focus:outline-none focus:ring-2 focus:ring-white\">\n <svg class=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"/>\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n\n <script>\n // Mobile sidebar toggle\n function openMobileSidebar() {\n const sidebar = document.getElementById('mobile-sidebar');\n const overlay = document.getElementById('mobile-sidebar-overlay');\n sidebar.classList.remove('-translate-x-full');\n overlay.classList.remove('hidden');\n }\n\n function closeMobileSidebar() {\n const sidebar = document.getElementById('mobile-sidebar');\n const overlay = document.getElementById('mobile-sidebar-overlay');\n sidebar.classList.add('-translate-x-full');\n overlay.classList.add('hidden');\n }\n\n // User dropdown toggle\n function toggleUserDropdown() {\n const dropDowns = document.querySelectorAll('.userDropdown');\n dropDowns.forEach(dropdown => {\n dropdown.classList.toggle('hidden');\n });\n }\n\n // Close dropdown when clicking outside\n document.addEventListener('click', function(event) {\n const dropdowns = document.querySelectorAll('.userDropdown');\n const button = event.target.closest('[data-user-menu]');\n if (!button) {\n dropdowns.forEach(function(dropdown) {\n if (!dropdown.contains(event.target)) {\n dropdown.classList.add('hidden');\n }\n });\n }\n });\n\n // Show notification\n function showNotification(message, type = 'info') {\n const container = document.getElementById('notification-container');\n const notification = document.createElement('div');\n const colors = {\n success: 'bg-green-50 dark:bg-green-500/10 text-green-700 dark:text-green-400 ring-green-600/20 dark:ring-green-500/20',\n error: 'bg-red-50 dark:bg-red-500/10 text-red-700 dark:text-red-400 ring-red-600/20 dark:ring-red-500/20',\n warning: 'bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 ring-amber-600/20 dark:ring-amber-500/20',\n info: 'bg-blue-50 dark:bg-blue-500/10 text-blue-700 dark:text-blue-400 ring-blue-600/20 dark:ring-blue-500/20'\n };\n\n notification.className = \\`rounded-lg p-4 ring-1 \\${colors[type] || colors.info} max-w-sm shadow-lg\\`;\n notification.innerHTML = \\`\n <div class=\"flex items-center justify-between\">\n <span class=\"text-sm\">\\${message}</span>\n <button onclick=\"this.parentElement.parentElement.remove()\" class=\"ml-4 hover:opacity-70\">\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\"></path>\n </svg>\n </button>\n </div>\n \\`;\n\n container.appendChild(notification);\n\n // Auto remove after 5 seconds\n setTimeout(() => {\n if (notification.parentElement) {\n notification.remove();\n }\n }, 5000);\n }\n\n // Initialize dark mode\n if (localStorage.getItem('darkMode') === 'false') {\n document.documentElement.classList.remove('dark');\n }\n\n // Migration banner functions\n function closeMigrationBanner() {\n const banner = document.getElementById('migration-banner');\n if (banner) {\n banner.classList.add('hidden');\n // Store in session storage so it doesn't show again during this session\n sessionStorage.setItem('migrationBannerDismissed', 'true');\n }\n }\n\n // Check for pending migrations on page load\n async function checkPendingMigrations() {\n // Don't check if user dismissed the banner in this session\n if (sessionStorage.getItem('migrationBannerDismissed') === 'true') {\n return;\n }\n\n try {\n const response = await fetch('/admin/api/migrations/status');\n if (response.ok) {\n const data = await response.json();\n if (data.success && data.data && data.data.pendingMigrations > 0) {\n const banner = document.getElementById('migration-banner');\n const countElement = document.getElementById('migration-count');\n if (banner && countElement) {\n countElement.textContent = data.data.pendingMigrations;\n banner.classList.remove('hidden');\n }\n }\n }\n } catch (error) {\n console.error('Failed to check migration status:', error);\n }\n }\n\n // Check for pending migrations when the page loads\n document.addEventListener('DOMContentLoaded', checkPendingMigrations);\n\n // Docs dropdown toggle\n function toggleDocsDropdown() {\n const dropDowns = document.querySelectorAll('.docsDropdown');\n dropDowns.forEach(dropdown => {\n dropdown.classList.toggle('hidden');\n });\n }\n\n // Close docs dropdown when clicking outside\n document.addEventListener('click', function(event) {\n const dropdowns = document.querySelectorAll('.docsDropdown');\n const button = event.target.closest('[data-docs-menu]');\n if (!button) {\n dropdowns.forEach(function(dropdown) {\n if (!dropdown.contains(event.target)) {\n dropdown.classList.add('hidden');\n }\n });\n }\n });\n\n // Plugins accordion toggle\n function togglePluginsMenu(btn) {\n var isNowClosed = document.documentElement.classList.toggle('plugins-closed');\n var chevron = btn.querySelector('[data-plugins-chevron]');\n if (chevron) chevron.classList.toggle('rotate-180');\n document.cookie = 'plugins_menu_open=' + (isNowClosed ? '0' : '1') + ';path=/;max-age=31536000';\n }\n\n // Auto-expand plugins submenu if a sub-item is currently active (overrides closed cookie)\n document.addEventListener('DOMContentLoaded', function() {\n document.querySelectorAll('[data-plugins-submenu]').forEach(function(submenu) {\n if (submenu.querySelector('[data-current=\"true\"]')) {\n document.documentElement.classList.remove('plugins-closed');\n var accordion = submenu.closest('[data-plugins-accordion]');\n if (accordion) {\n var chevron = accordion.querySelector('[data-plugins-chevron]');\n if (chevron) chevron.classList.add('rotate-180');\n }\n }\n });\n });\n </script>\n</body>\n</html>`;\n}\n\nfunction renderCatalystSidebar(\n currentPath: string = \"\",\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- user shape varies across admin pages; permissions read narrowly below\n user?: any,\n dynamicMenuItems?: Array<{ label: string; path: string; icon: string }>,\n isMobile: boolean = false,\n version?: string,\n enableExperimentalFeatures?: boolean\n): string {\n // Fall back to the declarative plugin menu singleton when no explicit\n // Active plugin nav items injected by pluginMenuMiddleware via dynamicMenuItems.\n // When not provided, the marker below is replaced by the middleware post-render.\n const resolvedMenuItems = dynamicMenuItems ?? [];\n const userRole: string = user?.role ?? 'viewer';\n const isAdmin = userRole === 'admin';\n const isEditor = userRole === 'editor';\n const canManageUsers = isAdmin;\n const canAccessSettings = isAdmin || isEditor;\n const canAccessPlugins = isAdmin;\n\n let baseMenuItems = [\n {\n label: \"Content\",\n path: \"/admin/content\",\n icon: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fill-rule=\"evenodd\" d=\"M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z\" clip-rule=\"evenodd\"/>\n </svg>`,\n },\n {\n label: \"Collections\",\n path: \"/admin/collections\",\n icon: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z\"/>\n </svg>`,\n },\n ...(canManageUsers ? [{\n label: \"Users\",\n path: \"/admin/users\",\n icon: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path d=\"M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z\"/>\n </svg>`,\n }] : []),\n ];\n\n const pluginsIcon = `<svg class=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"/>\n </svg>`;\n\n const settingsMenuItem = {\n label: \"Settings\",\n path: \"/admin/settings\",\n icon: `<svg class=\"h-5 w-5\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fill-rule=\"evenodd\" d=\"M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z\" clip-rule=\"evenodd\"/>\n </svg>`,\n };\n\n const allMenuItems = [...baseMenuItems];\n\n const isPluginsActive =\n currentPath === \"/admin/plugins\" ||\n (currentPath?.startsWith(\"/admin/plugins\") ?? false);\n\n const pluginsSubItems =\n resolvedMenuItems && resolvedMenuItems.length > 0\n ? resolvedMenuItems\n .map((item) => {\n const isActive =\n currentPath === item.path ||\n (item.path !== \"/admin\" && currentPath?.startsWith(item.path));\n return `\n <span class=\"relative\">\n ${isActive ? '<span class=\"absolute inset-y-2 -left-4 w-0.5 rounded-full bg-cyan-500 dark:bg-cyan-400\"></span>' : \"\"}\n <a\n href=\"${item.path}\"\n class=\"flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left text-sm/5 font-medium ${\n isActive\n ? \"text-zinc-950 dark:text-white\"\n : \"text-zinc-600 hover:bg-zinc-950/5 dark:text-zinc-400 dark:hover:bg-white/5\"\n }\"\n ${isActive ? 'data-current=\"true\"' : \"\"}\n >\n <span class=\"shrink-0 ${isActive ? \"fill-zinc-950 dark:fill-white\" : \"fill-zinc-500 dark:fill-zinc-400\"}\">\n ${item.icon}\n </span>\n <span class=\"truncate\">${item.label}</span>\n </a>\n </span>`;\n })\n .join(\"\")\n : \"<!-- DYNAMIC_PLUGIN_MENU -->\";\n\n const closeButton = isMobile\n ? `\n <div class=\"-mb-3 px-4 pt-3\">\n <button onclick=\"closeMobileSidebar()\" class=\"relative flex w-full items-center gap-3 rounded-lg p-2 text-left text-base/6 font-medium text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5 sm:text-sm/5\" aria-label=\"Close navigation\">\n <svg class=\"h-5 w-5 shrink-0 fill-zinc-500 dark:fill-zinc-400\" viewBox=\"0 0 20 20\">\n <path d=\"M6.28 5.22a.75.75 0 0 0-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 1 0 1.06 1.06L10 11.06l3.72 3.72a.75.75 0 1 0 1.06-1.06L11.06 10l3.72-3.72a.75.75 0 0 0-1.06-1.06L10 8.94 6.28 5.22Z\" />\n </svg>\n <span>Close menu</span>\n </button>\n </div>\n `\n : \"\";\n\n return `\n <nav class=\"flex h-full min-h-0 flex-col bg-white shadow-sm ring-1 ring-zinc-950/5 dark:bg-zinc-900 dark:ring-white/10 ${\n isMobile ? \"is-mobile rounded-lg p-2 m-2\" : \"\"\n }\">\n ${closeButton}\n\n <!-- Sidebar Header -->\n <div class=\"flex w-full flex-col border-b border-zinc-950/5 p-4 dark:border-white/5\">\n ${renderLogo({ size: \"md\", showText: true, variant: \"white\", version, href: \"/admin/content\" })}\n ${_branchLabel ? (() => { const col = branchColor(_branchLabel!); const short = _branchLabel!.split('/').pop() || _branchLabel!; return `<div class=\"mt-2 flex items-center justify-center\"><span class=\"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${col.bg} ${col.text} max-w-full truncate\" title=\"${_branchLabel}\">${short}</span></div>`; })() : \"\"}\n </div>\n\n <!-- Tenant switcher (injected by tenantMiddleware when the multi-tenant plugin is active) -->\n <!-- TENANT_SWITCHER -->\n\n <!-- Sidebar Body -->\n <div class=\"flex flex-1 flex-col overflow-y-auto p-4\">\n <div class=\"flex flex-col gap-0.5\">\n <!-- ADMIN_SIDEBAR_NAV_ITEMS -->\n ${allMenuItems\n .map((item) => {\n const isActive =\n currentPath === item.path ||\n (item.path !== \"/admin\" && currentPath?.startsWith(item.path));\n return `\n <span class=\"relative\">\n ${\n isActive\n ? `\n <span class=\"absolute inset-y-2 -left-4 w-0.5 rounded-full bg-cyan-500 dark:bg-cyan-400\"></span>\n `\n : \"\"\n }\n <a\n href=\"${item.path}\"\n class=\"flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-sm/5 font-medium ${\n isActive\n ? \"text-zinc-950 dark:text-white\"\n : \"text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\"\n }\"\n ${isActive ? 'data-current=\"true\"' : \"\"}\n >\n <span class=\"shrink-0 ${\n isActive\n ? \"fill-zinc-950 dark:fill-white\"\n : \"fill-zinc-500 dark:fill-zinc-400\"\n }\">\n ${item.icon}\n </span>\n <span class=\"truncate\">${item.label}</span>\n </a>\n </span>\n `;\n })\n .join(\"\")}\n ${canAccessPlugins ? `<!-- Plugins accordion -->\n <div data-plugins-accordion class=\"relative\">\n ${isPluginsActive ? '<span class=\"absolute inset-y-2 -left-4 w-0.5 rounded-full bg-cyan-500 dark:bg-cyan-400\"></span>' : \"\"}\n <div class=\"flex w-full items-center\">\n <a\n href=\"/admin/plugins\"\n class=\"flex flex-1 items-center gap-3 rounded-lg px-2 py-2.5 text-left text-sm/5 font-medium ${\n isPluginsActive\n ? \"text-zinc-950 dark:text-white\"\n : \"text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\"\n }\"\n ${isPluginsActive ? 'data-current=\"true\"' : \"\"}\n >\n <span class=\"shrink-0 ${isPluginsActive ? \"fill-zinc-950 dark:fill-white\" : \"fill-zinc-500 dark:fill-zinc-400\"}\">\n ${pluginsIcon}\n </span>\n <span class=\"truncate\">Plugins</span>\n </a>\n <button\n onclick=\"togglePluginsMenu(this)\"\n class=\"flex items-center justify-center rounded-lg p-2 text-zinc-500 hover:bg-zinc-950/5 dark:text-zinc-400 dark:hover:bg-white/5 flex-shrink-0\"\n aria-label=\"Toggle plugins submenu\"\n >\n <svg data-plugins-chevron class=\"h-4 w-4 rotate-180 transition-transform duration-200\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"/>\n </svg>\n </button>\n </div>\n <div data-plugins-submenu class=\"pl-6 mt-0.5 flex flex-col gap-0.5\">\n ${pluginsSubItems}\n </div>\n </div>` : \"\"}\n <!-- /ADMIN_SIDEBAR_NAV_ITEMS -->\n </div>\n </div>\n\n <!-- Docs menu -->\n <div class=\"border-t border-zinc-950/5 p-4 dark:border-white/5\">\n <div class=\"relative\">\n <button\n data-docs-menu\n onclick=\"toggleDocsDropdown()\"\n class=\"flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-sm/5 font-medium text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\"\n >\n <svg class=\"h-5 w-5 shrink-0 fill-zinc-500 dark:fill-zinc-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25\"/>\n </svg>\n <span class=\"flex-1 truncate\">Docs</span>\n <svg class=\"h-4 w-4 shrink-0 fill-zinc-500 dark:fill-zinc-400\" viewBox=\"0 0 20 20\">\n <path d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\" />\n </svg>\n </button>\n <div class=\"docsDropdown hidden absolute bottom-full mb-2 left-0 right-0 mx-2 rounded-xl bg-white shadow-lg ring-1 ring-zinc-950/10 dark:bg-zinc-800 dark:ring-white/10 z-50\">\n <div class=\"p-2\">\n <a href=\"/admin/api-reference\" class=\"flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\">\n <svg class=\"h-4 w-4 shrink-0 text-zinc-500 dark:text-zinc-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25\"/>\n </svg>\n API Docs\n </a>\n <a href=\"https://sonicjs.com\" target=\"_blank\" class=\"flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\">\n <svg class=\"h-4 w-4 shrink-0 text-zinc-500 dark:text-zinc-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.26 10.147a60.436 60.436 0 00-.491 6.347A48.627 48.627 0 0112 20.904a48.627 48.627 0 018.232-4.41 60.46 60.46 0 00-.491-6.347m-15.482 0a50.57 50.57 0 00-2.658-.813A59.905 59.905 0 0112 3.493a59.902 59.902 0 0110.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.697 50.697 0 0112 13.489a50.702 50.702 0 017.74-3.342M6.75 15a.75.75 0 100-1.5.75.75 0 000 1.5zm0 0v-3.675A55.378 55.378 0 0112 8.443m-7.007 11.55A5.981 5.981 0 006.75 15.75v-1.5\"/>\n </svg>\n Developer Docs\n </a>\n <a href=\"/api\" target=\"_blank\" class=\"flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\">\n <svg class=\"h-4 w-4 shrink-0 text-zinc-500 dark:text-zinc-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5\"/>\n </svg>\n OpenAPI\n </a>\n </div>\n </div>\n </div>\n </div>\n\n ${canAccessSettings ? `<!-- Settings Menu Item (Bottom) -->\n <div class=\"border-t border-zinc-950/5 p-4 dark:border-white/5\">\n ${(() => {\n const isActive =\n currentPath === settingsMenuItem.path ||\n currentPath?.startsWith(settingsMenuItem.path);\n return `\n <span class=\"relative\">\n ${\n isActive\n ? `\n <span class=\"absolute inset-y-2 -left-4 w-0.5 rounded-full bg-cyan-500 dark:bg-cyan-400\"></span>\n `\n : \"\"\n }\n <a\n href=\"${settingsMenuItem.path}\"\n class=\"flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-sm/5 font-medium ${\n isActive\n ? \"text-zinc-950 dark:text-white\"\n : \"text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\"\n }\"\n ${isActive ? 'data-current=\"true\"' : \"\"}\n >\n <span class=\"shrink-0 ${\n isActive\n ? \"fill-zinc-950 dark:fill-white\"\n : \"fill-zinc-500 dark:fill-zinc-400\"\n }\">\n ${settingsMenuItem.icon}\n </span>\n <span class=\"truncate\">${settingsMenuItem.label}</span>\n </a>\n </span>\n `;\n })()}\n </div>` : \"\"}\n\n <!-- Sidebar Footer (User) -->\n ${\n user\n ? `\n <div class=\"flex flex-col border-t border-zinc-950/5 p-4 dark:border-white/5\">\n <div class=\"relative\">\n <button\n data-user-menu\n onclick=\"toggleUserDropdown()\"\n class=\"flex w-full items-center gap-3 rounded-lg px-2 py-2.5 text-left text-sm/5 font-medium text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\"\n >\n <div class=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-zinc-950 text-white dark:bg-white dark:text-zinc-950\">\n <span class=\"text-xs font-semibold\">${(\n user.name ||\n user.email ||\n \"U\"\n )\n .charAt(0)\n .toUpperCase()}</span>\n </div>\n <span class=\"flex-1 truncate\">${\n user.name || user.email || \"User\"\n }</span>\n <svg class=\"h-4 w-4 shrink-0 fill-zinc-500 dark:fill-zinc-400\" viewBox=\"0 0 20 20\">\n <path d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\" />\n </svg>\n </button>\n\n <!-- User Dropdown -->\n <div class=\"userDropdown hidden absolute bottom-full mb-2 left-0 right-0 mx-2 rounded-xl bg-white shadow-lg ring-1 ring-zinc-950/10 dark:bg-zinc-800 dark:ring-white/10 z-50\">\n <div class=\"p-2\">\n <div class=\"px-3 py-2 border-b border-zinc-950/5 dark:border-white/5\">\n <p class=\"text-sm font-medium text-zinc-950 dark:text-white\">${\n user.name || user.email || \"User\"\n }</p>\n <p class=\"text-xs text-zinc-500 dark:text-zinc-400\">${\n user.email || \"\"\n }</p>\n </div>\n <a href=\"/admin/profile\" class=\"flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-zinc-950 hover:bg-zinc-950/5 dark:text-white dark:hover:bg-white/5\">\n <svg class=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z\"/>\n </svg>\n My Profile\n </a>\n <a href=\"/auth/logout\" class=\"flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-500/10\">\n <svg class=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1\"/>\n </svg>\n Sign Out\n </a>\n </div>\n </div>\n </div>\n </div>\n `\n : \"\"\n }\n </nav>\n `;\n}\n"]}
|