From fe79bb21093acd29674fc97fdfe621b37da6bf93 Mon Sep 17 00:00:00 2001 From: DemonGatanjieu <1027066412@qq.com> Date: Mon, 3 Aug 2026 20:56:29 +0800 Subject: [PATCH] fix(ui): redesign identity explanation --- .agents/logs/ai_changelog.md | 15 ++++++++ .agents/logs/ai_lessons.md | 2 +- ARCHITECTURE.md | 2 +- web/modules/ui_recipe_detail.js | 26 ++++++++++---- web/styles.css | 62 +++++++++++++++++++-------------- 5 files changed, 72 insertions(+), 35 deletions(-) diff --git a/.agents/logs/ai_changelog.md b/.agents/logs/ai_changelog.md index 88e3fbe..8ef681c 100644 --- a/.agents/logs/ai_changelog.md +++ b/.agents/logs/ai_changelog.md @@ -495,3 +495,18 @@ - `node --check web/modules/ui_recipe_detail.js` - `node tests/recipe_parser_roundtrip.mjs` - `python_embeded/python.exe tests/test_recipe_roundtrip.py` + +## [Snapshot] 2026-08-03 - Redesign identity explanation interaction + +**Implemented** + +- Replaced the duplicated hover/native tooltip behavior with a clickable question-mark control. +- Identity explanations now expand inline below the badge in normal document flow. +- Removed the overlapping absolute tooltip so adjacent text cannot cover or hide the explanation. +- Added `aria-expanded` and keyboard-focus styling to the help control. + +**Validation** + +- `node --check web/modules/ui_recipe_detail.js` +- `node tests/recipe_parser_roundtrip.mjs` +- `python_embeded/python.exe tests/test_recipe_roundtrip.py` diff --git a/.agents/logs/ai_lessons.md b/.agents/logs/ai_lessons.md index 6e37934..eb46d18 100644 --- a/.agents/logs/ai_lessons.md +++ b/.agents/logs/ai_lessons.md @@ -32,7 +32,7 @@ For network-backed buttons, disabling the control prevents duplicate requests bu ## 63. Native Title Tooltips Are Not Reliable UI Feedback -Browser `title` hints are delayed, low-contrast, difficult to style, and poor for keyboard users. For important explanations, keep the title as fallback but add a bounded custom tooltip triggered by both hover and focus. +Browser `title` hints are delayed, low-contrast, difficult to style, and poor for keyboard users. For explanations that must not cover neighboring content, an inline click-to-expand note is safer than an absolutely positioned tooltip and avoids duplicate browser/custom hints. 这份文档旨在记录 Anomalous 插件开发过程中的架构演进、UX 设计教训以及核心 Bug 排查经验。 --- diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1704954..5074b73 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -10,7 +10,7 @@ Recipe identity badges use short, bounded labels in the card layout; detailed ex Availability refresh actions expose immediate busy feedback, an accessible `aria-busy` state, and a recoverable error state; a disabled button without visible progress is not considered sufficient interaction feedback. -Identity help uses an in-plugin tooltip with high-contrast styling, bounded width, wrapping, hover visibility, and keyboard focus visibility. Native `title` remains only as a compatibility fallback. +Identity help uses a click-to-expand inline explanation attached to the badge. The explanation stays in normal card flow with bounded width and wrapping, so it cannot be duplicated by a native `title` tooltip or cover adjacent flex content. ## Current recipe interaction correction (2026-08-03) diff --git a/web/modules/ui_recipe_detail.js b/web/modules/ui_recipe_detail.js index 64425b2..1b2015b 100644 --- a/web/modules/ui_recipe_detail.js +++ b/web/modules/ui_recipe_detail.js @@ -292,6 +292,8 @@ function fullDiffValue(value) { function identityBadge(reference) { const identity = normaliseIdentity(reference?.identity); + const wrapper = document.createElement('span'); + wrapper.className = 'anomalous-recipe-identity-badge-wrap'; const badge = document.createElement('span'); badge.className = `anomalous-recipe-identity-badge anomalous-recipe-identity-${identity.status}`; @@ -299,17 +301,27 @@ function identityBadge(reference) { textSpan.textContent = t(`recipeIdentity${identity.status[0].toUpperCase()}${identity.status.slice(1)}`); badge.appendChild(textSpan); - const helpIcon = document.createElement('span'); - helpIcon.textContent = ' [?]'; + const helpIcon = document.createElement('button'); + helpIcon.type = 'button'; + helpIcon.textContent = '?'; helpIcon.className = 'anomalous-recipe-identity-help'; const helpText = t('recipeIdentityHelpDesc') || 'Verification checks physical file consistency, not model quality.'; - helpIcon.title = helpText; - helpIcon.dataset.tooltip = helpText; - helpIcon.setAttribute('role', 'img'); + helpIcon.setAttribute('aria-expanded', 'false'); helpIcon.setAttribute('aria-label', helpText); badge.appendChild(helpIcon); - - return badge; + + const explanation = document.createElement('span'); + explanation.className = 'anomalous-recipe-identity-explanation'; + explanation.textContent = helpText; + explanation.setAttribute('role', 'note'); + helpIcon.onclick = (event) => { + event.stopPropagation(); + const expanded = wrapper.classList.toggle('is-open'); + helpIcon.setAttribute('aria-expanded', String(expanded)); + }; + + wrapper.append(badge, explanation); + return wrapper; } function fingerprintText(recipe) { diff --git a/web/styles.css b/web/styles.css index f54ab9a..7a266fd 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1869,48 +1869,58 @@ min-width: 0; overflow-wrap: anywhere; } -.anomalous-recipe-identity-help { - position: relative; +.anomalous-recipe-identity-badge-wrap { display: inline-flex; + min-width: 0; + max-width: 100%; + flex: 0 1 auto; + flex-wrap: wrap; align-items: center; - font-weight: normal; - color: inherit; - opacity: 0.6; - margin-left: 2px; - cursor: help; } -.anomalous-recipe-identity-help::after { - position: absolute; - z-index: 20; - top: calc(100% + 8px); - right: 0; - width: max-content; - max-width: min(320px, calc(100vw - 40px)); +.anomalous-recipe-identity-help { + display: inline-flex; + width: 16px; + height: 16px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid currentColor; + border-radius: 50%; + color: inherit; + background: transparent; + font-weight: normal; + opacity: 0.6; + margin-left: 2px; + cursor: pointer; +} + +.anomalous-recipe-identity-help:hover, +.anomalous-recipe-identity-help:focus-visible { + opacity: 1; + outline: 2px solid rgba(255, 255, 255, 0.3); + outline-offset: 1px; +} + +.anomalous-recipe-identity-explanation { + display: none; + flex: 0 0 100%; + max-width: min(340px, calc(100vw - 48px)); padding: 9px 11px; border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 7px; color: #fff; background: rgba(20, 22, 28, 0.98); - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.42); - content: attr(data-tooltip); + box-shadow: 0 5px 16px rgba(0, 0, 0, 0.28); font-size: 0.78rem; font-weight: 400; line-height: 1.45; - opacity: 0; - pointer-events: none; - transform: translateY(-3px); - transition: opacity 0.15s ease, transform 0.15s ease; - visibility: hidden; white-space: normal; overflow-wrap: anywhere; } -.anomalous-recipe-identity-help:hover::after, -.anomalous-recipe-identity-help:focus-visible::after { - opacity: 1; - transform: translateY(0); - visibility: visible; +.anomalous-recipe-identity-badge-wrap.is-open .anomalous-recipe-identity-explanation { + display: block; } .anomalous-recipe-identity-verified {