create-kywi-app 0.7.0 → 0.8.0

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.
@@ -443,7 +443,15 @@ interchangeable.
443
443
  pin) and A/B experiments. Since 0.6.3 it also renders in the layout editor as
444
444
  a badged block with an **arm switcher**, each arm editable with the ordinary
445
445
  section/column/module tools, so server-resolution no longer costs the owner
446
- their editing surface.
446
+ their editing surface. Since kywi-cms#119, the owner doesn't need an agent to
447
+ hand-author this shape at all: the section chrome — in the admin layout
448
+ editor and in the in-place front-of-site overlay alike — offers
449
+ **Personalize this section** / **A/B test** actions that wrap an ordinary
450
+ section into a section-level variant container on the spot, seeding the
451
+ default arm from the section's current content and opening straight into its
452
+ config (audience/experiment picker included). An agent only needs to reach
453
+ for `update_layout` when scripting bulk changes or building a container the
454
+ UI can't reach.
447
455
  - **Module-level** — the `variantContainer` *module*, placed in a column; its
448
456
  arms are HTML strings (`defaultContent`, `variants: [{audienceId, label,
449
457
  content}]`). Every arm ships in the HTML (default visible, the rest
package/lib/templates.mjs CHANGED
@@ -1087,11 +1087,57 @@ export default function RootLayout({ children }: { children: React.ReactNode })
1087
1087
  `
1088
1088
  }
1089
1089
 
1090
+ /**
1091
+ * Client-side wrapper around core's SHARED nav renderer — the same component the
1092
+ * `navigation` module uses — so the site header and footer get identical markup,
1093
+ * CSS (`.kywi-nav-*`) and @kywi-software/js enhancement to any nav placed in a
1094
+ * page body. `items` is whatever app/(site)/layout.tsx resolved server-side (the
1095
+ * owner-managed menu, or the site tree as fallback); nothing is decided here.
1096
+ *
1097
+ * WHY THIS FILE EXISTS — it is `'use client'` on purpose, and the layout that
1098
+ * renders it is a Server Component. `BUILT_IN_MODULE_COMPONENTS` is exported
1099
+ * from one of core's `'use client'` modules, so a Server Component that imports
1100
+ * the map gets a client *reference*, not a real object:
1101
+ * `BUILT_IN_MODULE_COMPONENTS.navigation` reads back `undefined` there and React
1102
+ * throws "Element type is invalid… got: undefined" on every page (kywi-cms#112
1103
+ * follow-up). Crossing the boundary here, in a client module, is what makes the
1104
+ * lookup resolve to the real component — mirrors apps/reference/components/nav.tsx.
1105
+ */
1106
+ function siteNavComponent() {
1107
+ return `'use client'
1108
+
1109
+ import React from 'react'
1110
+ import { BUILT_IN_MODULE_COMPONENTS } from '@kywi-software/core/layout'
1111
+ import type { NavMenuItem } from '@kywi-software/core/nav'
1112
+
1113
+ // BUILT_IN_MODULE_COMPONENTS is a Record<string, …>, so a literal-key lookup is
1114
+ // typed as possibly-undefined under noUncheckedIndexedAccess; 'navigation' is
1115
+ // always present — it's one of core's own built-in modules, not user data.
1116
+ const NavRenderer = BUILT_IN_MODULE_COMPONENTS.navigation!
1117
+
1118
+ export interface SiteNavProps {
1119
+ items: NavMenuItem[]
1120
+ /** \`horizontal\`/\`mega\`/\`vertical\` for the header bar, \`footer\` for the footer columns. */
1121
+ variant: 'horizontal' | 'mega' | 'vertical' | 'footer'
1122
+ ariaLabel: string
1123
+ depth?: number
1124
+ }
1125
+
1126
+ /** Renders one resolved menu (header or footer) through core's shared nav renderer. */
1127
+ export function SiteNav({ items, variant, ariaLabel, depth }: SiteNavProps) {
1128
+ if (items.length === 0) return null
1129
+ return (
1130
+ <NavRenderer props={{ items, variant, ariaLabel, ...(depth ? { depth } : {}) }} />
1131
+ )
1132
+ }
1133
+ `
1134
+ }
1135
+
1090
1136
  /** @param {Answers} a — coupled public site shell. */
1091
1137
  function siteLayout(a) {
1092
1138
  return `import React from 'react'
1093
1139
  import { headers } from 'next/headers'
1094
- import { themeTokenStyleBlock, BUILT_IN_MODULE_COMPONENTS } from '@kywi-software/core/layout'
1140
+ import { themeTokenStyleBlock } from '@kywi-software/core/layout'
1095
1141
  import { navTreeToMenuItems, normalizePath } from '@kywi-software/core/nav'
1096
1142
  // Neutral, token-driven defaults for everything Kywi renders (prose, the layout
1097
1143
  // grid + modules, forms, the edit overlay). Restyle via theme tokens, not by
@@ -1101,6 +1147,7 @@ import '@kywi-software/core/site/styles.css'
1101
1147
  import './site.css'
1102
1148
  import config from '../../kywi.config'
1103
1149
  import { getKywi } from '../../lib/kywi'
1150
+ import { SiteNav } from '../../components/site-nav'
1104
1151
 
1105
1152
  /**
1106
1153
  * Public site shell. Header + footer carry this project's brand; edit them (and
@@ -1113,7 +1160,8 @@ import { getKywi } from '../../lib/kywi'
1113
1160
  * of the box — and the footer renders a \`'footer'\` menu when one exists. NEVER
1114
1161
  * hardcode this list: a menu item's href resolves from its linked page and
1115
1162
  * can never drift the way a pasted URL can (see AGENTS.md → Navigation).
1116
- * Rendered through core's shared nav renderer (\`BUILT_IN_MODULE_COMPONENTS\`)
1163
+ * Rendered through core's shared nav renderer via the generated
1164
+ * \`components/site-nav.tsx\` (a \`'use client'\` wrapper — see that file for why)
1117
1165
  * rather than bespoke markup, so it gets the same CSS and \`@kywi-software/js\`
1118
1166
  * enhancement as a \`navigation\` module placed in the page body.
1119
1167
  *
@@ -1126,8 +1174,6 @@ const siteTheme =
1126
1174
  config.themes.find((t) => t.name === config.sites[0]?.theme) ?? config.themes[0]
1127
1175
  const themeVars = themeTokenStyleBlock(siteTheme?.tokens)
1128
1176
 
1129
- const NavRenderer = BUILT_IN_MODULE_COMPONENTS.navigation
1130
-
1131
1177
  // Content edits (a renamed page, a reordered menu) must show up without a
1132
1178
  // restart — same reasoning as the page route.
1133
1179
  export const dynamic = 'force-dynamic'
@@ -1155,7 +1201,7 @@ export default async function SiteLayout({ children }: { children: React.ReactNo
1155
1201
  <a className="site-brand" href="/">${escapeJsxText(a.projectName)}</a>
1156
1202
  <div className="site-nav">
1157
1203
  {headerItems.length > 0 && (
1158
- <NavRenderer props={{ items: headerItems, variant: 'horizontal', ariaLabel: 'Primary', depth: 2 }} />
1204
+ <SiteNav items={headerItems} variant="horizontal" ariaLabel="Primary" depth={2} />
1159
1205
  )}
1160
1206
  <a className="site-nav__admin" href="/admin">Admin →</a>
1161
1207
  </div>
@@ -1167,7 +1213,7 @@ export default async function SiteLayout({ children }: { children: React.ReactNo
1167
1213
  <footer className="site-footer">
1168
1214
  <div className="site-footer__inner">
1169
1215
  {footerItems && footerItems.length > 0 && (
1170
- <NavRenderer props={{ items: footerItems, variant: 'footer', ariaLabel: 'Footer' }} />
1216
+ <SiteNav items={footerItems} variant="footer" ariaLabel="Footer" />
1171
1217
  )}
1172
1218
  <p className="site-footer__credit">Powered by <a href="https://kywi.dev">Kywi CMS</a></p>
1173
1219
  </div>
@@ -1513,6 +1559,7 @@ export default async function PublicPage({ params, searchParams }: Params & Sear
1513
1559
  initialLayout={layout ?? { regions: { main: [] } }}
1514
1560
  adminHref={\`/admin/content/\${contentType}/\${contentId}\`}
1515
1561
  moduleComponents={moduleComponents}
1562
+ hostModules={config.modules ?? []}
1516
1563
  >
1517
1564
  {content}
1518
1565
  </KywiFrontEdit>
@@ -1561,6 +1608,7 @@ import {
1561
1608
  BUILT_IN_MODULE_COMPONENTS,
1562
1609
  type LayoutDocument,
1563
1610
  type ModuleComponentMap,
1611
+ type ModuleConfig,
1564
1612
  } from '@kywi-software/core/layout'
1565
1613
  import type { SaveAction } from '@kywi-software/core/admin'
1566
1614
 
@@ -1592,6 +1640,10 @@ export interface KywiFrontEditProps {
1592
1640
  adminHref: string
1593
1641
  /** Custom (defineModule) renderers, shared with the public layout + admin (#48). */
1594
1642
  moduleComponents?: ModuleComponentMap
1643
+ /** Custom (defineModule) module CONFIGS from kywi.config.ts — gives the overlay
1644
+ * editor each custom module's prop definitions, so the props rail and inline
1645
+ * text editing work for them exactly as in the admin editor (kywi-cms#118). */
1646
+ hostModules?: ModuleConfig[]
1595
1647
  children: React.ReactNode
1596
1648
  }
