web: replace dialog with Popover API and CSS anchor positioning
change kxkoloupskptrwzovkpnuxrqkmrzknqm
commit 656a33b256bdba6b4491a82888ae597bfe33b23e
author Alpha Chen <alpha@kejadlen.dev>
date
parent ywyqozso
diff --git a/AGENTS.md b/AGENTS.md
index db93f94..f96ed9b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -52,6 +52,7 @@ tests/
 - **Subtasks are tasks**: `parent_id` on tasks — subtasks get full task capabilities.
 - **Tags**: Free-form labels on tasks via a many-to-many join table (`task_tags`). Used for cross-cutting concerns like `web`, `cli`, `infra`. Filter tasks by tag with `--tag`.
 - **No compile-time checked queries**: Using `sqlx::query_as` with runtime binding, not `query_as!` macros. No need for `DATABASE_URL` at build time.
+- **Web UI browser targets**: Latest Firefox and Safari. Modern APIs (Popover, CSS anchor positioning) are fair game.
 
 ## Testing
 
diff --git a/src/bin/ranger/commands/serve.rs b/src/bin/ranger/commands/serve.rs
index 9f19616..22faf85 100644
--- a/src/bin/ranger/commands/serve.rs
+++ b/src/bin/ranger/commands/serve.rs
@@ -284,11 +284,11 @@ async fn render_board(
                         "ranger" span.sep { "›" }
                         @if backlog_names.len() > 1 {
                             span.backlog-picker {
-                                button.backlog-trigger onclick="document.getElementById('backlog-dialog').show()" {
+                                button.backlog-trigger popovertarget="backlog-menu" {
                                     (backlog_name)
                                     span.backlog-caret { "▾" }
                                 }
-                                dialog #backlog-dialog {
+                                div #backlog-menu.backlog-popover popover="" {
                                     ul.backlog-list {
                                         @for name in &backlog_names {
                                             li {
diff --git a/static/board.js b/static/board.js
index dd28400..298d601 100644
--- a/static/board.js
+++ b/static/board.js
@@ -1,12 +1,4 @@
 (function() {
-    // === Backlog popover ===
-    document.addEventListener('click', function(e) {
-        var dialog = document.getElementById('backlog-dialog');
-        if (dialog && dialog.open && !dialog.contains(e.target) && !e.target.closest('.backlog-trigger')) {
-            dialog.close();
-        }
-    });
-
     // === Keyboard navigation ===
     function getFocusables() {
         return Array.from(document.querySelectorAll(
diff --git a/static/style.css b/static/style.css
index 67aebcc..6e06de3 100644
--- a/static/style.css
+++ b/static/style.css
@@ -130,7 +130,11 @@ header h1 .sep {
 }
 
 /* === Backlog popover === */
-dialog {
+.backlog-trigger {
+  anchor-name: --backlog-trigger;
+}
+
+.backlog-popover {
   border: none;
   border-radius: 6px;
   padding: 0;
@@ -138,25 +142,14 @@ dialog {
   color: var(--color-text);
   box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
   min-width: 180px;
+  inset: unset;
   margin: 0;
-  position: absolute;
-  top: 100%;
-  left: 0;
+  position-anchor: --backlog-trigger;
+  top: anchor(bottom);
+  left: anchor(left);
   margin-top: 6px;
 }
 
-dialog::backdrop {
-  background: transparent;
-}
-
-dialog[open] {
-  display: block;
-}
-
-.dialog-header {
-  display: none;
-}
-
 .backlog-list {
   list-style: none;
   padding: 4px;