Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/kickside-development/17-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,10 +465,10 @@ change in a module upgrade propagates immediately, and reset-to-default is
literally deleting the row (`unset`). Never store the default as an
override.

Reads are served from a boot-warmed `store.memory` cache. A cold cache is a
loud error - `"settings cache is not warmed"` - not a silent DB reload.
Consumers reading at boot must start after the cache-warm barrier or fail
clearly; they must not switch to a second DB/env read path.
Reads are served from a `store.memory` cache warmed at boot. A cold cache is
normal, because a hot module update can replace the store: the read reloads from
the DB and fills the empty cache. It returns an error only when that reload
fails, and it never returns partial data.

## Gotchas

Expand Down
24 changes: 9 additions & 15 deletions scripts/check-module.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ if (typeof packageJson.wippy?.description !== 'string' || packageJson.wippy.desc
if (!securityIndex.includes('default: kickside.security.groups:members')) report('user_security_scope must default to the platform Members group')
if (/^\s*default:\s*app[:.]/m.test(rootIndex)) report('requirements must not default to a host-owned app resource; the host binds them')
if (!securityIndex.includes('intent: module_access')) report('the module must publish a module_access policy')
if (!viteConfig.includes('preserveEntrySignatures: false')) report('web-component build must preserve the entry ownership contract')
if (!viteConfig.includes("preserveEntrySignatures: 'strict'")) report('web-component build must keep registration and webComponent() in the entry')
if (!viteConfig.includes('manualChunks')) report('web-component build must keep the wrapper in its own chunk, so a lazy chunk never imports the entry back')
if (/--p-(red|green|blue|orange|yellow|purple|pink|sky|emerald)-\d+/i.test(styles)) report('frontend uses a nonexistent color-named --p-* token')

// Library builds don't auto-replace process.env; without the define shim any
Expand All @@ -56,20 +57,13 @@ if (!viteConfig.includes("'process.env.NODE_ENV'")) report("vite.config.ts must
if (!viteConfig.includes("'process.env': '{}'")) report("vite.config.ts must define 'process.env': '{}' as the fallback shim")

// A component loads in two documents: auto-registered elements in the top
// document under the web-host import map, and surfaces inside the shell frame
// under the shell import map. Only packages both maps serve may stay external;
// a bundled copy of one of them duplicates framework state and reintroduces
// process.env reads the define shim cannot reach inside prebuilt library
// dists. Every other package, PrimeVue included, is bundled: the shell frame
// maps a subset of primevue/* and the top document maps none.
const hostProvidedPackages = ['vue', 'pinia', 'vue-router', 'axios', 'nanoevents', 'luxon', '@iconify/vue',
'@wippy-fe/proxy']
const declaredDeps = Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies, ...packageJson.peerDependencies })
for (const dep of declaredDeps) {
if (hostProvidedPackages.includes(dep) && !viteConfig.includes(`'${dep}'`)) {
report(`ui/package.json declares ${dep}, which every host import map provides; add it to rollupOptions.external in vite.config.ts`)
}
}
// document and surfaces inside the shell frame. Both serve the complete Host
// import map, so its pinned keys are the externals contract. A bundled copy of a
// mapped package duplicates framework state and reintroduces process.env reads
// the define shim cannot reach inside prebuilt library dists.
const hostImportMap = JSON.parse(await readFile(resolve(root, 'ui/web-host.import-map.snapshot.json'), 'utf8'))
const hostProvidedPackages = Object.keys(hostImportMap.imports)
if (!viteConfig.includes('Object.keys(hostImportMap.imports)')) report('rollupOptions.external must come from the pinned Host import map')

// The shell mounts page components in a definite-height surface whose every
// ancestor hides overflow: a page that does not own its scrolling gets
Expand Down
35 changes: 23 additions & 12 deletions scripts/test-initializer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -82,27 +82,38 @@ const declarePrimeVue = (source) => {
return `${JSON.stringify(manifest, null, 2)}\n`
}

const bundledPrimeVue = await checkWith({ 'ui/package.json': declarePrimeVue })
assert.equal(bundledPrimeVue.status, 0, `check must accept a module that bundles PrimeVue:\n${bundledPrimeVue.stderr}`)
const declaredPrimeVue = await checkWith({ 'ui/package.json': declarePrimeVue })
assert.equal(declaredPrimeVue.status, 0, `check must accept a module that declares PrimeVue:\n${declaredPrimeVue.stderr}`)

const externalPrimeVue = await checkWith({
'ui/package.json': declarePrimeVue,
'static/index.js': (source) => `import { default as Tabs } from "primevue/tabs";\n${source}`,
})
assert.equal(externalPrimeVue.status, 1, 'check must reject a bundle that imports PrimeVue from the import map')
assert.match(externalPrimeVue.stderr, /static\/index\.js imports primevue\/tabs/)
assert.equal(externalPrimeVue.status, 0, `check must accept a bundle that imports a mapped PrimeVue path:\n${externalPrimeVue.stderr}`)

const surfaceOnlyImport = await checkWith({
'static/index.js': (source) => `import MarkdownIt from"markdown-it";\n${source}`,
const unmappedImport = await checkWith({
'static/index.js': (source) => `import { spinner } from"@primevue/icons/spinner";\n${source}`,
})
assert.equal(surfaceOnlyImport.status, 1, 'check must reject a bare import the shell surface import map lacks')
assert.match(surfaceOnlyImport.stderr, /static\/index\.js imports markdown-it/)
assert.equal(unmappedImport.status, 1, 'check must reject a bare import the Host import map lacks')
assert.match(unmappedImport.stderr, /static[\\/]index\.js imports @primevue\/icons\/spinner/)

const bundledVue = await checkWith({
'ui/vite.config.ts': (source) => source.replace(/^\s*'vue',\n/m, ''),
const curatedExternals = await checkWith({
'ui/vite.config.ts': (source) => source.replace('Object.keys(hostImportMap.imports)', "['vue']"),
})
assert.equal(bundledVue.status, 1, 'check must reject a declared host-provided package left out of rollupOptions.external')
assert.match(bundledVue.stderr, /declares vue, which every host import map provides/)
assert.equal(curatedExternals.status, 1, 'check must reject externals that do not come from the Host import map')
assert.match(curatedExternals.stderr, /must come from the pinned Host import map/)

const droppedFactory = await checkWith({
'ui/vite.config.ts': (source) => source.replace("preserveEntrySignatures: 'strict'", 'preserveEntrySignatures: false'),
})
assert.equal(droppedFactory.status, 1, 'check must reject a build that can drop webComponent() from the entry')
assert.match(droppedFactory.stderr, /keep registration and webComponent\(\) in the entry/)

const sharedWrapper = await checkWith({
'ui/vite.config.ts': (source) => source.replace(' manualChunks: wcRuntimeChunk,\n', ''),
})
assert.equal(sharedWrapper.status, 1, 'check must reject a build that leaves the wrapper in the entry')
assert.match(sharedWrapper.stderr, /keep the wrapper in its own chunk/)

console.log('Initializer test passed')
await rm(target, { recursive: true, force: true })
Loading
Loading