From 7e3163e20d68a7f887673075f7a51ff6cc178da0 Mon Sep 17 00:00:00 2001 From: Paul Lizer Date: Wed, 19 Aug 2026 13:45:46 -0400 Subject: [PATCH 1/2] Point Admin Settings cross-tab links at cards instead of tabs Cross-references between Admin Settings tabs named a tab button directly, for example switchTab(event, 'workspaces-tab'). That couples every link to a tab id, so a rename or regroup breaks it silently: the button no longer exists, no pane is activated, and the only symptom is a console warning and a URL hash pointing at nothing. Two of the twelve links were already broken this way. Citations referenced Enable Video File Support and Enable Audio File Support with #workspaces, and the prose said to see the Workspaces tab, but both settings live under Search and Extract. Those links sent admins to the wrong tab entirely. Links now declare the card they want with data-admin-link, and the owning tab is resolved from the DOM at click time via closest('.tab-pane'). Cards can move between tabs, and tabs can be renamed or regrouped, without touching a link. The destination card is briefly highlighted so the jump is obvious. This matters now because the information architecture rework renames and regroups most tabs, which would have broken all twelve links at once. switchTab had no callers left and is removed, so the coupling cannot be reintroduced by copy-paste. The new test fails if a link points at a card that does not exist, if the href fallback stops matching, or if the old pattern returns. Also sanitizes the User Agreement preview inline at the sink. It used the same guarded-reassignment shape already corrected for the Home Page Text preview, which reads as unsanitized to static analysis; admin_settings.html now passes a full-file XSS scan rather than only a changed-lines scan. Field names and card ids unchanged at 462 and 110, and the 75 functional test files covering admin_settings.html show the same 33 pre-existing failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- application/single_app/config.py | 2 +- .../static/js/admin/admin_card_links.js | 103 +++++++++++ .../static/js/admin/admin_settings.js | 25 --- .../templates/admin/_panes/citation.html | 8 +- .../admin/_panes/latest-features.html | 16 +- .../templates/admin/_panes/scale.html | 2 +- .../admin/_panes/search-extract.html | 2 +- .../single_app/templates/admin_settings.html | 20 ++- .../ADMIN_SETTINGS_CARD_TARGETED_LINKS_FIX.md | 144 ++++++++++++++++ docs/explanation/release_notes.md | 15 ++ functional_tests/test_admin_card_links.py | 163 ++++++++++++++++++ 11 files changed, 453 insertions(+), 47 deletions(-) create mode 100644 application/single_app/static/js/admin/admin_card_links.js create mode 100644 docs/explanation/fixes/ADMIN_SETTINGS_CARD_TARGETED_LINKS_FIX.md create mode 100644 functional_tests/test_admin_card_links.py diff --git a/application/single_app/config.py b/application/single_app/config.py index 39d9a8a90..8dd84f2b2 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -96,7 +96,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.260.007" +VERSION = "0.260.008" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/static/js/admin/admin_card_links.js b/application/single_app/static/js/admin/admin_card_links.js new file mode 100644 index 000000000..9da7cf139 --- /dev/null +++ b/application/single_app/static/js/admin/admin_card_links.js @@ -0,0 +1,103 @@ +// admin_card_links.js +// Resolves links that point at an Admin Settings card rather than at a tab. +// +// Cross-tab links used to name a tab button directly, for example +// switchTab(event, 'workspaces-tab'). That couples the link to a tab id, so a +// tab rename or an information-architecture change silently breaks the link: +// the button no longer exists, no pane is activated, and the URL hash is left +// pointing at nothing. +// +// A link declares the card it wants instead: +// +// +// +// The owning tab is resolved from the DOM at click time, so card ids are the +// only contract. Cards can move between tabs, and tabs can be renamed or +// regrouped, without touching a single link. + +const HIGHLIGHT_CLASS = 'admin-card-link-target'; +const HIGHLIGHT_MS = 1600; + +/** + * Find the tab pane that contains a card. + * @param {string} cardId Element id of the target card. + * @returns {{card: HTMLElement, paneId: string|null}|null} + */ +export function resolveAdminCard(cardId) { + const card = document.getElementById(cardId); + if (!card) { + return null; + } + + const pane = card.closest('.tab-pane'); + return { card, paneId: pane ? pane.id : null }; +} + +/** + * Activate the tab owning a card, scroll to it, and highlight it briefly. + * @param {string} cardId Element id of the target card. + * @returns {boolean} Whether the card was found. + */ +export function openAdminCard(cardId) { + const resolved = resolveAdminCard(cardId); + if (!resolved) { + console.warn(`openAdminCard: no card with id "${cardId}"`); + return false; + } + + const { card, paneId } = resolved; + + if (paneId && typeof window.showAdminTab === 'function') { + window.showAdminTab(paneId); + syncSidebarActiveState(paneId); + } + + // Let the pane become visible before measuring scroll position. + window.setTimeout(() => { + card.scrollIntoView({ behavior: 'smooth', block: 'start' }); + card.classList.add(HIGHLIGHT_CLASS); + window.setTimeout(() => card.classList.remove(HIGHLIGHT_CLASS), HIGHLIGHT_MS); + }, 120); + + return true; +} + +/** + * Mirror the activated tab in the sidebar, when the sidebar layout is in use. + * @param {string} paneId Tab pane id that was activated. + */ +function syncSidebarActiveState(paneId) { + const navLink = document.querySelector(`.admin-nav-tab[data-tab="${paneId}"]`); + if (!navLink) { + return; + } + + document.querySelectorAll('.admin-nav-tab, .admin-nav-section').forEach((link) => { + link.classList.remove('active'); + }); + navLink.classList.add('active'); +} + +/** + * Delegate clicks so links added after load keep working. + */ +export function initAdminCardLinks() { + document.addEventListener('click', (event) => { + const link = event.target.closest('[data-admin-link]'); + if (!link) { + return; + } + + const cardId = link.getAttribute('data-admin-link'); + if (!cardId) { + return; + } + + event.preventDefault(); + openAdminCard(cardId); + }); +} + +window.openAdminCard = openAdminCard; + +document.addEventListener('DOMContentLoaded', initAdminCardLinks); diff --git a/application/single_app/static/js/admin/admin_settings.js b/application/single_app/static/js/admin/admin_settings.js index 26726f5f3..d30fabf78 100644 --- a/application/single_app/static/js/admin/admin_settings.js +++ b/application/single_app/static/js/admin/admin_settings.js @@ -9227,31 +9227,6 @@ function clearStatusAlert(statusAlert) { } -function switchTab(event, tabButtonId) { - event.preventDefault(); - const triggerEl = document.getElementById(tabButtonId); - if (triggerEl) { - const tabObj = new bootstrap.Tab(triggerEl); - tabObj.show(); - return; - } - - const inferredTabId = tabButtonId.replace(/-tab$/, ''); - if (typeof window.showAdminTab === 'function') { - window.showAdminTab(inferredTabId); - - const navLink = document.querySelector(`.admin-nav-tab[data-tab="${inferredTabId}"]`); - if (navLink) { - document.querySelectorAll('.admin-nav-tab, .admin-nav-section').forEach(link => { - link.classList.remove('active'); - }); - navLink.classList.add('active'); - } - } -} - -window.switchTab = switchTab; - function togglePassword(btnId, inputId) { const btn = document.getElementById(btnId); const inp = document.getElementById(inputId); diff --git a/application/single_app/templates/admin/_panes/citation.html b/application/single_app/templates/admin/_panes/citation.html index 3c6542a29..94f9788e1 100644 --- a/application/single_app/templates/admin/_panes/citation.html +++ b/application/single_app/templates/admin/_panes/citation.html @@ -192,10 +192,10 @@
Video Files