1597
1649
 
@@ -1616,6 +1668,7 @@ export function KywiFrontEdit({
1616
1668
  initialLayout,
1617
1669
  adminHref,
1618
1670
  moduleComponents = {},
1671
+ hostModules = [],
1619
1672
  children,
1620
1673
  }: KywiFrontEditProps) {
1621
1674
  const edit = useKywiEditMode({ canEdit, canPublish })
@@ -1629,10 +1682,14 @@ export function KywiFrontEdit({
1629
1682
  if (editRequested) startEdit()
1630
1683
  }, [editRequested, startEdit])
1631
1684
 
1632
- // Registries + renderers for the editor: built from core (no module list is
1633
- // re-declared here) and merged with this app's custom module renderers from
1634
- // lib/modules the SAME source the admin app and public renderer use (#48).
1635
- const moduleRegistry = React.useMemo(() => createModuleRegistry([]), [])
1685
+ // Registries + renderers for the editor: the module registry carries each
1686
+ // module's prop definitions (built-ins + this app's kywi.config.ts modules
1687
+ // without the host list, custom modules render but are uneditable, kywi-cms#118),
1688
+ // merged with the custom renderers from lib/modules (#48).
1689
+ const moduleRegistry = React.useMemo(
1690
+ () => createModuleRegistry(hostModules ?? []),
1691
+ [hostModules],
1692
+ )
1636
1693
  const themeRegistry = React.useMemo(() => createThemeRegistry(), [])
1637
1694
  const editorComponents = React.useMemo(
1638
1695
  () => ({ ...BUILT_IN_MODULE_COMPONENTS, ...moduleComponents }),
@@ -2169,10 +2226,10 @@ leaving the editor keeps \`?kywi-edit=1\` in the URL in sync, so reloading (or
2169
2226
  sharing the link) lands back in the same mode. It all lives in
2170
2227
  \`app/(site)/kywi-front-edit.tsx\`; the editor bundle (and the admin stylesheet) is
2171
2228
  lazy-loaded, so pages your visitors see never carry its weight — an anonymous
2172
- or read-only visitor gets no toolbar and no extra client JS. Note: custom
2173
- \`defineModule\` types still RENDER on the canvas, but they don't appear in the
2174
- front-of-site editor's insert palette (it uses the built-in module set) add
2175
- them from the admin's Layout tab instead.
2229
+ or read-only visitor gets no toolbar and no extra client JS. Custom
2230
+ \`defineModule\` types from \`kywi.config.ts\` also appear in the front-of-site
2231
+ editor's insert palette and are fully editable in placethe props rail and
2232
+ inline text editing work exactly as they do in the admin's Layout tab.
2176
2233
 
2177
2234
  ## Personalization, A/B testing & self-ID
2178
2235
 
@@ -2292,7 +2349,7 @@ lib/config.ts single import path for kywi.config.ts
2292
2349
  app/api/v1/[...kywi]/route.ts the versioned API (delegates to core)
2293
2350
  app/admin/[[...admin]]/page.tsx mounts the FULL core admin (all surfaces) at /admin
2294
2351
  lib/modules.tsx custom (defineModule) module renderers (admin + public)
2295
- app/{llms,robots,sitemap,…} root AX routes (llms.txt, robots.txt, sitemap.xml, …)${a.mode === 'coupled' ? '\napp/(site)/layout.tsx public shell: theme tokens + your header/footer\napp/(site)/site.css your site chrome styles (edit freely)\napp/(site)/[[...slug]]/page.tsx renders published pages (layout + SEO + JSON-LD + i18n)\nlib/site.ts public-render helpers: path/locale resolution, feeds, personalization\ncomponents/personalization-runtime.tsx optional client runtime (self-ID widget, live re-eval)\napp/(site)/kywi-front-edit.tsx front-of-site editor: browse toolbar + in-place Layout editor (?kywi-edit=1, lazy-loaded)' : '\napp/page.tsx returns 404 (no public rendering in this mode)'}
2352
+ app/{llms,robots,sitemap,…} root AX routes (llms.txt, robots.txt, sitemap.xml, …)${a.mode === 'coupled' ? '\napp/(site)/layout.tsx public shell: theme tokens + your header/footer\napp/(site)/site.css your site chrome styles (edit freely)\napp/(site)/[[...slug]]/page.tsx renders published pages (layout + SEO + JSON-LD + i18n)\nlib/site.ts public-render helpers: path/locale resolution, feeds, personalization\ncomponents/site-nav.tsx client wrapper around the shared nav renderer (header + footer)\ncomponents/personalization-runtime.tsx optional client runtime (self-ID widget, live re-eval)\napp/(site)/kywi-front-edit.tsx front-of-site editor: browse toolbar + in-place Layout editor (?kywi-edit=1, lazy-loaded)' : '\napp/page.tsx returns 404 (no public rendering in this mode)'}
2296
2353
  \`\`\`
2297
2354
  `
2298
2355
  }
@@ -2676,6 +2733,11 @@ export function buildFileSet(answers) {
2676
2733
  // page at its slug. More-specific /admin and /api routes take precedence.
2677
2734
  files['app/(site)/layout.tsx'] = siteLayout(answers)
2678
2735
  files['app/(site)/site.css'] = siteStyles(answers)
2736
+ // 'use client' wrapper around core's shared nav renderer — required because
2737
+ // BUILT_IN_MODULE_COMPONENTS is exported from a 'use client' core module, so
2738
+ // the (Server Component) site layout above must render it through here
2739
+ // rather than looking it up directly (see siteNavComponent's doc comment).
2740
+ files['components/site-nav.tsx'] = siteNavComponent()
2679
2741
  files['app/(site)/[[...slug]]/page.tsx'] = siteSlugPage()
2680
2742
  // Public-render helpers: path/locale resolution, feed + component resolvers,
2681
2743
  // personalization + experiments.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kywi-app",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Scaffold a new Kywi CMS project — npx create-kywi-app my-site",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/Kywi-Software/kywi-cms#readme",