mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
Wait for content the page owns before scanning it
Several accessibility scans ran against a page that had not finished arriving, so axe reported violations the app does not have. The worst of them was the medications case, which waited for a level-one heading and then checked that a level-one heading exists. A page that really lost its heading would have waited out the timeout under a misleading name instead of failing the rule, which makes it a check that cannot fail rather than a flaky one. The filter rail was the other bad sentinel. It renders beside the loading silhouettes rather than after them, so it was on screen while the rows were still grey rectangles. Under an 8x CPU throttle the rail and the loading marker resolved in the same 40ms poll and the rows a poll later. Every case now gates on a marker the real component carries in its resolved, non-empty branch, and on a component other than the one the scan asserts. Both the desktop table and the mobile card list carry the same marker so the wait holds at any viewport. No sentinel names a heading, the app frame, a busy marker or the filter rail any more.
This commit is contained in:
1 parent
210380f46d
commit
47e50ece39
10 files changed
+153
-31
No files matched your search
@@ -229,7 +229,13 @@ export function AppLogPreviewSection() {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{/* `app-log-rows` names the read events. The branch above it
|
||||
is a spinner and the one below an empty state, so the
|
||||
marker exists only once the buffer has actually answered. */}
|
||||
<tbody
|
||||
data-slot="app-log-rows"
|
||||
className="divide-border divide-y"
|
||||
>
|
||||
{events.map((event, i) => {
|
||||
const actionLabel =
|
||||
event.action?.name ??
|
||||
|
||||
@@ -658,7 +658,12 @@ export function BackupsSection() {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{/* `backup-rows` names the read rows. The three branches above
|
||||
are a spinner, a load error and an empty state, so the
|
||||
marker cannot stand for a page that has not answered yet.
|
||||
The narrow-layout list below carries it too, so a wait on
|
||||
it holds at any viewport. */}
|
||||
<tbody data-slot="backup-rows" className="divide-border divide-y">
|
||||
{rows.map((row, i) => (
|
||||
<tr key={row.id} className={i % 2 === 0 ? "bg-muted/30" : ""}>
|
||||
<td className="px-3 py-2 font-medium">{row.username}</td>
|
||||
@@ -713,6 +718,7 @@ export function BackupsSection() {
|
||||
stay reachable on a phone instead of scrolling off-screen. */}
|
||||
<ul
|
||||
className="space-y-2 md:hidden"
|
||||
data-slot="backup-rows"
|
||||
data-testid="admin-backups-mobile-list"
|
||||
>
|
||||
{rows.map((row) => (
|
||||
|
||||
@@ -99,7 +99,10 @@ export function RecentAuditPreview() {
|
||||
title={t("admin.overview.auditEmpty")}
|
||||
/>
|
||||
) : (
|
||||
<ul className="divide-border divide-y">
|
||||
// `admin-audit-rows` names the read entries. The branches above
|
||||
// are a spinner, a load error and an empty state, so the marker
|
||||
// stands for a card that has actually answered.
|
||||
<ul data-slot="admin-audit-rows" className="divide-border divide-y">
|
||||
{entries.map((entry) => {
|
||||
const isFailed = entry.action === "auth.login.failed";
|
||||
// F-31 (v1.4.19): each row links into the full
|
||||
|
||||
@@ -141,7 +141,13 @@ export function SystemStatusSection() {
|
||||
description={t("admin.systemStatusDescription")}
|
||||
/>
|
||||
{status ? (
|
||||
<div className="mt-4 grid gap-4 pl-7 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div
|
||||
// `system-status-grid` names the answered snapshot. The two
|
||||
// branches below it are a load error and a spinner, so the
|
||||
// marker cannot stand for a section still waiting on the read.
|
||||
data-slot="system-status-grid"
|
||||
className="mt-4 grid gap-4 pl-7 sm:grid-cols-2 lg:grid-cols-4"
|
||||
>
|
||||
<StatusItem
|
||||
icon={Database}
|
||||
label={t("admin.database")}
|
||||
|
||||
@@ -354,7 +354,15 @@ export function UserManagementSection() {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{/* `admin-user-rows` names the read accounts. The section
|
||||
renders an empty state instead when the list is empty and
|
||||
nothing at all while the query is in flight, so the marker
|
||||
stands for answered data. The mobile card list below
|
||||
carries it too, so a wait on it holds at any viewport. */}
|
||||
<tbody
|
||||
data-slot="admin-user-rows"
|
||||
className="divide-border divide-y"
|
||||
>
|
||||
{filteredUsers.map((u, i) => (
|
||||
<tr key={u.id} className={i % 2 === 0 ? "bg-muted/30" : ""}>
|
||||
<td className="px-3 py-2 font-medium">{u.username}</td>
|
||||
@@ -392,6 +400,7 @@ export function UserManagementSection() {
|
||||
is hidden behind a horizontal scroll. */}
|
||||
<ul
|
||||
className="mt-4 space-y-2 md:hidden"
|
||||
data-slot="admin-user-rows"
|
||||
data-testid="admin-users-mobile-list"
|
||||
>
|
||||
{filteredUsers.map((u) => (
|
||||
|
||||
Reference in new issue
Block a user