# Effortless — agent guide This project runs on Effortless, a single-binary web framework: Lua handlers, `.x.html` views, EQL queries and a declarative SQLite schema. This file holds what you need before you know what to look up. The full reference is inside the `effortless` binary, matches the installed version, and is read in parts: ``` effortless docs eql # a section by name or number (--list names them) effortless docs --search sync_rows # every paragraph that mentions a term effortless docs # the whole reference ``` ## Do not guess Rails, Laravel, Next.js, Jinja, Vue and Tailwind are close to Effortless and wrong in the details. A guessed function, option or syntax usually parses and then fails at runtime, or does the wrong thing quietly. 1. Before working in an area, read its section below, once per session. 2. Before using a function, option or column rule you have not seen in the docs output or in this project's code, run `effortless docs --search `. No result means it does not exist. 3. When `effortless check` or a runtime error names a docs topic, read it before changing code. Calling a name an `effortless.*` module does not have is an error that names the closest real one. | Task | Read | |---|---| | Page, handler, action, guard, redirect, JSON endpoint | `effortless docs routing` | | Query, insert, update, join table, transaction | `effortless docs eql` | | View, layout, component, template expression | `effortless docs markup` | | Table, column, validation rule, migration | `effortless docs schema` | | Sign-in, CSRF, error messages on a form, password reset | `effortless docs auth` | | Upload, outbound HTTP, subprocess, JSON, logging | `effortless docs files` | | A complete feature to copy: CRUD, pagination, CSV, tests | `effortless docs recipes` | | Browser-side interaction (islands) | `effortless docs inlay` | | CSS classes | `effortless docs styling` | | `app/lib` modules, commands, cron, jobs | `effortless docs domain` | | Configuration, environment variables, admin | `effortless docs configuration` | | Deploy, production data | `effortless docs deploy` | | Mail, AI, realtime | `effortless docs advanced` | | Commands for checking your work | `effortless docs verification` | ## Where things live ``` routes/todos/todos.lua handlers for /todos (get, post, actions, guard) routes/todos/todos.x.html view for /todos routes/todos/[id]/[id].lua /todos/:id, request.params.id routes/_layout.x.html wraps every page; renders app/schema/schema.lua the schema; the database converges on it app/lib/ shared Lua, loaded with require "name" app/components/ markup components and islands tests/ Lua tests, run by effortless test ``` ## Wrong guesses that parse - `eql "select …"` returns an array, `limit 1` included. `eql.one` returns a row or nil; `eql.find` returns a row or answers 404. Never write `or {}` after `eql "…"`. - `return eql "… $name"` cannot see the local `name`: a tail call drops the caller's locals before capture. Assign to a local, then return it. - A message computed in a POST reaches the page only as `return { _validation_errors = { field = msg }, _old = request.form }`. `{ error = msg }` is discarded by the redirect. - EQL columns in `where`, `order by` and `group by` take a leading dot (`.user_id`); field lists do not (`{ id, title }`). There is no raw SQL. - Validation rules live in the schema and run on every write. Do not re-check them in the handler. - Views use JavaScript expressions (`&&`, `??`, `?.`) and control flow as attributes (`@if={…}`, `@each={xs as x}`). Format dates and markdown in the handler, not the view. - CSS classes are mingled (`p:16 flex:between|center c:#4b5563`), not Tailwind. - Nothing is autoloaded: a route file starts with `local eql = require "effortless.eql"`. `eql` is a global only in `effortless eval` and tests. ## Verify every change ``` effortless check # after every edit; fix what it reports first effortless request GET /todos --as ada@example.com # run a route, print the response effortless request GET /todos --data --as 1 # the data the view gets effortless eval 'return eql.one "select todos"' # one-shot Lua in app context effortless test # tests/*.lua against a fresh database effortless migrate --dry-run # after a schema change ``` Commands work on the project in the current directory; `effortless -C ` runs one in another. Every feature gets a test with one success and one failure path. Work is done when `effortless check` and `effortless test` are both green. ## Hard rules 1. Never build a query by string concatenation; bind values with `$name`. 2. Every table of user data has an `owner = true` column; in a request every query is scoped by it to the signed-in user. `eql.unscoped` is the only way past it, for a query that crosses users on purpose. 3. `{@html}` only on HTML the framework produced (`format.markdown`), never on user input. 4. Schema changes go in `app/schema/schema.lua` only; there are no migration files. 5. One route is one directory; the file name matches the directory name. 6. Logs go to stderr. `--json` stdout is exactly one JSON document. 7. Commit messages are one line, in the imperative, saying what changed: no body, no attribution, no trailers. 8. App Lua runs with the server's privileges: request input never reaches `io`, `os.execute`, a `proc.run` argument or `load` unchecked. `effortless docs privileges`.