✨ feat: enhance admin UI configuration and SSR handling
- Add support for number chip arrays and JSON editor in admin UI config. - Introduce pagebuilder block registry for Svelte components in admin previews. - Implement custom role names and a 3-layer cascade model for field-level permissions. - Add CORS configuration hierarchy for better API security. - Update project setup instructions for admin token and config management. - Improve SSR 404 signaling with proper context handling in NotFound component. - Refactor routing structure to separate NotFound page into its own route.
This commit is contained in:
@@ -0,0 +1,248 @@
|
|||||||
|
# Build Checklist — Autonomous Website Project
|
||||||
|
|
||||||
|
> Navigate this checklist **in order** when building a complete website project from the `tibi-svelte-starter`. Each phase produces concrete artifacts. Do not skip phases — earlier decisions constrain later ones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 0: Project Bootstrap
|
||||||
|
|
||||||
|
**Skills:** `tibi-project-setup`
|
||||||
|
|
||||||
|
- [ ] Replace all starter placeholders in ALL files:
|
||||||
|
- `.env`: `__PROJECT_NAME__`, `__TIBI_NAMESPACE__`, `__ORG__`, `__PROJECT__`
|
||||||
|
- `api/config.yml`: `namespace: __TIBI_NAMESPACE__`
|
||||||
|
- `frontend/.htaccess`: both `__TIBI_NAMESPACE__` entries
|
||||||
|
- `api/hooks/config-client.js`: `__PROJECT__` (not `__PROJECT_NAME__`)
|
||||||
|
- [ ] Configure `.env` URLs: `CODING_URL`, `STAGING_URL`, `CODING_TIBIADMIN_URL`
|
||||||
|
- [ ] Update `package.json` metadata (name, repository)
|
||||||
|
- [ ] Run `grep -n '__[A-Z0-9_]\+__' . --include='*.{yml,js,env,htaccess,json}'` to verify no placeholder remains
|
||||||
|
- [ ] Generate secure `ADMIN_TOKEN` in `api/config.yml.env`
|
||||||
|
- [ ] Verify `ADMIN_ASSET_VERSION` exists in `api/config.yml.env` (re-generate if missing: `echo "ADMIN_ASSET_VERSION=$(node -e "process.stdout.write(require('crypto').randomBytes(6).toString('hex'))")-dirty-${Date.now()}" >> api/config.yml.env`)
|
||||||
|
- [ ] `yarn install` — must succeed
|
||||||
|
- [ ] `make docker-up` — verify all containers "Up"
|
||||||
|
- [ ] Verify URLs respond: website, tibiadmin, tibiserver API
|
||||||
|
- [ ] `yarn build && yarn build:server && yarn validate` — 0 errors, 0 warnings
|
||||||
|
|
||||||
|
## Phase 1: Solution Architecture
|
||||||
|
|
||||||
|
**Skills:** `website-solution-architecture`, `security-hardening-and-token-strategy`
|
||||||
|
|
||||||
|
- [ ] Document the website's content model:
|
||||||
|
- Which page types exist?
|
||||||
|
- Which data is page-local vs. reusable?
|
||||||
|
- Which domain collections exist (team, products, events, etc.)?
|
||||||
|
- Is content multilingual? Entry-level or field-level i18n?
|
||||||
|
- [ ] Document the navigation model:
|
||||||
|
- Header, footer, utility navigation?
|
||||||
|
- Language-specific or shared?
|
||||||
|
- Max nesting levels per tree?
|
||||||
|
- [ ] Document the route model:
|
||||||
|
- Language-prefixed URLs (`/{lang}/{path}`)?
|
||||||
|
- Content `path` stored without language prefix?
|
||||||
|
- Route translations needed?
|
||||||
|
- [ ] Document forms/workflows:
|
||||||
|
- Contact form, newsletter, booking, etc.?
|
||||||
|
- Action endpoints or collections?
|
||||||
|
- Persistence needed (inquiries collection)?
|
||||||
|
- [ ] Document SSR requirements:
|
||||||
|
- Which routes are SSR-valid?
|
||||||
|
- Which collections are page-critical (content, navigation)?
|
||||||
|
- Publication windows needed?
|
||||||
|
- [ ] Document permissions:
|
||||||
|
- Who can CRUD which collections?
|
||||||
|
- Field-level readonly/hidden for editors?
|
||||||
|
- Token-based integrations?
|
||||||
|
|
||||||
|
## Phase 2: Collection & Admin Model
|
||||||
|
|
||||||
|
**Skills:** `content-authoring`, `admin-ui-config`, `nova-pagebuilder-modeling`, `nova-navigation-modeling`, `media-seo-publishing`
|
||||||
|
|
||||||
|
- [ ] Create/modify collection YAML files in `api/collections/`:
|
||||||
|
- `content.yml` with pagebuilder blocks
|
||||||
|
- `navigation.yml` with `viewHint.navigation`
|
||||||
|
- `medialib.yml` with media library
|
||||||
|
- Domain collections (team, products, events, etc.)
|
||||||
|
- `ssr.yml` (SSR cache — keep as-is)
|
||||||
|
- [ ] Include all collections in `api/config.yml`
|
||||||
|
- [ ] Configure admin ergonomics for EVERY collection:
|
||||||
|
- `meta.preview` for row/breadcrumb/FK display
|
||||||
|
- `meta.viewHint` (table, cards, media, navigation)
|
||||||
|
- `sidebar` groups for publication/SEO/settings
|
||||||
|
- `containerProps.layout` for multi-column forms
|
||||||
|
- `drillDown` for complex `object[]` arrays
|
||||||
|
- `dependsOn` for conditional fields
|
||||||
|
- `pagebuilder` + `blockRegistry` for block-based collections
|
||||||
|
- `singleton` for single-document config collections
|
||||||
|
- `choices`, `foreign`, `widget` overrides as needed
|
||||||
|
- [ ] Configure field validators:
|
||||||
|
- `required`, `maxLength`, `min`, `max` where appropriate
|
||||||
|
- `accept` MIME types for file fields
|
||||||
|
- `image` dimension constraints for image fields
|
||||||
|
- `format: "email" | "url" | "slug"` for string fields
|
||||||
|
- [ ] Verify in Nova admin:
|
||||||
|
- Collection sidebar labels, icons, groups
|
||||||
|
- List views show meaningful previews
|
||||||
|
- Entry forms are usable (not one long scrolling column)
|
||||||
|
- Sidebar groups and sections are coherent
|
||||||
|
- Foreign references show readable previews
|
||||||
|
- Pagebuilder blocks are selectable and editable
|
||||||
|
|
||||||
|
## Phase 3: TypeScript Types
|
||||||
|
|
||||||
|
**Skills:** `content-authoring` (types section)
|
||||||
|
|
||||||
|
- [ ] Create/modify `types/global.d.ts`:
|
||||||
|
- `ContentBlockEntry` fields matching collection YAML subFields
|
||||||
|
- Domain collection entry types (e.g. `TeamEntry`, `ProductEntry`)
|
||||||
|
- `Ssr` type for SSR config interface
|
||||||
|
- [ ] Wire collection types into `EntryTypeSwitch` in `frontend/src/lib/api.ts`
|
||||||
|
- [ ] `yarn validate` — 0 errors, 0 warnings
|
||||||
|
|
||||||
|
## Phase 4: Frontend Components
|
||||||
|
|
||||||
|
**Skills:** `frontend-architecture`, `nova-pagebuilder-modeling`
|
||||||
|
|
||||||
|
- [ ] Create block components in `frontend/src/blocks/`:
|
||||||
|
- Each block type gets a Svelte 5 component
|
||||||
|
- Accept `block: ContentBlockEntry` as props
|
||||||
|
- SSR-safe (guard browser APIs with `typeof window !== "undefined"`)
|
||||||
|
- [ ] Register blocks in `frontend/src/blocks/BlockRenderer.svelte`:
|
||||||
|
- Add `import` and `{:else if}` case for each type
|
||||||
|
- [ ] Register pagebuilder blocks in admin bundle:
|
||||||
|
- Add each block to `blockRegistry` in `frontend/src/admin.ts`
|
||||||
|
- `yarn build` to regenerate `frontend/dist/admin.mjs`
|
||||||
|
- [ ] Verify frontend rendering:
|
||||||
|
- `yarn build` succeeds
|
||||||
|
- Page loads in browser
|
||||||
|
- All block types render
|
||||||
|
- Navigation and media references work
|
||||||
|
- Language switching works
|
||||||
|
|
||||||
|
## Phase 5: SSR Setup
|
||||||
|
|
||||||
|
**Skills:** `tibi-ssr-caching`
|
||||||
|
|
||||||
|
- [ ] Update `api/hooks/config.js`:
|
||||||
|
- `ssrValidatePath()` validates all public routes
|
||||||
|
- `publishedFilter` matches the publication model
|
||||||
|
- `ssrPublishCheckCollections` includes all time-sensitive collections
|
||||||
|
- [ ] Verify SSR endpoint directly:
|
||||||
|
```bash
|
||||||
|
curl "http://tibiserver:8080/api/v1/_/<namespace>/ssr?url=/de/..."
|
||||||
|
```
|
||||||
|
- HTTP status correct
|
||||||
|
- Page content present in HTML
|
||||||
|
- Navigation labels present in HTML
|
||||||
|
- `window.__SSR_CACHE__` present
|
||||||
|
- Second request returns `X-SSR-Cache: true`
|
||||||
|
- `yarn build:server` succeeds
|
||||||
|
|
||||||
|
## Phase 6: Backend Hooks & Actions
|
||||||
|
|
||||||
|
**Skills:** `tibi-hook-authoring`, `tibi-actions-and-forms`, `scheduled-jobs-and-automation`, `realtime-and-live-workflows`
|
||||||
|
|
||||||
|
- [ ] Create/update public read hooks (`api/hooks/<collection>/get_read.js`):
|
||||||
|
- Filter inactive/unpublished entries for public users
|
||||||
|
- Use `filter_public.js` pattern
|
||||||
|
- [ ] Create/update cache invalidation hooks (`api/hooks/clear_cache.js`):
|
||||||
|
- Clear SSR cache on content/navigation/medialib changes
|
||||||
|
- Handle `POST`, `PUT`, `DELETE` for each collection that affects SSR
|
||||||
|
- [ ] Create action endpoints in `api/actions/`:
|
||||||
|
- Contact form, newsletter, etc.
|
||||||
|
- Hook chain: bind → validate → handle → return
|
||||||
|
- Permissions per action (public vs. authenticated)
|
||||||
|
- Wire into `api/config.yml` under `actions:`
|
||||||
|
- [ ] Register all hooks in collection/action YAML files
|
||||||
|
- [ ] Verify hooks work:
|
||||||
|
- Public API returns only active entries
|
||||||
|
- Actions respond correctly to valid/invalid submissions
|
||||||
|
- Cache clears on content mutation
|
||||||
|
|
||||||
|
## Phase 7: Permissions & Security
|
||||||
|
|
||||||
|
**Skills:** `permissions-and-editor-workflows`, `security-hardening-and-token-strategy`
|
||||||
|
|
||||||
|
- [ ] Configure collection permissions:
|
||||||
|
- `public` read methods where appropriate
|
||||||
|
- `user` write methods for editors
|
||||||
|
- `"token:${ADMIN_TOKEN}"` for seed/test access
|
||||||
|
- [ ] Configure field-level permissions:
|
||||||
|
- `readonlyFields` at collection or permissionSet level
|
||||||
|
- `hiddenFields` for sensitive internal data
|
||||||
|
- `readonly`/`hidden` with eval for dynamic rules
|
||||||
|
- [ ] Secure sensitive config:
|
||||||
|
- Production `ADMIN_TOKEN` uses a real random value
|
||||||
|
- Hook `http.fetch` and `exec.command` used with caution
|
||||||
|
- [ ] Verify: non-admin users see only permitted fields/collections
|
||||||
|
- [ ] Verify CORS if external origins access the API
|
||||||
|
|
||||||
|
## Phase 8: Media & SEO
|
||||||
|
|
||||||
|
**Skills:** `media-seo-publishing`, `nova-ai-editor-features` (optional)
|
||||||
|
|
||||||
|
- [ ] Configure `api/collections/medialib.yml`:
|
||||||
|
- File field with `widget: image`
|
||||||
|
- Alt text, caption fields
|
||||||
|
- Image filters for thumbnails, cards, heroes
|
||||||
|
- [ ] Add SEO fields to content/page collections:
|
||||||
|
- `meta.title`, `meta.description`
|
||||||
|
- Social share image reference
|
||||||
|
- Sidebar placement for SEO fields
|
||||||
|
- [ ] Configure publication model:
|
||||||
|
- `active` boolean
|
||||||
|
- `publication.from` / `publication.to` if time-based
|
||||||
|
- SSR-aware cache invalidation for publication changes
|
||||||
|
- [ ] Verify: SSR HTML includes SEO meta tags
|
||||||
|
|
||||||
|
## Phase 9: Testing
|
||||||
|
|
||||||
|
**Skills:** `playwright-testing`
|
||||||
|
|
||||||
|
- [ ] Extend seed data in `tests/api/helpers/seed-data.ts`:
|
||||||
|
- Seeded pages for all public routes
|
||||||
|
- Seeded navigation entries
|
||||||
|
- Hidden `_testdata: true` marker for seed identity
|
||||||
|
- [ ] Write API tests for collections:
|
||||||
|
- Public reads return expected data
|
||||||
|
- Auth-required writes are enforced
|
||||||
|
- Actions respond correctly
|
||||||
|
- [ ] Write E2E tests for critical user journeys:
|
||||||
|
- Homepage loads
|
||||||
|
- Language switching works
|
||||||
|
- SPA navigation works
|
||||||
|
- Each block type renders
|
||||||
|
- 404 for non-existent pages
|
||||||
|
- [ ] Write admin smoke tests if admin workflows are stable:
|
||||||
|
- Login works
|
||||||
|
- Core collections are reachable
|
||||||
|
- [ ] Run affected tests:
|
||||||
|
```bash
|
||||||
|
npx playwright test tests/api/health.spec.ts --project=api
|
||||||
|
npx playwright test tests/e2e/home.spec.ts --project=chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 10: Video Tours (optional)
|
||||||
|
|
||||||
|
- [ ] Update/create tour files in `video-tours/tours/`:
|
||||||
|
- Key user flows for documentation/training
|
||||||
|
- Desktop and mobile variants
|
||||||
|
- [ ] Run tours and verify output:
|
||||||
|
```bash
|
||||||
|
yarn tour && yarn tour:mobile
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 11: Final Verification
|
||||||
|
|
||||||
|
**Skills:** `tibi-project-setup` (build steps), `playwright-testing` (test suite)
|
||||||
|
|
||||||
|
- [ ] `yarn build` — success
|
||||||
|
- [ ] `yarn build:server` — success
|
||||||
|
- [ ] `yarn validate` — 0 errors, 0 warnings
|
||||||
|
- [ ] `rg '__[A-Z0-9_]\+__' . --include='*.{yml,js,env,htaccess,json,ts,svelte}'` — no placeholder remains
|
||||||
|
- [ ] All Playwright tests pass (or known failures documented)
|
||||||
|
- [ ] Public site loads at website URL
|
||||||
|
- [ ] Nova admin loads at admin URL
|
||||||
|
- [ ] Pages are creatable and editable in admin
|
||||||
|
- [ ] SSR renders real page content
|
||||||
|
- [ ] Forms/actions work (if applicable)
|
||||||
|
- [ ] `config.yml.env` has production-ready `ADMIN_TOKEN`
|
||||||
@@ -108,6 +108,7 @@ fields:
|
|||||||
| ----------- | ---------------------- | --------------------------------------------- |
|
| ----------- | ---------------------- | --------------------------------------------- |
|
||||||
| `string` | Text input | Use `inputProps.multiline: true` for textarea |
|
| `string` | Text input | Use `inputProps.multiline: true` for textarea |
|
||||||
| `number` | Number input | |
|
| `number` | Number input | |
|
||||||
|
| `number[]` | Number chip array | Multiple numeric values |
|
||||||
| `boolean` | Toggle/checkbox | |
|
| `boolean` | Toggle/checkbox | |
|
||||||
| `date` | Date picker | |
|
| `date` | Date picker | |
|
||||||
| `object` | Nested field group | Requires `subFields` |
|
| `object` | Nested field group | Requires `subFields` |
|
||||||
@@ -115,6 +116,7 @@ fields:
|
|||||||
| `string[]` | Tag input | |
|
| `string[]` | Tag input | |
|
||||||
| `file` | File upload | |
|
| `file` | File upload | |
|
||||||
| `file[]` | Multi-file upload | |
|
| `file[]` | Multi-file upload | |
|
||||||
|
| `any` | JSON editor | For mixed/arbitrary data |
|
||||||
|
|
||||||
### inputProps — widget customization
|
### inputProps — widget customization
|
||||||
|
|
||||||
@@ -451,7 +453,79 @@ For complex nested objects, use `drillDown` to render them as a sub-page:
|
|||||||
|
|
||||||
## Admin module (frontend/src/admin.ts)
|
## Admin module (frontend/src/admin.ts)
|
||||||
|
|
||||||
The `admin.ts` file exports custom Svelte components for injection into the tibi-admin UI. Components are rendered inside Shadow DOM to isolate styles.
|
The `admin.ts` file exports the **pagebuilder block registry** and optional custom Svelte components for the tibi-admin UI. This is how the admin preview renders your Svelte blocks.
|
||||||
|
|
||||||
|
### Pagebuilder block registry
|
||||||
|
|
||||||
|
The current starter uses `createContentBlockDefinition()` to register each block type. This mounts real Svelte block components into Shadow DOM for admin previews:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mount, unmount, type Component, type SvelteComponent } from "svelte"
|
||||||
|
import BlockRenderer from "./blocks/BlockRenderer.svelte"
|
||||||
|
|
||||||
|
// Creates a block definition that renders the same Svelte component
|
||||||
|
// used in the public frontend. The block is mounted inside Shadow DOM
|
||||||
|
// for style isolation.
|
||||||
|
function createContentBlockDefinition(presentation: {
|
||||||
|
label: string
|
||||||
|
icon: string
|
||||||
|
color: string
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
css: [previewCssUrl], // CSS files to inject into Shadow DOM
|
||||||
|
label: presentation.label,
|
||||||
|
icon: presentation.icon,
|
||||||
|
color: presentation.color,
|
||||||
|
previewStyles: {
|
||||||
|
"background-color": "white",
|
||||||
|
},
|
||||||
|
render(container, row, context) {
|
||||||
|
// Mount the Svelte component inside the admin preview
|
||||||
|
const target = document.createElement("div")
|
||||||
|
container.appendChild(target)
|
||||||
|
|
||||||
|
let mountedComponent = mount(BlockRenderer as Component<any>, {
|
||||||
|
target,
|
||||||
|
props: { blocks: [row], isAdminPreview: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(nextRow) {
|
||||||
|
unmount(mountedComponent)
|
||||||
|
target.innerHTML = ""
|
||||||
|
mountedComponent = mount(BlockRenderer as Component<any>, {
|
||||||
|
target,
|
||||||
|
props: { blocks: [nextRow], isAdminPreview: true },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
unmount(mountedComponent)
|
||||||
|
target.remove()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockRegistry = {
|
||||||
|
hero: createContentBlockDefinition({ label: "Hero", icon: "image", color: "#1d4ed8" }),
|
||||||
|
richtext: createContentBlockDefinition({ label: "Richtext", icon: "article", color: "#7c3aed" }),
|
||||||
|
// ... add new blocks here
|
||||||
|
}
|
||||||
|
|
||||||
|
export { blockRegistry }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
- Each registry entry wraps the Svelte `BlockRenderer` to render the block in the admin preview.
|
||||||
|
- The `row` object is the block data (same shape as `ContentBlockEntry`).
|
||||||
|
- Preview data may contain hydrated `_lookup.<fieldPath>` foreign key data and absolute file URLs — do not prepend `apiBase` or attempt re-fetching.
|
||||||
|
- The `previewCssUrl` loads the project's `index.css` into Shadow DOM so block styles apply.
|
||||||
|
- After adding blocks to the registry, run `yarn build` so `frontend/dist/admin.mjs` is regenerated.
|
||||||
|
|
||||||
|
### Custom Svelte components (advanced)
|
||||||
|
|
||||||
|
For custom dashboard widgets, preview components, or field widgets that require Svelte rendering inside the admin UI, use `getRenderedElement()`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { SvelteComponent } from "svelte"
|
import type { SvelteComponent } from "svelte"
|
||||||
@@ -462,16 +536,14 @@ function getRenderedElement(
|
|||||||
nestedElements?: { tagName: string; className?: string }[]
|
nestedElements?: { tagName: string; className?: string }[]
|
||||||
) {
|
) {
|
||||||
// Creates a Shadow DOM container, mounts the Svelte component inside
|
// Creates a Shadow DOM container, mounts the Svelte component inside
|
||||||
// addCss: CSS files to inject into Shadow DOM
|
|
||||||
// nestedElements: wrapper elements inside Shadow DOM
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getRenderedElement }
|
export { getRenderedElement }
|
||||||
```
|
```
|
||||||
|
|
||||||
Build with `yarn build`. The output includes the admin module and is loaded by tibi-admin-nova as a custom module.
|
### Build
|
||||||
|
|
||||||
**Use case:** Custom dashboard widgets, preview components, or field widgets that require Svelte rendering inside the admin UI.
|
Run `yarn build`. The admin module (`frontend/src/admin.ts`) is compiled into `frontend/dist/admin.mjs` as part of the esbuild build pipeline (the same build produces both `index.mjs` for the SPA and `admin.mjs` for the admin module). tibi-admin-nova loads this module from the project's asset path (`/_/assets/dist/admin.mjs`). The `ADMIN_ASSET_VERSION` from `config.yml.env` is appended as a query parameter for cache busting: `admin.mjs?v=${ADMIN_ASSET_VERSION}`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -513,7 +585,7 @@ permissions:
|
|||||||
get: true
|
get: true
|
||||||
post: true
|
post: true
|
||||||
put: true
|
put: true
|
||||||
delete: true
|
delete: false # usually false for real editorial workflows
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
- name: active
|
- name: active
|
||||||
@@ -583,6 +655,33 @@ fields:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Indexes and search
|
||||||
|
|
||||||
|
For production collections with many entries, consider adding indexes in the YAML:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: products
|
||||||
|
indexes:
|
||||||
|
- name: price_sort
|
||||||
|
key: [price]
|
||||||
|
- name: category_active
|
||||||
|
key: [category, -active] # -prefix for descending
|
||||||
|
- name: slug_unique
|
||||||
|
key: [slug]
|
||||||
|
unique: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Search configurations can be added for advanced text/vector search:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
search:
|
||||||
|
- name: default
|
||||||
|
mode: text
|
||||||
|
fields: [name, description]
|
||||||
|
```
|
||||||
|
|
||||||
|
See `tibi-server/docs/04-collections.md` (sections on indexes and search config) for full reference.
|
||||||
|
|
||||||
## Common pitfalls
|
## Common pitfalls
|
||||||
|
|
||||||
- **`meta.label` supports both strings and i18n objects** — Use i18n objects only when the collection or field label must be localized.
|
- **`meta.label` supports both strings and i18n objects** — Use i18n objects only when the collection or field label must be localized.
|
||||||
|
|||||||
@@ -49,6 +49,20 @@ At minimum, reason about permissions on these levels:
|
|||||||
|
|
||||||
Do not flatten all of this into one vague notion of “editor access”.
|
Do not flatten all of this into one vague notion of “editor access”.
|
||||||
|
|
||||||
|
**Custom role names:** Permission set keys in collection/action YAML are arbitrary strings. You can define any role name (e.g. `editor`, `reviewer`, `publisher`, `seo-manager`) and assign users with matching permissions. Combined with org/team membership (see `tibi-server/docs/18-orgs-teams.md`), this enables fine-grained editorial workflows beyond the built-in `public` and `user` roles.
|
||||||
|
|
||||||
|
### The 3-layer cascade model
|
||||||
|
|
||||||
|
Field-level permissions follow a strict 3-layer cascade:
|
||||||
|
|
||||||
|
1. **Collection-Level** (`collection.readonlyFields`, `collection.hiddenFields`): Base set applied to all permission sets.
|
||||||
|
2. **PermissionSet-Level** (`permissions.<role>.readonlyFields`, `permissions.<role>.hiddenFields`): Adds to or removes from the collection-level set. Prefix a field with `-` to negate (e.g. `-createdBy` removes it from the effective set).
|
||||||
|
3. **Field-Definition Override** (`field.readonly`, `field.hidden`): Absolute override — `true` forces the field into the set, `false` forces it out regardless of upper layers.
|
||||||
|
|
||||||
|
**Important:** Field-definition `readonly`/`hidden` also supports **eval expressions** (JS) for per-document dynamic evaluation. Eval rules are evaluated in a separate phase after the static cascade (Phase 1 = static cascade, Phase 2 = per-document eval). Admin role (role=0) bypasses all field-level restrictions.
|
||||||
|
|
||||||
|
See `tibi-server/docs/17-field-level-permissions.md` for the full reference with examples and eval expression context variables (`$`, `$this`, `$auth`, `$method`, `$project`, `$namespace`).
|
||||||
|
|
||||||
## Collection-level workflow design
|
## Collection-level workflow design
|
||||||
|
|
||||||
Before implementing permissions, define who does what.
|
Before implementing permissions, define who does what.
|
||||||
|
|||||||
@@ -124,6 +124,25 @@ Think in terms of:
|
|||||||
|
|
||||||
Do not rely on frontend hiding or convention where server-side permissions should be explicit.
|
Do not rely on frontend hiding or convention where server-side permissions should be explicit.
|
||||||
|
|
||||||
|
## CORS configuration
|
||||||
|
|
||||||
|
CORS follows a 3-level hierarchy. Configure it in `api/config.yml` under `cors:` for project-wide settings, or in individual collection/action YAML for per-endpoint overrides:
|
||||||
|
|
||||||
|
| Level | Configuration location | Scope |
|
||||||
|
|-------|----------------------|-------|
|
||||||
|
| Server | tibi-server `config.yml` | Global default |
|
||||||
|
| Project | `api/config.yml` → `cors:` | Per project |
|
||||||
|
| Collection/Action | Collection or action YAML → `cors:` | Per endpoint |
|
||||||
|
|
||||||
|
Each level can `merge: true` (append to parent) or `merge: false` (replace entirely).
|
||||||
|
|
||||||
|
For a project that serves a browser-based SPA to end users on its own domain and serves API/tibiadmin on separate subdomains, the default (no explicit CORS config) is usually correct since the SPA makes same-origin API calls via the BrowserSync/production reverse proxy. Add explicit CORS only when:
|
||||||
|
- the API is called from external origins (e.g. third-party integrations)
|
||||||
|
- the admin UI is served on a different origin than the API
|
||||||
|
- an action endpoint needs to support cross-origin form submissions
|
||||||
|
|
||||||
|
See `tibi-server/docs/02-configuration.md` (section "CORS Configuration Hierarchy") for details.
|
||||||
|
|
||||||
## Secure implementation patterns
|
## Secure implementation patterns
|
||||||
|
|
||||||
### Public form endpoint
|
### Public form endpoint
|
||||||
|
|||||||
@@ -53,12 +53,19 @@ sed -i "s/__PROJECT_NAME__/$PROJECT/g" .env
|
|||||||
sed -i "s/__TIBI_NAMESPACE__/$NAMESPACE/g" .env api/config.yml frontend/.htaccess
|
sed -i "s/__TIBI_NAMESPACE__/$NAMESPACE/g" .env api/config.yml frontend/.htaccess
|
||||||
```
|
```
|
||||||
|
|
||||||
Also update the starter-derived values that are not placeholder tokens anymore, especially `STAGING_PATH`, `STAGING_URL`, `CODING_URL`, `api/hooks/config-client.js`, and starter metadata in `package.json`.
|
Also update the starter-derived values that are not placeholder tokens anymore, especially `STAGING_PATH`, `STAGING_URL`, `CODING_URL`, `CODING_TIBIADMIN_URL`, `api/hooks/config-client.js`, and starter metadata in `package.json`.
|
||||||
|
|
||||||
**Verify each replacement:**
|
**Important:** The file `api/hooks/config-client.js` contains a **separate** placeholder `__PROJECT__` (not `__PROJECT_NAME__`):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
grep -n '__PROJECT_NAME__\|__TIBI_NAMESPACE__' .env api/config.yml frontend/.htaccess
|
# api/hooks/config-client.js has: const originURL = "https://__PROJECT__.code.testversion.online"
|
||||||
|
sed -i "s/__PROJECT__/$PROJECT/g" api/hooks/config-client.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify all placeholders:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
grep -n '__PROJECT_NAME__\|__TIBI_NAMESPACE__\|__PROJECT__\|__ORG__' .env api/config.yml frontend/.htaccess api/hooks/config-client.js
|
||||||
# Expected: no output (all placeholders replaced)
|
# Expected: no output (all placeholders replaced)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -83,7 +90,9 @@ The page title is set dynamically via `<svelte:head>` in `frontend/src/App.svelt
|
|||||||
|
|
||||||
Also verify that SSR still renders meaningful page content and not just the shell after the rewrite.
|
Also verify that SSR still renders meaningful page content and not just the shell after the rewrite.
|
||||||
|
|
||||||
## Step 4 — Admin token
|
## Step 4 — Admin token and config.yml.env
|
||||||
|
|
||||||
|
**How config.yml.env works:** The file `api/config.yml.env` is **not** a standard `.env` file. It is an env-file that the tibi-server reads from the same directory as `config.yml`. The server resolves `${ADMIN_TOKEN}` and `${ADMIN_ASSET_VERSION}` variables in the YAML config from this file. This is separate from the project-root `.env` which serves Docker Compose and the Makefile.
|
||||||
|
|
||||||
`api/config.yml.env` ships with a default `ADMIN_TOKEN`. For production projects, generate a secure one:
|
`api/config.yml.env` ships with a default `ADMIN_TOKEN`. For production projects, generate a secure one:
|
||||||
|
|
||||||
@@ -95,6 +104,8 @@ This updates only `ADMIN_TOKEN` and keeps the other env keys in the file intact.
|
|||||||
|
|
||||||
**Verify:** `cat api/config.yml.env` shows a 40-character hex token while preserving entries such as `ADMIN_ASSET_VERSION`.
|
**Verify:** `cat api/config.yml.env` shows a 40-character hex token while preserving entries such as `ADMIN_ASSET_VERSION`.
|
||||||
|
|
||||||
|
**Note:** The `ADMIN_ASSET_VERSION` in the same file is used for cache-busting the admin bundle (`frontend/dist/admin.mjs`). It is auto-generated on build — but if missing, the admin bundle may not load correctly.
|
||||||
|
|
||||||
## Step 5 — Install, upgrade, and start
|
## Step 5 — Install, upgrade, and start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -106,6 +117,8 @@ make docker-start # Start stack in foreground (CTRL-C to stop)
|
|||||||
|
|
||||||
Do not blindly run a full dependency upgrade as part of project bootstrap unless the task explicitly includes dependency maintenance. First get the starter running as-is, then upgrade intentionally and validate.
|
Do not blindly run a full dependency upgrade as part of project bootstrap unless the task explicitly includes dependency maintenance. First get the starter running as-is, then upgrade intentionally and validate.
|
||||||
|
|
||||||
|
**Important:** After changing `.env` or `api/config.yml.env`, you must run `make docker-down && make docker-up` for the changes to take effect. Docker Compose reads `.env` at startup time; tibi-server reads `config.yml.env` at reload. A simple `make docker-restart-frontend` is not sufficient for environment variable changes.
|
||||||
|
|
||||||
**Verify containers are running:**
|
**Verify containers are running:**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -135,12 +148,10 @@ For frontend development without a running tibi-server backend:
|
|||||||
```sh
|
```sh
|
||||||
# Set in .env:
|
# Set in .env:
|
||||||
MOCK=1
|
MOCK=1
|
||||||
# Then restart:
|
# Then full restart (env change requires docker-down first):
|
||||||
make docker-up
|
make docker-down && make docker-up
|
||||||
```
|
```
|
||||||
|
|
||||||
Mock data lives in `frontend/mocking/` as JSON files. Missing endpoints return 404.
|
|
||||||
|
|
||||||
**When to use mock mode:** Early UI prototyping, frontend-only work, CI environments without a database.
|
**When to use mock mode:** Early UI prototyping, frontend-only work, CI environments without a database.
|
||||||
|
|
||||||
## Step 8 — Remove demo content
|
## Step 8 — Remove demo content
|
||||||
@@ -215,6 +226,22 @@ For tibi-server specifically, decide early whether the site also needs:
|
|||||||
- field-level permissions
|
- field-level permissions
|
||||||
- AI/LLM integration for admin/editor workflows
|
- AI/LLM integration for admin/editor workflows
|
||||||
|
|
||||||
|
## SSR debugging: manual project reload
|
||||||
|
|
||||||
|
After changing collection YAML files, hook code, or `config.js` (SSR route validation), you may need to trigger a project reload for tibi-server to pick up the changes. Hook files auto-reload, but structural changes (new collections, config changes) require explicit reload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "$CODING_URL/api/v1/_/$TIBI_NAMESPACE/_/admin/reload" \
|
||||||
|
-H "Token: $ADMIN_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
The starter's `api/config.yml` has `allowReload: true` for the admin token by default. Response `{"message": "ok"}` confirms the reload.
|
||||||
|
|
||||||
|
Use this when:
|
||||||
|
- A new collection was added to `api/config.yml` but the API doesn't see it
|
||||||
|
- Hook files changed but the server hasn't picked them up
|
||||||
|
- SSR route validation (`api/hooks/config.js`) was updated and the old behavior persists
|
||||||
|
|
||||||
## Step 11 — Functional verification for a real website project
|
## Step 11 — Functional verification for a real website project
|
||||||
|
|
||||||
After the first project shaping pass, verify more than just TypeScript:
|
After the first project shaping pass, verify more than just TypeScript:
|
||||||
|
|||||||
@@ -150,6 +150,41 @@ If this mapping is wrong, SSR may appear to work for root pages while returning
|
|||||||
- This means SSR is not just HTML prerendering; it also primes client-side data access.
|
- This means SSR is not just HTML prerendering; it also primes client-side data access.
|
||||||
- If HTML renders but `window.__SSR_CACHE__` is missing, the SSR pipeline is incomplete.
|
- If HTML renders but `window.__SSR_CACHE__` is missing, the SSR pipeline is incomplete.
|
||||||
|
|
||||||
|
## SSR 404 signaling
|
||||||
|
|
||||||
|
When a page is not found during SSR, the framework returns the 404 page but with HTTP status **200** unless a 404 signal is set. The SSR hook (`get_read.js`) checks `context.is404` after rendering:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// get_read.js, after app.default.render()
|
||||||
|
if (context.is404) {
|
||||||
|
status = 404
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The signal is set from `NotFound.svelte` — when this component is rendered during SSR, it sets the flag directly. This keeps the 404 logic in the component that owns it:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// NotFound.svelte — top-level script, runs during render:
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
// @ts-ignore - context is the goja global in SSR runtime
|
||||||
|
context.is404 = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this works:**
|
||||||
|
- The `tibi-types` package declares `var context: HookContext` as a global (available because goja provides it during SSR).
|
||||||
|
- During SSR, `loadContent()` runs synchronously (goja transforms `async`/`await`).
|
||||||
|
- By the time `render(App)` returns in `ssr.ts`, `context.is404` is already `true`.
|
||||||
|
- `get_read.js` reads it, returns HTTP 404, and the rendered 404 page HTML is sent with the correct status.
|
||||||
|
- Caching is automatically skipped for 404 responses.
|
||||||
|
|
||||||
|
**Verification:** Test with a non-existent URL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -w "\nHTTP Status: %{http_code}\n" "http://tibiserver:8080/api/v1/_/<namespace>/ssr?url=/de/nicht-existierend"
|
||||||
|
# Expected: HTTP 404, body contains the 404 page HTML
|
||||||
|
```
|
||||||
|
|
||||||
## What an LLM should inspect first when changing SSR
|
## What an LLM should inspect first when changing SSR
|
||||||
|
|
||||||
1. `api/hooks/ssr/get_read.js` to understand cache lookup, route validation, and template injection.
|
1. `api/hooks/ssr/get_read.js` to understand cache lookup, route validation, and template injection.
|
||||||
|
|||||||
@@ -115,6 +115,12 @@ Use:
|
|||||||
- `singleton`
|
- `singleton`
|
||||||
- foreign previews
|
- foreign previews
|
||||||
|
|
||||||
|
**I18n field config:** When modeling multilingual content, decide early whether to use:
|
||||||
|
- **Field-level i18n** — object fields whose subField names match language codes (`de`, `en`, etc.) are auto-detected and rendered with language tabs in Nova. Configured via `api.meta.i18n` in `config.yml` or per-collection `meta.i18n`.
|
||||||
|
- **Entry-level i18n** — each entry represents one language, linked by a shared `translationGroup` UUID. The `I18nEntryConfig` type (from `tibi-admin-nova/types/admin.d.ts`) defines `languageField`, `groupField`, and `copyFields`/`clearFields` for translation cloning behavior.
|
||||||
|
|
||||||
|
See `tibi-admin-nova/types/admin.d.ts` interfaces `I18nFieldConfig` and `I18nEntryConfig` for the full API.
|
||||||
|
|
||||||
Do not treat admin config as optional polish. It is part of the solution architecture.
|
Do not treat admin config as optional polish. It is part of the solution architecture.
|
||||||
|
|
||||||
### 6. Actions and workflows
|
### 6. Actions and workflows
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ jobs:
|
|||||||
npm install -g yarn
|
npm install -g yarn
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
|
- name: validate
|
||||||
|
run: |
|
||||||
|
yarn validate
|
||||||
|
|
||||||
- name: modify config
|
- name: modify config
|
||||||
run: ./scripts/ci-modify-config.sh
|
run: ./scripts/ci-modify-config.sh
|
||||||
|
|
||||||
|
|||||||
@@ -4,226 +4,135 @@ Tibi CMS starter template — Svelte 5 SPA with esbuild, SSR via goja, and Playw
|
|||||||
|
|
||||||
## Project overview
|
## Project overview
|
||||||
|
|
||||||
- **Frontend**: Svelte 5 SPA in `frontend/src/`, bundled with esbuild, styled with Tailwind CSS 4.
|
- **Frontend**: Svelte 5 SPA in `frontend/src/`, esbuild, Tailwind CSS 4.
|
||||||
- **Backend**: tibi-server with API hooks in `api/hooks/`, collections in `api/collections/`.
|
- **Backend**: tibi-server with hooks in `api/hooks/`, collections in `api/collections/`.
|
||||||
- **SSR**: Server-side rendering via goja (Go JS runtime) in `api/hooks/ssr/`.
|
- **SSR**: goja (Go JS runtime) in `api/hooks/ssr/`.
|
||||||
- **Tests**: Playwright for E2E, API, mobile, and visual regression tests in `tests/`.
|
- **Tests**: Playwright in `tests/` (API, E2E, mobile, visual).
|
||||||
- **Types**: Shared TypeScript types in `types/global.d.ts`. Keep `tibi-types/` read-only.
|
- **Types**: `types/global.d.ts` (project), `tibi-types/` (read-only, from sibling repo).
|
||||||
|
|
||||||
## Project bootstrap
|
## Key constraints
|
||||||
|
|
||||||
Before treating this repo as a real project, replace the starter placeholders and initial project values.
|
- **Dev servers always run in Docker** — never `yarn dev` or `yarn start` locally.
|
||||||
|
- `yarn` is for standalone tasks: `build`, `build:server`, `validate`.
|
||||||
|
- Bootstrap details (placeholder replacement, docker setup) → `tibi-project-setup` skill.
|
||||||
|
- Build checklist for full website projects → `.agents/BUILD_CHECKLIST.md`.
|
||||||
|
|
||||||
Derive these values from the real repo path `gitbase.de/ORG/REPO`:
|
## Quick reference
|
||||||
|
|
||||||
- `PROJECT_NAME`: use the repo name in kebab-case.
|
| Action | Command |
|
||||||
- `TIBI_NAMESPACE`: set it equal to `PROJECT_NAME`, i.e. use the same repo name in kebab-case.
|
|--------|---------|
|
||||||
- `STAGING_PATH`: use the real repo org and repo name, i.e. `/staging/ORG/REPO/dev`.
|
| Start dev stack | `make docker-up` or `make docker-start` |
|
||||||
|
| Restart frontend | `make docker-restart-frontend` |
|
||||||
|
| Logs | `make docker-logs` |
|
||||||
|
| Build frontend | `yarn build` |
|
||||||
|
| Build SSR | `yarn build:server` |
|
||||||
|
| Validate | `yarn validate` |
|
||||||
|
| Run all tests | `yarn test` |
|
||||||
|
| Run E2E tests | `yarn test:e2e` |
|
||||||
|
| Run API tests | `yarn test:api` |
|
||||||
|
|
||||||
- `.env`: replace `PROJECT_NAME=__PROJECT_NAME__`, `TIBI_NAMESPACE=__TIBI_NAMESPACE__`, `STAGING_PATH=/staging/__ORG__/__PROJECT__/dev`, `STAGING_URL=https://dev-__PROJECT_NAME__.staging.testversion.online`, and `CODING_URL=https://__PROJECT_NAME__.code.testversion.online`.
|
## Testing notes
|
||||||
- `api/config.yml`: replace `namespace: __TIBI_NAMESPACE__`.
|
|
||||||
- `frontend/.htaccess`: replace both `__TIBI_NAMESPACE__` proxy targets.
|
|
||||||
- `api/hooks/config-client.js`: replace `https://__PROJECT__.code.testversion.online` with the real origin URL.
|
|
||||||
- `package.json`: adapt starter metadata like `name` and `repository` when creating the real project repo.
|
|
||||||
- Docker and local URLs derive from `.env`, so `PROJECT_NAME` and `TIBI_NAMESPACE` must be correct before `make docker-up`.
|
|
||||||
- Playwright seed/API tests read `CODING_URL` from `.env` first. Use the configured host from `.env` whenever it serves both `/` and `/api/...`, even in starter-style local bootstrap setups.
|
|
||||||
- Recommended check: search for remaining starter placeholders with `rg '__[A-Z0-9_]+__' .`.
|
|
||||||
|
|
||||||
## Setup commands
|
- Tests read `CODING_URL` from `.env`. Ensure it serves both `/` and `/api/...`.
|
||||||
|
- After changes, run only affected specs: `npx playwright test tests/e2e/filename.spec.ts`.
|
||||||
- Install deps: `yarn install`
|
- CI runs `yarn validate` but not Playwright (needs MongoDB + tibi-server).
|
||||||
- Start dev: `make docker-up` or `make docker-start`
|
|
||||||
- Restart frontend watcher/dev-server: `make docker-restart-frontend`
|
|
||||||
- View logs: `make docker-logs` or `make docker-logs-X`
|
|
||||||
- Start with mock data: set `MOCK=1` in `.env`, then use the normal Docker start command
|
|
||||||
- Build frontend/admin bundle: `yarn build`
|
|
||||||
- Build SSR bundle: `yarn build:server`
|
|
||||||
- Validate types: `yarn validate`
|
|
||||||
|
|
||||||
## Development workflow
|
|
||||||
|
|
||||||
- **Dev servers always run in Docker** — never use `yarn dev` or `yarn start` locally; web access only works through the Docker reverse proxy.
|
|
||||||
- Local `yarn` is only for standalone tasks: `yarn build`, `yarn build:server`, `yarn validate`.
|
|
||||||
- **Mock mode**: Set `MOCK=1` to run the frontend without a tibi-server. API calls are served from JSON files in `frontend/mocking/`. Missing mock endpoints return 404.
|
|
||||||
- Frontend code is automatically rebuilt by the watcher/BrowserSync stack; backend hooks reload on change.
|
|
||||||
- Read `.env` for environment URLs and secrets.
|
|
||||||
- `webserver/` is for staging/ops only; use BrowserSync/esbuild for day-to-day dev.
|
|
||||||
- If development environment is running, access the website at: `https://${PROJECT_NAME}.code.testversion.online/`.
|
|
||||||
- For a11y testing use MCP a11y tools if available.
|
|
||||||
- For quick interactive browser testing, ask the user to connect Playwright MCP (preferred) or Browser MCP (only in non-autonomous/chat mode).
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
- Run all tests: `yarn test`
|
|
||||||
- E2E tests: `yarn test:e2e`
|
|
||||||
- API tests: `yarn test:api`
|
|
||||||
- Visual regression: `yarn test:visual`
|
|
||||||
- Before running Playwright, ensure the `CODING_URL` from `.env` actually serves both `/` and `/api/...` for this repo.
|
|
||||||
- After code changes, run only affected spec files: `npx playwright test tests/e2e/filename.spec.ts`.
|
|
||||||
- Write unit tests for new functionality and ensure existing tests pass.
|
|
||||||
|
|
||||||
## Video tours
|
|
||||||
|
|
||||||
- Video tours are Playwright-based screen recordings (not tests) in `video-tours/`.
|
|
||||||
- Run all tours (desktop): `yarn tour`
|
|
||||||
- Run all tours (mobile): `yarn tour:mobile`
|
|
||||||
- Videos are saved to `video-tours/output/` (git-ignored).
|
|
||||||
- Tour files use `.tour.ts` suffix in `video-tours/tours/`.
|
|
||||||
- Helpers: `injectVisibleCursor()`, `moveThenClick()`, `moveThenType()`, `smoothScroll()` in `video-tours/helpers.ts`.
|
|
||||||
- Fixtures provide a `tourPage` with visible cursor overlay via `video-tours/tours/fixtures.ts`.
|
|
||||||
|
|
||||||
## API access
|
## API access
|
||||||
|
|
||||||
- API access to collections uses the reverse proxy: `CODING_URL/api/<collection>` (e.g. `CODING_URL/api/content`).
|
- `Token` header with `ADMIN_TOKEN` from `api/config.yml.env`.
|
||||||
- Auth via `Token` header with `ADMIN_TOKEN` from `api/config.yml.env` when a configured token with the required permissions is needed.
|
- Collection `user:` permissions apply to JWT auth (`X-Auth-Token`), not static `Token:`.
|
||||||
- Collection permissions for `user:` apply to JWT-authenticated users (`X-Auth-Token`), not to the static `Token:` header.
|
- For write access via `ADMIN_TOKEN`, add `"token:${ADMIN_TOKEN}": { methods: { post: true, put: true } }` to the collection permissions.
|
||||||
- If a collection should allow writes via `ADMIN_TOKEN`, define an explicit permission block like `"token:${ADMIN_TOKEN}":` with the required methods.
|
|
||||||
|
|
||||||
## Required secrets and credentials
|
## Required secrets
|
||||||
|
|
||||||
| Secret | Location | Purpose |
|
| Secret | Location | Purpose |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `ADMIN_TOKEN` | `api/config.yml.env` | Access token for configured API/admin permissions |
|
| `ADMIN_TOKEN` | `api/config.yml.env` | API/admin access token |
|
||||||
| `SENTRY_AUTH_TOKEN` | Gitea repo secrets | Sourcemap upload to Sentry (CI only) |
|
| `ADMIN_ASSET_VERSION` | `api/config.yml.env` | Cache-busting for admin bundle (auto-generated) |
|
||||||
| `.basic-auth-web` | project root (git-ignored) | Basic auth for BrowserSync dev server |
|
| `.basic-auth-web` | project root (git-ignored) | BrowserSync basic auth |
|
||||||
| `.basic-auth-code` | project root (git-ignored) | Basic auth for Code-Server / admin |
|
| `.basic-auth-code` | project root (git-ignored) | Code-Server basic auth |
|
||||||
| `RSYNC_PASS` | Gitea secrets (`github.token`) | rsync deployment password (CI only) |
|
|
||||||
|
|
||||||
## Infrastructure prerequisites
|
Generated dev defaults in `api/config.yml.env` may be committed. Production values are overwritten in CI/CD via secrets.
|
||||||
|
|
||||||
- **Code-Server environment** — This project is designed for development on a Code-Server instance at `*.code.testversion.online` with a **Traefik reverse proxy** managing HTTPS and auto-routing via Docker labels.
|
## Reference repositories (sibling repos)
|
||||||
- **Without Code-Server/Traefik** — The Docker stack starts but the website is not reachable via hostname. Workaround: access BrowserSync directly via `http://localhost:3000` (requires exposing the port in `docker-compose-local.yml`).
|
|
||||||
- **Docker + Docker Compose** — Required for all development. Never run `yarn dev` or `yarn start` locally.
|
|
||||||
- **MongoDB** — Runs as a Docker service (`mongo`). Data persists in `tmp/mongo-data/`.
|
|
||||||
- **Production** — Deployed via rsync to an existing server running tibi-server + MongoDB. The frontend is a static SPA served by a Node.js webserver (`webserver/webserver.js`) or directly by tibi-server.
|
|
||||||
- **Staging** — Docker Compose builds a `www` container that connects to an external tibi-server at `dev-tibi-server.staging.testversion.online`.
|
|
||||||
|
|
||||||
## Reference repositories
|
| Repo | Path | Key file |
|
||||||
|
|------|------|----------|
|
||||||
These sibling repos in the workspace provide documentation, types, and reference implementations:
|
| **tibi-types** | `../../cms/tibi-types` | `index.d.ts` (context types), `schemas/config/collection.schema.json` |
|
||||||
|
| **tibi-server** | `../../cms/tibi-server` | `docs/` (19 files: collections, hooks, auth, actions, SSE, jobs, etc.) |
|
||||||
| Repository | Path | Purpose |
|
| **tibi-admin-nova** | `../../cms/tibi-admin-nova` | `types/admin.d.ts` (all admin config types) |
|
||||||
| --- | --- | --- |
|
|
||||||
| **tibi-types** | `../../cms/tibi-types` | TypeScript type definitions for hooks, collections, permissions, etc. Included via `tsconfig.json` — **read-only, do not modify**. Key file: `index.d.ts`. JSON schemas in `schemas/api-config/`. |
|
|
||||||
| **tibi-server** | `../../cms/tibi-server` | Go source code of the server. `docs/` has detailed documentation (16 files), `examples/` has sample projects. |
|
|
||||||
| **tibi-admin-nova** | `../../cms/tibi-admin-nova` | Admin UI reference project. Key file: `types/admin.d.ts` (1147 lines — all admin types: `AdminCollection`, `AdminCollectionField`, `AdminCollectionMeta`, `AdminDashboard`, etc.). Use as reference for collection field configs, dashboard setup, and fieldLists. |
|
|
||||||
|
|
||||||
### When to consult which repo
|
### When to consult which repo
|
||||||
|
|
||||||
- **Write collection YAML** → `tibi-server/docs/04-collections.md` + `tibi-types/schemas/api-config/collection.json` (JSON schema)
|
- **Collection YAML** → `tibi-server/docs/04-collections.md` + `tibi-types/schemas/config/collection.schema.json`
|
||||||
- **Write/debug hooks** → `tibi-server/docs/06-hooks.md` + `tibi-types/index.d.ts` (context types)
|
- **Hooks** → `tibi-server/docs/06-hooks.md` + `tibi-types/index.d.ts`
|
||||||
- **Configure admin UI** → `tibi-admin-nova/types/admin.d.ts` (field types, meta, dashboard) + `tibi-admin-nova/api/collections/` (real examples)
|
- **Admin UI config** → `tibi-admin-nova/types/admin.d.ts` + `tibi-admin-nova/api/collections/` (examples)
|
||||||
- **Permissions** → `tibi-server/docs/05-authentication.md`
|
- **Permissions** → `tibi-server/docs/05-authentication.md`
|
||||||
|
- **Actions/forms** → `tibi-server/docs/19-actions.md`
|
||||||
- **Realtime/SSE** → `tibi-server/docs/07-realtime.md`
|
- **Realtime/SSE** → `tibi-server/docs/07-realtime.md`
|
||||||
- **Images/uploads** → `tibi-server/docs/08-file-upload-images.md`
|
- **Images/uploads** → `tibi-server/docs/08-file-upload-images.md`
|
||||||
- **LLM integration** → `tibi-server/docs/09-llm-integration.md`
|
- **LLM integration** → `tibi-server/docs/09-llm-integration.md`
|
||||||
|
- **Field-level permissions** → `tibi-server/docs/17-field-level-permissions.md`
|
||||||
|
|
||||||
### TypeScript types
|
## TypeScript
|
||||||
|
|
||||||
`tibi-types` is included via `tsconfig.json`:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"include": ["frontend/src/**/*", "types/**/*", "./../../cms/tibi-types", "api/**/*"]
|
"include": ["frontend/src/**/*", "types/**/*", "./../../cms/tibi-types", "api/**/*"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Project-specific types (e.g. `Ssr`, `ApiOptions`, `ContentEntry`) live in `types/global.d.ts`.
|
- Domain types (entities, block props, API responses) be strict. `any`/`[key: string]: any` OK for inherently dynamic data (MongoDB filters, CMS fields, hook payloads).
|
||||||
|
- No `@ts-ignore` — use `/** @type {…} */` cast instead.
|
||||||
|
- **Zero warnings policy**: always run `yarn validate` after changes; fix all warnings.
|
||||||
|
- Svelte 5 with Runes: `$props()`, `$state()`, `$derived()`, `$effect()`.
|
||||||
|
|
||||||
## Code style
|
## Tailwind CSS 4
|
||||||
|
|
||||||
- Follow the existing code style and conventions used in the project.
|
Canonical v4 syntax — no v3 legacy classes:
|
||||||
- Write clear and concise comments where necessary to explain complex logic.
|
|
||||||
- Ensure code is modular and reusable where possible.
|
|
||||||
- Avoid introducing new dependencies unless absolutely necessary.
|
|
||||||
- Respect a11y and localization best practices; optimize for WCAG AA standards.
|
|
||||||
- Check the problems tab for any errors or warnings in the code.
|
|
||||||
- **Zero warnings policy**: After making changes, always run `yarn validate` and check the IDE problems tab. Fix all TypeScript, Svelte, and Tailwind warnings — the codebase must stay warning-free.
|
|
||||||
- When Tailwind `suggestCanonicalClasses` warnings appear, always fix the **source** `.svelte`/`.ts`/`.css` file — never the compiled output.
|
|
||||||
|
|
||||||
### Architecture skills (loaded on demand)
|
| v3 | v4 |
|
||||||
|
|----|----|
|
||||||
These skills provide deep-dive documentation. Use them by phase instead of treating them as an unsorted reference list.
|
|
||||||
|
|
||||||
#### 1. Project start and solution design
|
|
||||||
|
|
||||||
| Skill | When to use |
|
|
||||||
| --- | --- |
|
|
||||||
| `tibi-project-setup` | Setting up a new project from scratch |
|
|
||||||
| `website-solution-architecture` | Translating website requirements into a complete solution across content, admin, SSR, and workflows |
|
|
||||||
| `security-hardening-and-token-strategy` | Applying secure token, secret, permission, and hook-capability decisions |
|
|
||||||
|
|
||||||
#### 2. Content model and editor UX
|
|
||||||
|
|
||||||
| Skill | When to use |
|
|
||||||
| --- | --- |
|
|
||||||
| `content-authoring` | Adding new pages, content blocks, or collections |
|
|
||||||
| `nova-pagebuilder-modeling` | Designing editor-friendly block systems, nested block schemas, and pagebuilder UX |
|
|
||||||
| `nova-navigation-modeling` | Modeling multilingual header/footer/navigation trees with current Nova navigation features |
|
|
||||||
| `admin-ui-config` | Configuring collection admin views, field widgets, layouts |
|
|
||||||
| `media-seo-publishing` | Modeling media, SEO, and publication workflows for website projects |
|
|
||||||
| `permissions-and-editor-workflows` | Designing safe editorial permissions, field rules, and role-aware admin workflows |
|
|
||||||
| `nova-ai-editor-features` | Applying AI and LLM capabilities in editor workflows and media authoring responsibly |
|
|
||||||
|
|
||||||
#### 3. Backend behavior and integrations
|
|
||||||
|
|
||||||
| Skill | When to use |
|
|
||||||
| --- | --- |
|
|
||||||
| `tibi-hook-authoring` | Writing or debugging server-side hooks |
|
|
||||||
| `tibi-actions-and-forms` | Building contact forms, workflow endpoints, and other action-based website features |
|
|
||||||
| `scheduled-jobs-and-automation` | Building cron-based background tasks, cleanups, reports, and sync workflows |
|
|
||||||
| `realtime-and-live-workflows` | Designing SSE-based live updates, notifications, previews, and status workflows |
|
|
||||||
|
|
||||||
#### 4. Frontend runtime and delivery
|
|
||||||
|
|
||||||
| Skill | When to use |
|
|
||||||
| --- | --- |
|
|
||||||
| `frontend-architecture` | Routing, state management, Svelte 5 patterns, API layer, error handling |
|
|
||||||
| `tibi-ssr-caching` | SSR rendering and cache invalidation |
|
|
||||||
| `playwright-testing` | Extending or debugging seeded Playwright API, desktop, mobile, or visual tests |
|
|
||||||
|
|
||||||
### Quickstart roadmap for a new website
|
|
||||||
|
|
||||||
Use this order when building a project from scratch on this starter:
|
|
||||||
|
|
||||||
1. Foundation.
|
|
||||||
Start with `tibi-project-setup` until Docker, URLs, build, and validate are green.
|
|
||||||
|
|
||||||
2. Solution design.
|
|
||||||
Use `website-solution-architecture` first, then `security-hardening-and-token-strategy`, before writing components or hooks.
|
|
||||||
|
|
||||||
3. Content and admin model.
|
|
||||||
Use the skills from **Content model and editor UX** to shape `api/collections/content.yml`, `api/collections/navigation.yml`, reusable domain collections, and Nova authoring UX.
|
|
||||||
|
|
||||||
4. Frontend and runtime.
|
|
||||||
Use `frontend-architecture` plus `nova-pagebuilder-modeling` when wiring routing, i18n, content loading, `frontend/src/blocks/BlockRenderer.svelte`, and shared types.
|
|
||||||
|
|
||||||
5. Backend behavior.
|
|
||||||
Use the skills from **Backend behavior and integrations** for hooks, forms/actions, jobs, and realtime only where the project actually needs them.
|
|
||||||
|
|
||||||
6. SSR and publishing.
|
|
||||||
Use `tibi-ssr-caching` once routes, navigation, and page-critical data are defined, and use `media-seo-publishing` plus `permissions-and-editor-workflows` where publication, SEO, and editorial restrictions matter.
|
|
||||||
|
|
||||||
7. Optional AI editor features.
|
|
||||||
Use `nova-ai-editor-features` only when there is a concrete editorial workflow for it.
|
|
||||||
|
|
||||||
8. Final verification.
|
|
||||||
Use `playwright-testing` for deterministic API/E2E coverage, confirm public site, admin authoring, pagebuilder rendering, navigation/media resolution, forms/actions, and SSR all work, then run `yarn build`, `yarn build:server`, and `yarn validate`.
|
|
||||||
|
|
||||||
For one-off tasks, use the phase tables above as the lookup index instead of maintaining a second skill matrix here.
|
|
||||||
|
|
||||||
### Tailwind CSS 4 canonical classes
|
|
||||||
|
|
||||||
This project uses Tailwind CSS 4. Always use the canonical v4 syntax:
|
|
||||||
|
|
||||||
| Legacy (v3) | Canonical (v4) |
|
|
||||||
| ---------------------- | ---------------------- |
|
|
||||||
| `bg-gradient-to-*` | `bg-linear-to-*` |
|
| `bg-gradient-to-*` | `bg-linear-to-*` |
|
||||||
| `aspect-[4/3]` | `aspect-4/3` |
|
| `aspect-[4/3]` | `aspect-4/3` |
|
||||||
| `!bg-brand-600` | `bg-brand-600!` |
|
| `!bg-brand-600` | `bg-brand-600!` |
|
||||||
| `hover:!bg-brand-700` | `hover:bg-brand-700!` |
|
| `hover:!bg-brand-700` | `hover:bg-brand-700!` |
|
||||||
| `!rounded-xl` | `rounded-xl!` |
|
|
||||||
|
|
||||||
Rules:
|
## Architecture skills (loaded on demand)
|
||||||
|
|
||||||
- Gradient directions use `bg-linear-to-{direction}` instead of `bg-gradient-to-{direction}`.
|
Use these tables as the lookup index. Each skill is a deep-dive for its domain.
|
||||||
- Bare-ratio aspect values like `aspect-4/3` replace arbitrary `aspect-[4/3]`.
|
|
||||||
- The `!important` modifier is a **suffix** (`class!`) instead of a **prefix** (`!class`).
|
### 1. Project start and solution design
|
||||||
|
|
||||||
|
| Skill | When |
|
||||||
|
|-------|------|
|
||||||
|
| `tibi-project-setup` | Bootstrap a new project from scratch |
|
||||||
|
| `website-solution-architecture` | Translate requirements into collections, blocks, SSR, workflows |
|
||||||
|
| `security-hardening-and-token-strategy` | Token strategy, secrets, hook risk surfaces, CORS |
|
||||||
|
|
||||||
|
### 2. Content model and editor UX
|
||||||
|
|
||||||
|
| Skill | When |
|
||||||
|
|-------|------|
|
||||||
|
| `content-authoring` | Add pages, block types, collections |
|
||||||
|
| `nova-pagebuilder-modeling` | Block schemas, preview, drillDown, dependsOn |
|
||||||
|
| `nova-navigation-modeling` | Header/footer trees, viewHint.navigation, declaredTrees |
|
||||||
|
| `admin-ui-config` | Field widgets, sidebar, layout, choices, foreign refs |
|
||||||
|
| `media-seo-publishing` | Image fields, alt/caption, SEO, publication model |
|
||||||
|
| `permissions-and-editor-workflows` | Field-level readonly/hidden, 3-layer cascade, custom roles |
|
||||||
|
| `nova-ai-editor-features` | AI-assisted alt text, LLM actions, token budgets |
|
||||||
|
|
||||||
|
### 3. Backend behavior
|
||||||
|
|
||||||
|
| Skill | When |
|
||||||
|
|-------|------|
|
||||||
|
| `tibi-hook-authoring` | Write/debug server-side hooks (goja) |
|
||||||
|
| `tibi-actions-and-forms` | Contact forms, newsletter, webhooks, action endpoints |
|
||||||
|
| `scheduled-jobs-and-automation` | Cron tasks, cleanup, reports, sync |
|
||||||
|
| `realtime-and-live-workflows` | SSE channels, live notifications, preview refresh |
|
||||||
|
|
||||||
|
### 4. Frontend and delivery
|
||||||
|
|
||||||
|
| Skill | When |
|
||||||
|
|-------|------|
|
||||||
|
| `frontend-architecture` | SPA router, stores, Svelte 5 patterns, API layer |
|
||||||
|
| `tibi-ssr-caching` | SSR rendering, cache invalidation, 404 signaling |
|
||||||
|
| `playwright-testing` | Deterministic seed data, API/E2E/admin/visual tests |
|
||||||
|
|||||||
@@ -29,3 +29,11 @@ Server-side rendering via goja (Go JS runtime) with HTML caching.
|
|||||||
- SSR route validation is active in `config.js`.
|
- SSR route validation is active in `config.js`.
|
||||||
- Public page URLs are language-prefixed (`/de/...`, `/en/...`), while `content.path` in the DB is stored without that prefix.
|
- Public page URLs are language-prefixed (`/de/...`, `/en/...`), while `content.path` in the DB is stored without that prefix.
|
||||||
- `ssrValidatePath()` must strip the language prefix before querying content and return a canonical language-prefixed URL when needed.
|
- `ssrValidatePath()` must strip the language prefix before querying content and return a canonical language-prefixed URL when needed.
|
||||||
|
|
||||||
|
## 404 signaling
|
||||||
|
|
||||||
|
The SSR hook (`get_read.js`) checks `context.is404` after rendering to determine the HTTP status code. If `true`, it returns HTTP 404 with the rendered 404 page HTML (and does not cache the result).
|
||||||
|
|
||||||
|
`NotFound.svelte` sets `context.is404 = true` during SSR. When the component renders (only when the page is not found), its top-level script sets the flag. The goja runtime provides `context` as a global during SSR, so it's available from the compiled frontend code.
|
||||||
|
|
||||||
|
When adding a 404 page or changing the not-found logic, ensure `context.is404` is still set during SSR.
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ Svelte 5 SPA bundled with esbuild and styled with Tailwind CSS 4.
|
|||||||
- `src/lib/` — utilities, stores, actions, API layer.
|
- `src/lib/` — utilities, stores, actions, API layer.
|
||||||
- `src/widgets/` — reusable UI components (Button, Input, Form, etc.).
|
- `src/widgets/` — reusable UI components (Button, Input, Form, etc.).
|
||||||
- `src/css/` — global styles and Tailwind imports.
|
- `src/css/` — global styles and Tailwind imports.
|
||||||
- Keep route components in a `src/routes/` folder when needed.
|
- `src/routes/` — page-level components (e.g. `NotFound.svelte` for the 404 page). Not file-based routing — these are manually imported by `App.svelte`.
|
||||||
|
|
||||||
## Routing
|
## Routing
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import ToastContainer from "./widgets/ToastContainer.svelte"
|
import ToastContainer from "./widgets/ToastContainer.svelte"
|
||||||
import DebugFooterInfo from "./widgets/DebugFooterInfo.svelte"
|
import DebugFooterInfo from "./widgets/DebugFooterInfo.svelte"
|
||||||
import BlockRenderer from "./blocks/BlockRenderer.svelte"
|
import BlockRenderer from "./blocks/BlockRenderer.svelte"
|
||||||
import NotFound from "./blocks/NotFound.svelte"
|
import NotFound from "./routes/NotFound.svelte"
|
||||||
import { initScrollRestoration } from "./lib/navigation"
|
import { initScrollRestoration } from "./lib/navigation"
|
||||||
import { getCachedEntries } from "./lib/api"
|
import { getCachedEntries } from "./lib/api"
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
import { _ } from "../lib/i18n/index"
|
import { _ } from "../lib/i18n/index"
|
||||||
import { localizedPath } from "../lib/i18n"
|
import { localizedPath } from "../lib/i18n"
|
||||||
import { reveal } from "../lib/actions/reveal"
|
import { reveal } from "../lib/actions/reveal"
|
||||||
|
|
||||||
|
// Signal HTTP 404 to the SSR hook during server-side rendering.
|
||||||
|
// get_read.js checks context.is404 after render() and returns status 404.
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
// @ts-ignore - context is the goja global in SSR runtime (declared by tibi-types)
|
||||||
|
context.is404 = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="min-h-[60vh] flex items-center justify-center bg-gray-50">
|
<section class="min-h-[60vh] flex items-center justify-center bg-gray-50">
|
||||||
Reference in New Issue
Block a user