diff --git a/CLAUDE.md b/CLAUDE.md index 81b2fe5..c873b7a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -331,6 +331,62 @@ let (items, navigator) = build_page_view(page, request, RoasterView::from, ROASTER_PAGE_PATH, ROASTER_FRAGMENT_PATH, search); ``` +### Static Assets + +Static files live in `templates/` and are compiled into the binary via `include_str!()`/`include_bytes!()`. Each file needs an explicit route in `application/routes/mod.rs`: + +```rust +.route("/styles.css", get(styles)) +.route("/extract.js", get(extract_js)) +.route("/favicon.ico", get(favicon)) + +async fn extract_js() -> impl IntoResponse { + ( + [("content-type", "application/javascript; charset=utf-8")], + include_str!("../../../templates/extract.js"), + ) +} +``` + +There is no `tower-http` static file serving — all assets are embedded at compile time. + +### AI Extraction Controls + +Pages with AI-powered form filling (roasters, roasts, scan) share a common JavaScript library at `templates/extract.js` served at `/extract.js`. It provides three functions: + +- `triggerPhotoExtract(formId, endpoint, onSuccess)` — opens camera/file picker, reads as data URL +- `extractFromText(formId, endpoint, onSuccess)` — reads text from input field +- `doExtract(formId, endpoint, body, onSuccess)` — POST to API, toggle waiting state, call callback on success + +Each page provides only its own `onSuccess` callback (e.g., `fillRoasterForm`, `fillRoastForm`, `fillScanForms`). + +#### Element ID Convention + +The shared library locates DOM elements using the `formId` prefix: + +| Element | ID pattern | Purpose | +|---------|-----------|---------| +| Controls wrapper | `{formId}-extract-controls` | Hidden during extraction | +| Waiting message | `{formId}-extract-waiting` | Shown during extraction (spinner + text) | +| Error paragraph | `{formId}-extract-error` | Shown on failure | +| Text input | `{formId}-extract-text` | Text description input | + +Example `formId` values: `roaster-form`, `roast-form`, `scan`. + +#### Template Structure + +```html +