Imported from dxnz-id/siwayut-catering (
AGENTS.md). Install upstream withnpx skills add dxnz-id/siwayut-catering. Copyright stays with the author.
AGENTS.md — Siwayut Catering
Project overview
Custom PHP MVC ("Vanilla Framework"). Catering order management — landing page + admin dashboard.
Dev commands
php vanilla serve # Start dev server (default port 8000)
php vanilla serve --port=8080
php vanilla migrate # Run SQL migrations from database/migrations/
php vanilla migrate:fresh # Drop all tables + re-migrate
php vanilla db:seed # Run seeders (AdminSeeder, MenuSeeder, OrderSeeder)
php vanilla key:generate # Generate APP_KEY for .env
php vanilla make:controller # Scaffold controllers/models/services/etc.
php vanilla routes # List registered routes
npm run css:build # Build Tailwind CSS (one-time)
npm run css:watch # Watch + rebuild CSS on changes
npm run serve # Alias for `php vanilla serve`
npm run dev # Run both PHP server + Tailwind watcher (needs concurrently)
Build setup
- Tailwind CSS v4 via
@tailwindcss/cli - Input:
public/assets/css/input.css—@themetokens + custom component CSS - Output:
public/assets/css/app.css(auto-generated, do not edit directly) - Config: no
tailwind.config.js— v4 uses@themeblock ininput.css - NPM scripts in
package.json - Cache busting: manual
?v=Nquerystring on CSS/JS links
Architecture
Directory layout
public/index.php— entrypoint, loads.env+ bootstrap + routesroutes/web.php— web routes (public, auth, user, admin)routes/api.php— JSON API endpointsconfig/bindings.php— DI container wiring (Model → Service → Controller)src/Controllers/— thin controllers, inject services via constructorsrc/Services/— business logic layersrc/Models/— extendsBaseModel. Queries use raw PDO + table name propertysrc/Views/— plain PHP templates (no Blade/Twig)public/assets/css/app.css— generated by Tailwind frominput.csspublic/assets/js/app.js— shared JS (file-upload, LQIP, load-more)
Key patterns
- CSRF token in every POST form:
<?= \App\Core\Csrf::field() ?>or<?= csrf_field() ?> - Component helper:
<?php component('progressive-image', ['src' => ..., 'alt' => ...]) ?> - Session flash for messages/errors via
\App\Core\Session::flash() - JSON response:
\App\Core\Response::jsonSuccess($data)or::jsonError($msg)
Two layouts
- Landing page (
welcome.php) — standalone, no layout wrapper, own parallax/scroll JS - Admin dashboard — uses
main.phplayout (sidebar + navbar) orauth.phplayout (centered card)
Admin routes
All admin routes require middleware: ['auth', 'role:admin']:
/categories,/events,/menus,/orders,/users— full CRUD
Public routes
/— landing page with food gallery + featured menus (load-more via API)/login— admin login/order-form— customer order form/track-order/{id}— order tracking/api/menus?page=N— load-more API (JSON, paginated, filtered by active status)
Progressive images (LQIP)
- Thumbnail at
/uploads/{dir}/thumbs/{file}, full at/uploads/{dir}/{file} - HTML structure:
<span class="progressive-wrap"><img class="progressive-img blur-up" src="thumb" data-full="full"></span> - JS in
app.jsloads full image asynchronously and swaps src - Inline
onerrorfallback on each<img>tag
Database
- MySQL, PDO with
FETCH_ASSOC - Migrations: sequential SQL files in
database/migrations/(e.g.,001_create_users_table.sql) - No ORM —
BaseModelprovidesall(),find(),create(),update(),delete(),paginate() - All queries ORDER BY
created_at DESCby default
CSS conventions (admin)
:rootvariables for all colors/spacing:--color-primary,--color-bg,--color-surface,--color-text, etc.- Landing-page aliases also available:
--accent-gold,--card-border,--bg-dark - Glassmorphism via
backdrop-filter, gold accent (#e58e26), dark background (#09090b) - Component classes:
.card,.btn/.btn-primary,.table-wrapper,.form-input,.sidebar,.navbar - Landing page (
welcome.php) has its own inline<style>that overrides app.css
Gotchas
- No tests. No test runner configured.
- DELETE / PUT / PATCH not used. All mutations use POST + dedicated delete routes.
php vanilla serveuses-t publicflag (document root = public/, index.php handles routing)public/index.phpreturns 404 for HEAD / (known PHP built-in server quirk) — use GET for curl checks.envis loaded manually viaparse_ini_file()inpublic/index.php— no framework env helper- Model paginate calls
BaseModel::paginate()which returns{data, total, per_page, current_page, last_page} - No route model binding — manual
$this->menuService->find($id)in controllers - AI description generation uses OpenAI-compatible API via
AiService(configured in.env)