fix(ui): redesign identity explanation
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user