Reference: - + Enable Video File Support - (see the Workspaces tab) + (see AI Video Intelligence under Search and Extract)

{% endif %} @@ -231,10 +231,10 @@
Audio Files

Reference: - + Enable Audio File Support - (see the Workspaces tab) + (see AI Voice Conversations under Search and Extract)

{% endif %} diff --git a/application/single_app/templates/admin/_panes/latest-features.html b/application/single_app/templates/admin/_panes/latest-features.html index 81d30da64..0ef37a941 100644 --- a/application/single_app/templates/admin/_panes/latest-features.html +++ b/application/single_app/templates/admin/_panes/latest-features.html @@ -382,7 +382,7 @@
Admin-only workflow: The review modal is intended for administrators who need to standardize agents on a new default model, control cost, or intentionally replace older explicit bindings as new models are released.
- Enter the full Key Vault secret name. + Enter the full Key Vault secret name. Enable Key Vault for Agent and Action Secrets must be enabled and configured.
diff --git a/application/single_app/templates/admin/_panes/search-extract.html b/application/single_app/templates/admin/_panes/search-extract.html index 37dfa5574..1db055f2b 100644 --- a/application/single_app/templates/admin/_panes/search-extract.html +++ b/application/single_app/templates/admin/_panes/search-extract.html @@ -1688,7 +1688,7 @@

- + Enhanced Citations will dramatically improve the citation experience for video and audio files. diff --git a/application/single_app/templates/admin_settings.html b/application/single_app/templates/admin_settings.html index 8edfa33f4..5d0c4ad44 100644 --- a/application/single_app/templates/admin_settings.html +++ b/application/single_app/templates/admin_settings.html @@ -39,6 +39,14 @@ vertical-align: middle; } + /* Brief highlight when a data-admin-link jumps to a card, so the + destination is obvious after the tab switch and scroll. */ + .admin-card-link-target { + outline: 2px solid var(--bs-primary, #0d6efd); + outline-offset: 3px; + transition: outline-color 0.4s ease-out; + } + /* Floating Save Button */ .floating-save-btn { position: fixed; @@ -1847,6 +1855,7 @@