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.
- package/assets/agent-patterns.md +9 -1
- package/lib/templates.mjs +77 -15
- package/package.json +1 -1
package/assets/agent-patterns.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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:
|
|
1633
|
-
//
|
|
1634
|
-
//
|
|
1635
|
-
|
|
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.
|
|
2173
|
-
\`defineModule\` types
|
|
2174
|
-
|
|
2175
|
-
|
|
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 place — the 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