✨ feat: enhance project setup and architecture documentation
- Updated `tibi-project-setup` skill to clarify project initialization goals and steps. - Improved `tibi-ssr-caching` skill to detail SSR architecture, responsibilities, and caching mechanisms. - Introduced `website-solution-architecture` skill for translating website requirements into coherent solutions. - Refined `AGENTS.md` to provide a structured roadmap for project development phases. - Added `ADMIN_ASSET_VERSION` to `api/config.yml.env` for asset versioning. - Updated SSR request flow and cache invalidation logic in `api/hooks/ssr/AGENTS.md`. - Removed obsolete `esbuild.config.admin.js` and integrated asset versioning into the main `esbuild.config.js`. - Adjusted `api/collections/content.yml` to utilize asset versioning for admin scripts.
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
---
|
||||
name: tibi-actions-and-forms
|
||||
description: Build endpoint-style website features with tibi-server actions. Covers when to use actions instead of collections, action hook flow, validation, permissions, CORS, mail/webhook patterns, and frontend integration for forms.
|
||||
---
|
||||
|
||||
# tibi-actions-and-forms
|
||||
|
||||
## When to use this skill
|
||||
|
||||
Use this skill when:
|
||||
|
||||
- Building contact forms, newsletter forms, quote requests, callbacks, or booking requests
|
||||
- Adding endpoint-like backend logic without CRUD storage
|
||||
- Replacing old collection hacks that only existed to accept POST requests
|
||||
- Designing frontend form submissions against tibi-server actions
|
||||
- Deciding whether a feature should be an action, a collection, or both
|
||||
|
||||
## Goal
|
||||
|
||||
The goal is to teach an LLM how to build website workflows that behave like real endpoints.
|
||||
|
||||
A form feature is complete only when all of these are coherent:
|
||||
|
||||
- action config
|
||||
- hook flow
|
||||
- validation
|
||||
- permissions and CORS
|
||||
- optional persistence or side effects
|
||||
- frontend submission and error handling
|
||||
|
||||
## Source of truth
|
||||
|
||||
Use these sources when implementing or reviewing action-based features:
|
||||
|
||||
- `tibi-server/docs/19-actions.md`
|
||||
- `tibi-server/docs/06-hooks.md`
|
||||
- `api/config.yml`
|
||||
- `api/hooks/`
|
||||
- `frontend/src/lib/api.ts`
|
||||
- `frontend/src/App.svelte` or the relevant frontend form surface
|
||||
|
||||
## Core decision: action or collection?
|
||||
|
||||
Use an **action** when the feature is primarily an endpoint or workflow.
|
||||
|
||||
Typical action cases:
|
||||
|
||||
- contact form
|
||||
- newsletter signup
|
||||
- quote request
|
||||
- callback request
|
||||
- webhook receiver
|
||||
- utility endpoint
|
||||
- AI-assisted helper endpoint
|
||||
|
||||
Use a **collection** when the feature is primarily stored content or stored records with CRUD semantics.
|
||||
|
||||
Typical collection cases:
|
||||
|
||||
- products
|
||||
- team members
|
||||
- events
|
||||
- testimonials
|
||||
- persisted inquiries that editors must browse/edit in admin
|
||||
|
||||
Use **action + collection** when you need a public workflow plus internal persistence.
|
||||
|
||||
Example:
|
||||
|
||||
- a contact form submits to an action
|
||||
- the action validates, sends mail, and optionally creates an `inquiries` entry for staff follow-up
|
||||
|
||||
Do not fake endpoint logic with empty collections unless there is a very specific reason.
|
||||
|
||||
## Routing model
|
||||
|
||||
Actions are exposed under:
|
||||
|
||||
```text
|
||||
POST /api/v1/_/:project/_actions/:action
|
||||
GET /api/v1/_/:project/_actions/:action
|
||||
```
|
||||
|
||||
The `_actions` prefix is part of the contract. Frontend form code should treat actions as explicit API endpoints, not as collection writes.
|
||||
|
||||
## Where actions are configured
|
||||
|
||||
Actions are declared in `api/config.yml` under `actions:` and typically point to files under:
|
||||
|
||||
- `api/actions/` for YAML configs
|
||||
- `api/hooks/<action-name>/` for hook files
|
||||
|
||||
Typical config shape:
|
||||
|
||||
```yaml
|
||||
actions:
|
||||
- !include actions/contact-form.yml
|
||||
```
|
||||
|
||||
```yaml
|
||||
name: contact-form
|
||||
|
||||
meta:
|
||||
label: { de: "Kontaktformular", en: "Contact Form" }
|
||||
|
||||
permissions:
|
||||
public:
|
||||
methods:
|
||||
post: true
|
||||
|
||||
hooks:
|
||||
post:
|
||||
bind:
|
||||
type: javascript
|
||||
file: hooks/contact-form/post_bind.js
|
||||
validate:
|
||||
type: javascript
|
||||
file: hooks/contact-form/post_validate.js
|
||||
handle:
|
||||
type: javascript
|
||||
file: hooks/contact-form/post_handle.js
|
||||
return:
|
||||
type: javascript
|
||||
file: hooks/contact-form/post_return.js
|
||||
```
|
||||
|
||||
## Action lifecycle
|
||||
|
||||
### POST flow
|
||||
|
||||
`bind` → `validate` → `handle` → `return`
|
||||
|
||||
Use the steps deliberately:
|
||||
|
||||
- `bind`: normalize the request body, derive helper data, prepare context
|
||||
- `validate`: enforce required fields, anti-spam checks, shape checks, consent checks
|
||||
- `handle`: execute the business logic
|
||||
- `return`: normalize the response payload for the frontend
|
||||
|
||||
### GET flow
|
||||
|
||||
`handle` → `return`
|
||||
|
||||
GET actions are useful for utility endpoints, signed links, status endpoints, or controlled data retrieval that is not a collection read.
|
||||
|
||||
## Recommended website patterns
|
||||
|
||||
### Contact form
|
||||
|
||||
Recommended behavior:
|
||||
|
||||
- public `POST` action
|
||||
- validate required fields, email format, consent, and anti-spam signal
|
||||
- send mail or queue message handling
|
||||
- optionally persist a normalized inquiry record
|
||||
- return a small stable payload for the frontend
|
||||
|
||||
### Newsletter signup
|
||||
|
||||
Recommended behavior:
|
||||
|
||||
- public `POST` action
|
||||
- validate email and consent
|
||||
- call external provider or create local opt-in entry
|
||||
- keep provider-specific logic in the action, not in the frontend
|
||||
|
||||
### Quote or booking request
|
||||
|
||||
Recommended behavior:
|
||||
|
||||
- public `POST` action
|
||||
- transform raw form data into a normalized structure in `bind`
|
||||
- validate business rules in `validate`
|
||||
- persist or forward the request in `handle`
|
||||
|
||||
### Webhook receiver
|
||||
|
||||
Recommended behavior:
|
||||
|
||||
- restricted `POST` action
|
||||
- verify secret/signature before any state change
|
||||
- keep webhook-specific logic isolated from public website forms
|
||||
|
||||
## Validation rules
|
||||
|
||||
Validation belongs server-side even when the frontend already validates.
|
||||
|
||||
Always validate:
|
||||
|
||||
- required fields
|
||||
- string lengths
|
||||
- email/phone formats when applicable
|
||||
- consent flags
|
||||
- expected enums or modes
|
||||
- anti-spam or rate-limit conditions
|
||||
|
||||
Do not trust the frontend form shape.
|
||||
|
||||
## Permissions and CORS
|
||||
|
||||
Actions use the same permission model as collections.
|
||||
|
||||
For public website forms:
|
||||
|
||||
- keep only the needed methods public
|
||||
- avoid opening `GET` unless the use case needs it
|
||||
- use action-level CORS only when the frontend origin truly differs from the project default
|
||||
|
||||
Public form access should be narrow, explicit, and auditable.
|
||||
|
||||
## Persistence strategy
|
||||
|
||||
Not every form submission belongs in a collection.
|
||||
|
||||
Choose persistence deliberately:
|
||||
|
||||
- no persistence: mail, webhook, or third-party API only
|
||||
- minimal persistence: store the normalized request for internal staff
|
||||
- full persistence: store and manage lifecycle in a dedicated collection
|
||||
|
||||
If editors must browse, triage, export, or annotate the data in Nova, add a dedicated collection instead of overloading the action itself.
|
||||
|
||||
## Frontend integration
|
||||
|
||||
Frontend forms should submit to actions through the normal API layer.
|
||||
|
||||
Keep the frontend responsible for:
|
||||
|
||||
- collecting input
|
||||
- disabled/loading state
|
||||
- optimistic or conservative UX
|
||||
- success and error messages
|
||||
|
||||
Keep the backend responsible for:
|
||||
|
||||
- real validation
|
||||
- side effects
|
||||
- persistence
|
||||
- normalization of response shape
|
||||
|
||||
## Response design
|
||||
|
||||
Return a small stable payload that the frontend can rely on.
|
||||
|
||||
Typical response examples:
|
||||
|
||||
```json
|
||||
{ "ok": true, "message": "Danke für Ihre Nachricht." }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "ok": true, "nextStep": "confirm-email" }
|
||||
```
|
||||
|
||||
Avoid leaking internal implementation details or raw provider responses to the frontend.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- Creating empty fake collections just to receive POST requests
|
||||
- Moving validation only into the browser
|
||||
- Sending third-party API credentials from the frontend
|
||||
- Returning unstable error shapes
|
||||
- Mixing public forms and internal admin workflows into one hook without boundaries
|
||||
- Persisting everything by default without a real editorial or operational need
|
||||
|
||||
## Verification checklist
|
||||
|
||||
After adding an action-based workflow, verify all of these:
|
||||
|
||||
1. The action is declared in `api/config.yml`.
|
||||
2. The hook chain exists and has the intended steps.
|
||||
3. Invalid submissions fail with useful status and message.
|
||||
4. Valid submissions trigger the intended side effects.
|
||||
5. Public permissions are no broader than necessary.
|
||||
6. The frontend handles success and failure predictably.
|
||||
7. `yarn validate` stays clean.
|
||||
|
||||
## What an LLM should inspect first
|
||||
|
||||
When asked to add a website form or endpoint on this starter, inspect in this order:
|
||||
|
||||
1. `tibi-server/docs/19-actions.md`
|
||||
2. `api/config.yml`
|
||||
3. related hooks in `api/hooks/`
|
||||
4. the frontend form/API surface
|
||||
5. whether persistence belongs in a collection too
|
||||
|
||||
This prevents the common mistake of starting with a fake collection when the feature is really an action.
|
||||
Reference in New Issue
Block a user