fix: don't close modal on inner HTMX requests.

changelog
This commit is contained in:
Per Stark
2026-06-12 15:08:51 +02:00
parent 4cd428185f
commit 4947d48ecf
5 changed files with 20 additions and 95 deletions
+1
View File
@@ -1,6 +1,7 @@
# Changelog # Changelog
## Unreleased ## Unreleased
- Performance: ingestion skips per-task index rebuild; worker runs scheduled `REBUILD INDEX` (default every 24h via `index_rebuild_interval_secs`, `0` disables) - Performance: ingestion skips per-task index rebuild; worker runs scheduled `REBUILD INDEX` (default every 24h via `index_rebuild_interval_secs`, `0` disables)
- Fix: regression re suggestion of relationships
## 1.0.3 (2026-06-12) ## 1.0.3 (2026-06-12)
- Search: filter results by type — knowledge entities, ingested content, or both - Search: filter results by type — knowledge entities, ingested content, or both
-93
View File
@@ -285,37 +285,6 @@
} }
} }
} }
.drawer-open {
> .drawer-side {
overflow-y: auto;
}
> .drawer-toggle {
display: none;
& ~ .drawer-side {
pointer-events: auto;
visibility: visible;
position: sticky;
display: block;
width: auto;
overscroll-behavior: auto;
opacity: 100%;
& > .drawer-overlay {
cursor: default;
background-color: transparent;
}
& > *:not(.drawer-overlay) {
translate: 0%;
[dir="rtl"] & {
translate: 0%;
}
}
}
&:checked ~ .drawer-side {
pointer-events: auto;
visibility: visible;
}
}
}
.drawer-toggle { .drawer-toggle {
position: fixed; position: fixed;
height: calc(0.25rem * 0); height: calc(0.25rem * 0);
@@ -1074,22 +1043,6 @@
grid-row-start: 1; grid-row-start: 1;
min-width: calc(0.25rem * 0); min-width: calc(0.25rem * 0);
} }
.chat-image {
grid-row: span 2 / span 2;
align-self: flex-end;
}
.chat-footer {
grid-row-start: 3;
display: flex;
gap: calc(0.25rem * 1);
font-size: 0.6875rem;
}
.chat-header {
grid-row-start: 1;
display: flex;
gap: calc(0.25rem * 1);
font-size: 0.6875rem;
}
.container { .container {
width: 100%; width: 100%;
@media (width >= 40rem) { @media (width >= 40rem) {
@@ -1796,9 +1749,6 @@
.w-10 { .w-10 {
width: calc(var(--spacing) * 10); width: calc(var(--spacing) * 10);
} }
.w-11 {
width: calc(var(--spacing) * 11);
}
.w-11\/12 { .w-11\/12 {
width: calc(11/12 * 100%); width: calc(11/12 * 100%);
} }
@@ -1862,9 +1812,6 @@
.flex-none { .flex-none {
flex: none; flex: none;
} }
.flex-shrink {
flex-shrink: 1;
}
.flex-shrink-0 { .flex-shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
@@ -1877,13 +1824,6 @@
.grow { .grow {
flex-grow: 1; flex-grow: 1;
} }
.border-collapse {
border-collapse: collapse;
}
.-translate-y-1 {
--tw-translate-y: calc(var(--spacing) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-1\/2 { .-translate-y-1\/2 {
--tw-translate-y: calc(calc(1/2 * 100%) * -1); --tw-translate-y: calc(calc(1/2 * 100%) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y); translate: var(--tw-translate-x) var(--tw-translate-y);
@@ -1956,9 +1896,6 @@
.justify-start { .justify-start {
justify-content: flex-start; justify-content: flex-start;
} }
.gap-0 {
gap: calc(var(--spacing) * 0);
}
.gap-0\.5 { .gap-0\.5 {
gap: calc(var(--spacing) * 0.5); gap: calc(var(--spacing) * 0.5);
} }
@@ -2091,9 +2028,6 @@
.border-base-200 { .border-base-200 {
border-color: var(--color-base-200); border-color: var(--color-base-200);
} }
.border-base-content {
border-color: var(--color-base-content);
}
.border-base-content\/10 { .border-base-content\/10 {
border-color: var(--color-base-content); border-color: var(--color-base-content);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -2130,9 +2064,6 @@
.bg-transparent { .bg-transparent {
background-color: transparent; background-color: transparent;
} }
.bg-warning {
background-color: var(--color-warning);
}
.bg-warning\/10 { .bg-warning\/10 {
background-color: var(--color-warning); background-color: var(--color-warning);
@supports (color: color-mix(in lab, red, red)) { @supports (color: color-mix(in lab, red, red)) {
@@ -2151,9 +2082,6 @@
.loading-spinner { .loading-spinner {
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");
} }
.mask-repeat {
mask-repeat: repeat;
}
.fill-current { .fill-current {
fill: currentcolor; fill: currentcolor;
} }
@@ -2184,9 +2112,6 @@
.p-8 { .p-8 {
padding: calc(var(--spacing) * 8); padding: calc(var(--spacing) * 8);
} }
.px-1 {
padding-inline: calc(var(--spacing) * 1);
}
.px-1\.5 { .px-1\.5 {
padding-inline: calc(var(--spacing) * 1.5); padding-inline: calc(var(--spacing) * 1.5);
} }
@@ -2341,9 +2266,6 @@
--tw-tracking: var(--tracking-widest); --tw-tracking: var(--tracking-widest);
letter-spacing: var(--tracking-widest); letter-spacing: var(--tracking-widest);
} }
.text-wrap {
text-wrap: wrap;
}
.break-words { .break-words {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
@@ -2410,17 +2332,6 @@
.italic { .italic {
font-style: italic; font-style: italic;
} }
.underline {
text-decoration-line: underline;
}
.swap-active {
.swap-off {
opacity: 0%;
}
.swap-on {
opacity: 100%;
}
}
.opacity-0 { .opacity-0 {
opacity: 0%; opacity: 0%;
} }
@@ -2514,10 +2425,6 @@
--tw-duration: 300ms; --tw-duration: 300ms;
transition-duration: 300ms; transition-duration: 300ms;
} }
.ease-in-out {
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
.ease-out { .ease-out {
--tw-ease: var(--ease-out); --tw-ease: var(--ease-out);
transition-timing-function: var(--ease-out); transition-timing-function: var(--ease-out);
+1 -1
View File
@@ -12,7 +12,7 @@
{# Default: one outer #modal_form. Modals with multiple forms (scratchpad editor) {# Default: one outer #modal_form. Modals with multiple forms (scratchpad editor)
override modal_form_open / modal_form_close — nested <form> is invalid HTML. #} override modal_form_open / modal_form_close — nested <form> is invalid HTML. #}
{% block modal_form_open %}<form id="modal_form" hx-on::after-request="if(event.detail.successful) document.getElementById('body_modal').close()" {% block form_attributes %}{% endblock %}>{% endblock %} {% block modal_form_open %}<form id="modal_form" hx-on::after-request="if(event.detail.successful && event.detail.elt === event.currentTarget) document.getElementById('body_modal').close()" {% block form_attributes %}{% endblock %}>{% endblock %}
<div class="flex flex-col flex-1 gap-5"> <div class="flex flex-col flex-1 gap-5">
{% block modal_content %}{% endblock %} {% block modal_content %}{% endblock %}
</div> </div>
+16
View File
@@ -333,6 +333,22 @@ async fn snapshot_new_entity_modal() {
snapshot_settings().bind(|| insta::assert_snapshot!("new_entity_modal", body)); snapshot_settings().bind(|| insta::assert_snapshot!("new_entity_modal", body));
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn modal_form_after_request_ignores_inner_htmx_requests() {
let (app, db) = build_test_app().await;
let cookie = seeded_cookie(&app, &db).await;
let modal = get_html(&app, "/knowledge-entity/new", Some(&cookie)).await;
// Inner buttons (e.g. Suggest Relationships) bubble htmx:afterRequest to
// #modal_form; closing must only run when the form itself submitted.
assert!(
modal.contains(
r#"hx-on::after-request="if(event.detail.successful && event.detail.elt === event.currentTarget) document.getElementById('body_modal').close()"#
),
"#modal_form should ignore bubbled after-request events from child elements"
);
}
async fn sign_in(app: &Router, email: &str, password: &str) -> String { async fn sign_in(app: &Router, email: &str, password: &str) -> String {
let response = app let response = app
.clone() .clone()
@@ -1,5 +1,6 @@
--- ---
source: html-router/tests/router_integration.rs source: html-router/tests/router_integration.rs
assertion_line: 333
expression: body expression: body
--- ---
<dialog id="body_modal" class="modal"> <dialog id="body_modal" class="modal">
@@ -18,7 +19,7 @@ expression: body
</button> </button>
<form id="modal_form" hx-on::after-request="if(event.detail.successful) document.getElementById('body_modal').close()" <form id="modal_form" hx-on::after-request="if(event.detail.successful && event.detail.elt === event.currentTarget) document.getElementById('body_modal').close()"
hx-post="/knowledge-entity" hx-post="/knowledge-entity"
hx-target="#knowledge_pane" hx-target="#knowledge_pane"
hx-swap="outerHTML" hx-swap="outerHTML"