diff --git a/examples/html_examples/README.md b/examples/html_examples/README.md
index 55fe3f3..25b11eb 100644
--- a/examples/html_examples/README.md
+++ b/examples/html_examples/README.md
@@ -19,19 +19,49 @@ Names match the htmx example URL slug exactly, e.g. `modal-custom` from
`https://htmx.org/examples/modal-custom/`. Rust resource names use normal
identifier spelling only where the language requires it.
-| htmx example slug | hemx idiom | Proof anchor |
-| --- | --- | --- |
-| `click-to-edit` | A read view and edit form are the same generated `contact_card` partial; the server toggles `editing` and returns `gallery::contact_card.replace(...)`. | `templates/partials/contact_card.heml`, `contact_card_handlers::edit_contact`, `save_contact` |
-| `edit-row` | A table row is a keyed `.heml` partial with generated edit/save forms; no selector target strings. | `templates/partials/editable_row.heml`, `editable_row_handlers::edit_row`, `save_row` |
-| `delete-row` | Server state removes the row and returns `gallery::editable_row.remove(id)`. | `editable_row_handlers::delete_row` |
-| `inline-validation` | A generated form reports field failure with `validate_email_form.error(...)`, focuses the field, and updates status text. | `templates/gallery.heml`, `gallery_handlers::validate_email` |
-| `click-to-load` | The server owns the loaded count and returns generated keyed `loaded_row` replacements plus status text. | `gallery_handlers::load_more`, `LoadedRow` |
-| `active-search` | The search form posts a query; the server derives result rows and replaces generated `search_result` keyed partials. | `gallery_handlers::search`, `SearchResult` |
+Status legend:
+
+- **implemented**: copyable `.heml` and server handlers exist in this example.
+- **integration-owned**: use hemx generated resources plus app/host/browser policy;
+ do not grow hemx core for the policy.
+- **refused**: would clone htmx/client framework behavior or a third-party UI kit.
+- **deferred**: useful, but needs a later vertical slice and proof before becoming
+ a copyable hemx pattern.
+
+| htmx example slug | Status | hemx idiom / boundary | Proof anchor |
+| --- | --- | --- | --- |
+| `click-to-edit` | implemented | A read view and edit form are the same generated `contact_card` partial; the server toggles `editing` and returns `gallery::contact_card.replace(...)`. | `templates/partials/contact_card.heml`, `contact_card_handlers::edit_contact`, `save_contact` |
+| `bulk-update` | deferred | Same generated-form path as inline validation, but needs a real multi-row selection/write slice so batch semantics are tested instead of claimed. | Next slice should add keyed batch rows plus one server-owned bulk command. |
+| `click-to-load` | implemented | The server owns the loaded count and returns generated keyed `loaded_row` replacements plus status text. | `gallery_handlers::load_more`, `LoadedRow` |
+| `delete-row` | implemented | Server state removes the row and returns `gallery::editable_row.remove(id)`. | `editable_row_handlers::delete_row` |
+| `edit-row` | implemented | A table row is a keyed `.heml` partial with generated edit/save forms; no selector target strings. | `templates/partials/editable_row.heml`, `editable_row_handlers::edit_row`, `save_row` |
+| `lazy-load` | deferred | Should be the same loaded-row resource with a browser-owned visibility trigger; needs a tiny runtime convention or explicit app JS policy before copy/paste. | Not implemented in core gallery yet. |
+| `inline-validation` | implemented | A generated form reports field failure with `validate_email_form.error(...)`, focuses the field, and updates status text. | `templates/gallery.heml`, `gallery_handlers::validate_email` |
+| `infinite-scroll` | integration-owned | This is load-more plus scroll/visibility policy; hemx should not own infinite-scroll state without an app proof. | Use `click-to-load` until a product slice proves scroll policy. |
+| `active-search` | implemented | The search form posts a query; the server derives result rows and replaces generated `search_result` keyed partials. | `gallery_handlers::search`, `SearchResult` |
+| `progress-bar` | deferred | Needs a real job/progress source and polling or push convention; a fake timer would be a trophy demo. | Later slice should prove server-owned job state. |
+| `value-select` | deferred | Dependent selects are ordinary generated form state, but need a copyable domain example and validation path. | Later slice should add two select resources and server-derived options. |
+| `animations` | integration-owned | CSS transitions are presentation policy around generated replacements; hemx should only preserve stable DOM boundaries. | Use keyed partials and app CSS; no core animation framework. |
+| `file-upload` | integration-owned | Upload transport, size limits, progress, storage, and security are app/integration policy. | Needs product-owned upload route before becoming copyable. |
+| `file-upload-input` | integration-owned | Preserving file inputs after errors is browser/security policy; hemx should not fake file state in core effects. | Use app-owned upload form policy. |
+| `reset-user-input` | deferred | Generated form `.clear()` already exists; needs a small copyable pattern showing reset after success and preserved values after failure. | Later slice can add to inline-validation. |
+| `dialogs` | integration-owned | Browser `alert/confirm/prompt` are app policy; hemx can expose event boundaries but should not own dialog UX. | Use native controls or host/app code. |
+| `modal-uikit` | refused | Third-party UI kit integration is not a hemx core pattern. | Keep as app-owned integration. |
+| `modal-bootstrap` | refused | Third-party UI kit integration is not a hemx core pattern. | Keep as app-owned integration. |
+| `modal-custom` | deferred | A custom modal can be a generated partial plus focus/escape policy, but needs accessibility proof before copy/paste. | Later slice should include keyboard/focus tests. |
+| `tabs-hateoas` | deferred | Good hemx fit: server-owned selected tab and generated tab panel replacement; needs a focused slice. | Later slice should add one tab group. |
+| `tabs-javascript` | refused | Client-owned tab state is exactly what generated server-owned state is meant to avoid unless a product needs it. | Prefer `tabs-hateoas`. |
+| `keyboard-shortcuts` | integration-owned | Keyboard policy belongs to the app/host; hemx should only receive explicit events. | Use `Effect::event`/app JS when needed. |
+| `sortable` | integration-owned | Drag/drop ordering needs a browser library or pointer policy plus server reorder command. | Keep Sortable.js as app-owned integration until proven reusable. |
+| `update-other-content` | implemented | Generated effects can update multiple slots from one handler; validation and search already update status plus rows/errors. | `validate_email`, `search` handlers. |
+| `confirm` | integration-owned | Confirmation wording and irreversible-action policy belong to the app; hemx should not own a global confirm system. | Use native confirm/app dialog around generated delete forms. |
+| `async-auth` | integration-owned | Token refresh/auth sessions belong to auth/session integration, not hemx core. | See auth/session recipe boundary. |
+| `web-components` | integration-owned | Shadow DOM/custom elements are host integration; hemx can emit events but should not pierce component internals. | Use app-owned web component adapters. |
+| `move-before` | refused | Experimental DOM preservation API is not a stable hemx contract. | Avoid until browser support and a product need make it boring. |
## Boundary
-This gallery deliberately skips the trophy-list patterns that would need a real
-product reason or a browser/plugin policy first: drag-and-drop, modal libraries,
-file upload progress, infinite scroll, web components, and authentication token
-refresh. Add those only as vertical slices when hemx has a boring product-owned
-answer, not as demo-only JavaScript.
+Implemented rows must remain runnable hemx behavior. Deferred/integration-owned/refused
+rows are not failures; they prevent a trophy checklist from turning hemx into a
+client framework. Promote a deferred row only when the slice proves a reusable,
+boring contract with `.heml`, generated resources, server-owned state, and tests.
diff --git a/examples/html_examples/src/main.rs b/examples/html_examples/src/main.rs
index 37b2ab8..1f33e2c 100644
--- a/examples/html_examples/src/main.rs
+++ b/examples/html_examples/src/main.rs
@@ -482,6 +482,46 @@ mod tests {
)
}
+ #[test]
+ fn readme_maps_every_htmx_example_slug() {
+ let readme = include_str!("../README.md");
+ for slug in [
+ "click-to-edit",
+ "bulk-update",
+ "click-to-load",
+ "delete-row",
+ "edit-row",
+ "lazy-load",
+ "inline-validation",
+ "infinite-scroll",
+ "active-search",
+ "progress-bar",
+ "value-select",
+ "animations",
+ "file-upload",
+ "file-upload-input",
+ "reset-user-input",
+ "dialogs",
+ "modal-uikit",
+ "modal-bootstrap",
+ "modal-custom",
+ "tabs-hateoas",
+ "tabs-javascript",
+ "keyboard-shortcuts",
+ "sortable",
+ "update-other-content",
+ "confirm",
+ "async-auth",
+ "web-components",
+ "move-before",
+ ] {
+ assert!(
+ readme.contains(&format!("`{slug}`")),
+ "missing htmx example slug {slug}"
+ );
+ }
+ }
+
#[tokio::test]
async fn gallery_covers_core_html_patterns_with_generated_resources() {
let state = Arc::new(GalleryState::seeded());