fix(ui): redesign identity explanation

This commit is contained in:
DemonGatanjieu
2026-08-03 20:56:29 +08:00
parent 3d4af1cc97
commit fe79bb2109
5 changed files with 72 additions and 35 deletions
+15
View File
@@ -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`
+1 -1
View File
@@ -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 排查经验。
---
+1 -1
View File
@@ -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)
+19 -7
View File
@@ -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) {
+36 -26
View File
@@ -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 {