491f495c66
- 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.
289 lines
7.6 KiB
Markdown
289 lines
7.6 KiB
Markdown
---
|
|
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.
|