A filled-in model of the FEATURES.md format that /3-setup-features produces. The example app is a website for a small welding & fabrication shop.
Purpose: Let potential customers request a fabrication quote from the website so the shop stops losing work to unanswered phone calls.
- Visitor lands on the site and clicks "Request a Quote" in the header
- Fills in contact info, job type, and a description of the work
- Optionally attaches drawings or photos (PDF, JPG, PNG)
- Submits and sees a confirmation with the shop's response-time promise
- Shop owner gets an email notification with the request details
- Owner reviews new requests in a simple admin list and marks each one handled
In: public request form, file attachments, database storage, email notification to the shop, admin list with handled/unhandled status Out: pricing calculation, customer accounts, payment, SMS notifications, multi-shop support
/quote — Request a Quote
+--------------------------------------+
| Request a Quote |
| |
| Name [ ] |
| Email [ ] |
| Phone [ ] |
| Job type [ Structural v ] |
| Description [ ] |
| [ ] |
| |
| Drawings/photos |
| +------------------------------+ |
| | Drag files here or browse | |
| +------------------------------+ |
| |
| [ Send request ] |
+--------------------------------------+
/admin/quotes — Request List (auth required)
+----------------------------------------------+
| Quote Requests [ Unhandled v ] |
|----------------------------------------------|
| NEW Mar 4 Dana R. Structural 2 files |
| NEW Mar 3 T. Alvarez Railings 0 files |
| DONE Mar 1 Mike B. Repair 1 file |
+----------------------------------------------+
- Email field: format-validated on blur; inline error below the field
- Phone field: optional, but form requires email or phone before submit enables
- Job type: select with Structural / Railings / Repair / Other
- File dropzone: max 5 files, 10 MB each, PDF/JPG/PNG only; rejected files show a reason
- Send request: disables and shows a spinner during submit; success replaces the form with the confirmation; failure shows a retry-able error above the button
- Admin list rows: click to expand full details; "Mark handled" flips status and moves the row out of the Unhandled filter
- Models:
QuoteRequest(id, name, email, phone, jobType, description, status, createdAt),QuoteAttachment(id, quoteRequestId, fileName, filePath, sizeBytes) submitQuoteRequestserver action — validates, stores request + attachments, sends notification emailGET /admin/quotes— server-rendered list, session requiredmarkQuoteHandledserver action — flips status, session required
- Description required, 20–2000 characters
- A request with neither email nor phone is rejected server-side regardless of client state
- Attachment limits enforced server-side (count, size, MIME type)
- Admin routes and actions require an authenticated session
- Notification email failure must not lose the request — store first, then notify
- A visitor can submit a request with two attachments and see the confirmation
- The request and files appear in the database and the admin list within one refresh
- The shop email arrives with name, job type, and description
- Submitting with a 10-character description shows a validation error and stores nothing
- /admin/quotes redirects to login when visited without a session