From e22e98fd45fdd2041ab5e9d74644d51f38f48d02 Mon Sep 17 00:00:00 2001 From: yanhenrique-dev Date: Sun, 27 Sep 2026 00:33:05 -0300 Subject: [PATCH 1/6] fix(a11y): fecha contraste do success e o foco dos switches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A3 — `--v2-state-fg-success` era green-800 (#198b43) a 3,97:1 sobre o bg-success, em texto de 11px (tag "Em dia") e 13px (.status-line): falha WCAG 1.4.3. green-900 (#1d783c) mede 5,03:1 no bg e 4,93:1 em layer-02, então o token também segura se a tag renderizar sobre superfície neutra. A1 — a regra de foco do switch era `[data-component="switch"] [data-slot="switch-control"]:focus-visible`, e o span filho não é focalizável: a regra nunca casava e os três switches (Detecção automática, Modo, Backup) estavam sem indicador de foco, falha WCAG 2.4.7. A regra vai para o + + +
@@ -361,6 +361,6 @@

Preferências

- + diff --git a/static/shell.css b/static/shell.css index 3f9b417..be4c4e8 100644 --- a/static/shell.css +++ b/static/shell.css @@ -274,10 +274,13 @@ .page-header__actions { display: flex; align-items: center; gap: 8px; margin-top: 4px; } -/* Etapas da operação — 3 passos, densidade alta */ +/* Etapas da operação — 3 passos, densidade alta. data-done reflete o progresso + real (pintado por paintSteps()): Localizar acende com pasta, Comparar com + check concluído, Trocar quando nada está pendente. */ .steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; } .step { display: flex; flex-direction: column; gap: 2px; padding: 12px; border-radius: var(--v2-radius-surface); background: var(--v2-background-bg-layer-01); box-shadow: var(--v2-elevation-raised); } .step__index { color: var(--v2-text-text-faint); font-family: var(--v2-font-family-mono); font-size: var(--v2-text-micro-size); line-height: 1; letter-spacing: 0; } +.step[data-done="true"] .step__index { color: var(--v2-state-fg-success); } .step__title { font-size: var(--v2-text-label-size); font-weight: var(--v2-weight-medium); line-height: var(--v2-text-label-line); letter-spacing: var(--v2-text-label-tracking); } .step__body { color: var(--v2-text-text-muted); font-size: var(--v2-text-micro-size); line-height: 1; letter-spacing: var(--v2-text-micro-tracking); } @@ -355,10 +358,18 @@ gap: 2px; min-height: 88px; padding: 12px 16px; + border: 0; border-radius: var(--v2-radius-surface); background: var(--v2-background-bg-layer-01); box-shadow: var(--v2-elevation-raised); + font: inherit; + text-align: start; + cursor: pointer; + transition: box-shadow var(--v2-motion-control), transform var(--v2-motion-control); } +.stat-card:hover { box-shadow: var(--v2-elevation-floating); } +.stat-card:focus-visible { outline: 2px solid var(--v2-border-border-focus); outline-offset: 2.5px; } +.stat-card[data-active="true"] { box-shadow: var(--v2-elevation-floating), 0 0 0 1px var(--v2-border-border-strong); } .stat-card__label { display: flex; align-items: center; gap: 6px; color: var(--v2-text-text-muted); font-size: var(--v2-text-label-size); font-weight: var(--v2-weight-medium); line-height: var(--v2-text-label-line); letter-spacing: var(--v2-text-label-tracking); } .stat-card__value { color: var(--v2-text-text-base); font-size: var(--v2-text-stat-size); font-weight: var(--v2-weight-emphasis); line-height: var(--v2-text-stat-line); letter-spacing: var(--v2-text-stat-spacing); font-variant-numeric: tabular-nums; } .stat-card__hint { color: var(--v2-text-text-faint); font-size: var(--v2-text-micro-size); line-height: 1; letter-spacing: var(--v2-text-micro-tracking); } @@ -504,4 +515,8 @@ .topbar, .sidebar, .setting-row { box-shadow: none; border-color: CanvasText; } + /* I3: o Windows HC descarta outline não-mapeado — sem isto o foco some. */ + *:focus-visible { outline-color: Highlight; } + .status-dot { forced-color-adjust: none; } + .stat-card[data-active="true"] { outline: 2px solid Highlight; } } diff --git a/static/tokens.css b/static/tokens.css index f570663..9555d31 100644 --- a/static/tokens.css +++ b/static/tokens.css @@ -368,19 +368,8 @@ --v2-avatar-border-pink: #c83d8b; --v2-avatar-bg-purple: #623be2; --v2-avatar-border-purple: #5230c2; - - --v2-elevation-raised: 0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12); - --v2-elevation-floating: 0px 8px 16px 0px var(--v2-alpha-dark-4), 0px 4px 8px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12); - --v2-elevation-overlay: 0px 16px 32px 0px var(--v2-alpha-dark-4), 0px 8px 16px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12); - --v2-elevation-button-neutral: 0px 1px 1.5px 0px var(--v2-alpha-dark-10), 0px 0px 0px 0.5px var(--v2-alpha-dark-14); - --v2-elevation-button-contrast: 0px 1px 1.5px 0px var(--v2-alpha-dark-20), 0px 0px 0px 0.5px var(--v2-grey-800), inset 0px 1px 2px 0px var(--v2-alpha-light-14), inset 0px -1px 2px 0px var(--v2-alpha-dark-6); - --v2-elevation-elements: 0px 0.5px 0.5px 0px var(--v2-alpha-dark-40); - --v2-elevation-switch-off: inset 0px 1px 1px 0px var(--v2-alpha-dark-8), inset 0px 0.5px 0.5px 0px var(--v2-alpha-dark-8), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-10); - --v2-elevation-switch-on: inset 0px 2px 2px 0px var(--v2-alpha-dark-10), inset 0px 1px 1px 0px var(--v2-alpha-dark-10), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-20); - - --v2-illustration-layer-01: var(--v2-grey-300); - --v2-illustration-layer-02: var(--v2-grey-400); - --v2-illustration-layer-03: var(--v2-grey-500); + /* B3: elevation/illustration do light são idênticos ao :root (linhas 202-214) + — herança já resolve, 12 linhas duplicadas removidas. */ } /* -------------------------------------------------------------------------- From e95851e49792b3a080456a38b4dd32e77d3afdc5 Mon Sep 17 00:00:00 2001 From: yanhenrique-dev Date: Wed, 30 Sep 2026 00:38:23 -0300 Subject: [PATCH 5/6] feat(frontend): leva de skills ux-heuristics, refactoring-ui, microinteractions, high-perf-browser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ux-heuristics (H4/H6, Krug #3): - card "Revisão ou erro" somava 2 categorias mas linkava 1: link dinâmico (paintSteps aponta p/ sem-dono ou erros, conforme a não-vazia) - branch explícita com title="Branch fixada no mapeamento" (H6) - cabeçalhos com metade das palavras (Biblioteca/Atividade/Preferências) refactoring-ui: - toast com fio lateral no tone (hierarquia sem ler o ícone) - map-row com fio de pertencimento à linha do repo microinteractions: - "Atualizar pendentes (N)": botão mostra o tamanho do lote - toast pausa no hover, rearma 2s na saída (não some sob o mouse) high-perf-browser: - preload de app.js + color-scheme dark light - fontes com ?v=3 (casam com o preload; caem no immutable do servidor) Gate: 169 pytest + 38 node --test + node --check + ruff + pyright verdes. --- static/app.js | 19 +++++++++++++++++-- static/components.css | 4 ++++ static/index.html | 18 ++++++++++-------- static/shell.css | 1 + static/tokens.css | 6 +++--- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/static/app.js b/static/app.js index c2d3c96..154a70f 100644 --- a/static/app.js +++ b/static/app.js @@ -296,7 +296,7 @@ function renderRow(row, index) { return ` ${renderIdentityCell(row, index)} - ${row.auto ? `${esc(branch)}` : `${esc(branch)}`} + ${row.auto ? `${esc(branch)}` : `${esc(branch)}`} ${esc(artifactLabel(row))} ${esc(shortSha(row.local_sha))} ${esc(shortSha(row.remote_sha))} @@ -469,6 +469,13 @@ function paintSteps(counts) { if (compare) compare.dataset.done = checked ? "true" : "false"; if (swap) swap.dataset.done = state.base && counts.behind === 0 && counts.all > 0 ? "true" : "false"; document.querySelectorAll("[data-filter-link]").forEach((card) => { + if ("attentionCard" in card.dataset) { + // H4: o card soma sem-dono+erros; o link vai para a categoria não-vazia, + // senão o clique mostrava "Nenhum item neste filtro" com N > 0 no card. + const target = counts.unmapped > 0 ? "unmapped" : "error"; + card.dataset.filterLink = target; + card.setAttribute("aria-label", target === "unmapped" ? "Ver itens sem dono" : "Ver itens com erro"); + } card.dataset.active = card.dataset.filterLink === state.filter ? "true" : "false"; }); } @@ -509,6 +516,8 @@ function renderUnsafe() { restoreUrlDrafts(drafts); dom.btnAll.disabled = state.busy || !state.base || counts.behind === 0; + const allLabel = $("btn-all-label"); + if (allLabel) allLabel.textContent = counts.behind > 0 ? `Atualizar pendentes (${counts.behind})` : "Atualizar pendentes"; } /* ------------------------------------------------------------------ theme */ @@ -651,7 +660,13 @@ function toast(message, tone = "info") { item.querySelector("[data-slot=toast-v2-close]").addEventListener("click", dismiss); dom.toastRegion.appendChild(item); while (dom.toastRegion.children.length > 4) dom.toastRegion.firstElementChild.remove(); - window.setTimeout(dismiss, 4600); + // Não some sob o mouse: hover pausa, saída rearma com 2s. + let dismissTimer = window.setTimeout(dismiss, 4600); + item.addEventListener("mouseenter", () => window.clearTimeout(dismissTimer)); + item.addEventListener("mouseleave", () => { + window.clearTimeout(dismissTimer); + dismissTimer = window.setTimeout(dismiss, 2000); + }); } function setBusy(value) { diff --git a/static/components.css b/static/components.css index b799c1a..2d78979 100644 --- a/static/components.css +++ b/static/components.css @@ -748,6 +748,10 @@ body { [data-component="toast-v2"][data-tone="warning"] [data-slot="toast-v2-icon"] { color: var(--v2-state-fg-warning); } [data-component="toast-v2"][data-tone="danger"] [data-slot="toast-v2-icon"] { color: var(--v2-state-fg-danger); } [data-component="toast-v2"][data-tone="info"] [data-slot="toast-v2-icon"] { color: var(--v2-state-fg-info); } +[data-component="toast-v2"][data-tone="success"] { box-shadow: var(--v2-elevation-floating), inset 2px 0 0 0 var(--v2-state-fg-success); } +[data-component="toast-v2"][data-tone="warning"] { box-shadow: var(--v2-elevation-floating), inset 2px 0 0 0 var(--v2-state-fg-warning); } +[data-component="toast-v2"][data-tone="danger"] { box-shadow: var(--v2-elevation-floating), inset 2px 0 0 0 var(--v2-state-fg-danger); } +[data-component="toast-v2"][data-tone="info"] { box-shadow: var(--v2-elevation-floating), inset 2px 0 0 0 var(--v2-state-fg-info); } @keyframes v2-toast-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes v2-toast-out { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(8px); } } diff --git a/static/index.html b/static/index.html index 158d368..49851d5 100644 --- a/static/index.html +++ b/static/index.html @@ -5,14 +5,16 @@ + RepoRefresh — bancada local - - - + + + + @@ -148,7 +150,7 @@
@@ -187,7 +189,7 @@

Biblioteca

@@ -205,7 +207,7 @@

Biblioteca

- +
@@ -262,7 +264,7 @@

Biblioteca