diff --git a/static/css/01-agent-chat.css b/static/css/01-agent-chat.css
index 8686048a8..0afdf6e58 100644
--- a/static/css/01-agent-chat.css
+++ b/static/css/01-agent-chat.css
@@ -6523,10 +6523,8 @@ body.bg-pattern-ascii-fireflies {
}
.confirm-btn-secondary { background:var(--bg); color:var(--fg); }
.confirm-btn-secondary:hover { background:var(--border); }
- #styled-confirm-ok {
- position: relative;
- top: 2px;
- }
+ #styled-confirm-ok .styled-confirm-btn-label,
+ #styled-confirm-alt .styled-confirm-btn-label,
#styled-confirm-cancel .styled-confirm-cancel-label {
position:relative;
top:2px;
diff --git a/static/index.html b/static/index.html
index 3c3a95c30..623ed455d 100644
--- a/static/index.html
+++ b/static/index.html
@@ -247,32 +247,32 @@
real request is discarded and the font fetched a second time. -->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
@@ -2709,7 +2709,7 @@
-
+
diff --git a/static/js/ui.js b/static/js/ui.js
index e6b4e96e1..86168e9e6 100644
--- a/static/js/ui.js
+++ b/static/js/ui.js
@@ -776,14 +776,16 @@ export function styledConfirm(message, { confirmText = 'Confirm', cancelText = '
if (titleEl) titleEl.textContent = title || 'Confirm';
msgEl.textContent = message;
+ // Labels sit in spans so CSS can nudge the text without moving the button.
+ const btnLabel = (text) => Object.assign(document.createElement('span'), { className: 'styled-confirm-btn-label', textContent: text });
// Use the affirmative label consistently for generic confirmations.
- okBtn.textContent = confirmText === 'OK' ? 'Yes' : confirmText;
+ okBtn.replaceChildren(btnLabel(confirmText === 'OK' ? 'Yes' : confirmText));
cancelBtn.textContent = '';
const cancelLabel = document.createElement('span');
cancelLabel.className = 'styled-confirm-cancel-label';
cancelLabel.textContent = cancelText;
cancelBtn.appendChild(cancelLabel);
- altBtn.textContent = alternateText || '';
+ altBtn.replaceChildren(btnLabel(alternateText || ''));
okBtn.className = danger ? 'confirm-btn confirm-btn-danger' : 'confirm-btn confirm-btn-primary';
cancelBtn.className = 'confirm-btn confirm-btn-secondary';
altBtn.className = 'confirm-btn confirm-btn-secondary';
diff --git a/static/sw.js b/static/sw.js
index 65fd3c012..643c8f3f9 100644
--- a/static/sw.js
+++ b/static/sw.js
@@ -7,7 +7,7 @@
// - Other static assets (images/fonts/libs): cache-first with bg refresh.
// - API / non-GET: never cached.
// Bump CACHE_NAME whenever the precache list or SW logic changes.
-const CACHE_NAME = 'odysseus-v1136-pr40-editor-precache';
+const CACHE_NAME = 'odysseus-v1137-pr40-editor-precache';
// KaTeX resolves these from its own stylesheet, so caching the CSS without them
// gives offline math fallback glyphs instead of proper typesetting.
@@ -41,28 +41,28 @@ const PRECACHE = [
'/static/js/modalVisibility.js',
'/static/js/compare/sse.js',
'/',
- '/static/css/00-tokens.css?v=20261008confirmwrap1',
- '/static/css/01-agent-chat.css?v=20261008confirmwrap1',
- '/static/css/02-compare.css?v=20261008confirmwrap1',
- '/static/css/03-agent-chat.css?v=20261008confirmwrap1',
- '/static/css/04-memory.css?v=20261008confirmwrap1',
- '/static/css/05-documents.css?v=20261008confirmwrap1',
- '/static/css/06-admin-settings.css?v=20261008confirmwrap1',
- '/static/css/07-documents.css?v=20261008confirmwrap1',
- '/static/css/08-skills.css?v=20261008confirmwrap1',
- '/static/css/09-gallery.css?v=20261008confirmwrap1',
- '/static/css/10-cookbook.css?v=20261008confirmwrap1',
- '/static/css/11-tasks.css?v=20261008confirmwrap1',
- '/static/css/12-gallery.css?v=20261008confirmwrap1',
- '/static/css/13-image-editor.css?v=20261008confirmwrap1',
- '/static/css/14-email.css?v=20261008confirmwrap1',
- '/static/css/15-notes.css?v=20261008confirmwrap1',
- '/static/css/16-calendar.css?v=20261008confirmwrap1',
- '/static/css/17-research.css?v=20261008confirmwrap1',
- '/static/css/documents-gallery-editor.css?v=20261008confirmwrap1',
- '/static/css/email-calendar-notes-tasks.css?v=20261008confirmwrap1',
- '/static/css/cookbook-research-memory-settings.css?v=20261008confirmwrap1',
- '/static/app.js?v=20261008confirmwrap1',
+ '/static/css/00-tokens.css?v=20261021confirmalign1',
+ '/static/css/01-agent-chat.css?v=20261021confirmalign1',
+ '/static/css/02-compare.css?v=20261021confirmalign1',
+ '/static/css/03-agent-chat.css?v=20261021confirmalign1',
+ '/static/css/04-memory.css?v=20261021confirmalign1',
+ '/static/css/05-documents.css?v=20261021confirmalign1',
+ '/static/css/06-admin-settings.css?v=20261021confirmalign1',
+ '/static/css/07-documents.css?v=20261021confirmalign1',
+ '/static/css/08-skills.css?v=20261021confirmalign1',
+ '/static/css/09-gallery.css?v=20261021confirmalign1',
+ '/static/css/10-cookbook.css?v=20261021confirmalign1',
+ '/static/css/11-tasks.css?v=20261021confirmalign1',
+ '/static/css/12-gallery.css?v=20261021confirmalign1',
+ '/static/css/13-image-editor.css?v=20261021confirmalign1',
+ '/static/css/14-email.css?v=20261021confirmalign1',
+ '/static/css/15-notes.css?v=20261021confirmalign1',
+ '/static/css/16-calendar.css?v=20261021confirmalign1',
+ '/static/css/17-research.css?v=20261021confirmalign1',
+ '/static/css/documents-gallery-editor.css?v=20261021confirmalign1',
+ '/static/css/email-calendar-notes-tasks.css?v=20261021confirmalign1',
+ '/static/css/cookbook-research-memory-settings.css?v=20261021confirmalign1',
+ '/static/app.js?v=20261021confirmalign1',
'/static/js/storage.js',
'/static/js/appConfig.js',
'/static/js/ui.js?v=20260916largetoolscroll1',
diff --git a/tests/test_dialog_aria.py b/tests/test_dialog_aria.py
index f23e252e6..06ef33d14 100644
--- a/tests/test_dialog_aria.py
+++ b/tests/test_dialog_aria.py
@@ -58,6 +58,24 @@ def test_styled_confirm_cancel_or_close_label_is_shifted_without_moving_button()
assert "top:2px;" in rule
+def test_styled_confirm_ok_and_alternate_labels_shift_without_moving_buttons():
+ # Moving #styled-confirm-ok itself (position:relative; top:2px) left it 2px
+ # below Cancel and the alternate button. The OK and alternate labels get the
+ # same label nudge as Cancel instead, and no rule moves a footer button.
+ css = app_css()
+
+ assert "okBtn.replaceChildren(btnLabel(" in _UI
+ assert "altBtn.replaceChildren(btnLabel(" in _UI
+ rule_start = css.index("#styled-confirm-ok .styled-confirm-btn-label")
+ rule = css[rule_start:css.index("}", rule_start)]
+ assert "#styled-confirm-alt .styled-confirm-btn-label" in rule
+ assert "position:relative;" in rule and "top:2px;" in rule
+ for selector in ("#styled-confirm-ok {", "#styled-confirm-alt {", "#styled-confirm-cancel {"):
+ if selector in css:
+ block = css[css.index(selector):].split("}", 1)[0]
+ assert "top:" not in block.replace(" ", ""), selector
+
+
def test_styled_confirm_buttons_grow_to_fit_wrapped_labels():
# The global `button { height:32px }` held a two-line label (e.g.
# "Delete chat and images") to one line's height, so its second line