What you’ll have
- Columns and cards stored in SQLite
- Create and delete for both, with no page reload
- A confirmation step before anything is destroyed
- Data that’s still there after a refresh
Which parts of Feather this uses
Models and mixins
UUIDMixin and TimestampMixin supply the columns every model ends up wanting.Services
Business logic, with the transaction handled by a decorator.
HTMX
Routes that return HTML fragments instead of JSON.
Exceptions
A missing row raises
NotFoundError and becomes a 404 on its own.Step 1: Scaffold
- Continue from part 1
- Start fresh
Part 1 had no database, so the app has no
models/ package, no migrations/ directory
and no DATABASE_URL. Add them:feather db init exists for exactly this case. In an app scaffolded with a database
it fails, because migrations/ is already there.Step 2: Run the prompt
The prompt
Step 3: Test it
- Click Add Column and give it a name. It appears without a reload.
- Type in the input at the bottom of a column and press Enter. The card appears, and the input clears.
- Hover a card and click the X. Confirm the dialog, and the card goes.
- Click the X on a column header. Confirm, and the column and its cards go together.
- Refresh the page. Everything is still there.
feather routes is worth a look here. You should see the page route at / plus four
/htmx/ routes, which is everything this part added.
What the framework did
The mixins wrote your boilerplate.UUIDMixin gave every row a UUID primary key and
TimestampMixin gave it created_at and updated_at. Neither appears in the model body.
See Models.
Deleting a column deleted its cards. That’s cascade="all, delete-orphan" on the
relationship. There is no cleanup code in the service.
The routes stayed thin. Each handler parses the request, calls a service and returns a
template. A handler doing more than that is reported by
feather check as the fat-route warning, which is why the logic ended
up in services.
A DELETE returns an empty string. HTMX swaps that emptiness over the element, which is
how the row disappears. A POST returns the rendered partial for the thing it just made.
Both patterns repeat for the rest of the series. See HTMX.
The confirm dialog isn’t the browser’s. hx-confirm is backed by Feather’s
confirm_modal component, so it’s styled and dark-mode aware. Native confirm() is a
feather check error.
Next: drag and drop
The first part that needs real state in the browser.