From 626900a22a40312ec8350125bbdcc123eee44bd7 Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Fri, 12 Jun 2026 04:34:37 -0700 Subject: [PATCH] =?UTF-8?q?fix(#1668):=20typography=20pass=20=E2=80=94=201?= =?UTF-8?q?4px=20body=20/=2012px+500=20chip=20floor=20(M3)=20(#1679)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Red commit: 91fc49f98a7d278edb3521f1704b493a2adbb90b (CI run: pending until pushed) **Partial fix for #1668 (M3 of 6).** M2 cleared ~85% of BLOCKER contrast violations (v3.9.1). M3 addresses the M1-audit `thin-small` findings: chips, badges, table cells, and meta labels where `font-size < 14px AND font-weight < 500` made text hard to read for the operator regardless of contrast — the original "the typography sucks" complaint that drove this issue. ## Design (operator-locked) - Body text floor: **14px** - Chip / badge / meta floor: **12px AND weight ≥ 500** (or 14px if 400) - Visual hierarchy preserved — H1/H2/H3 untouched - No palette changes (M2 owns colors) · no layout (M4 owns that) ## What changed New `:root` weight tokens: `--fw-{normal,medium,semibold,bold}`. | Selector | Before (px/weight) | After | |---|---|---| | `.nav-link` | 12.8 / 400 | 12-14 fluid / **500** | | `.tab-btn` | 13 / 400 | 13 / **500** | | `.alab-pkt` (audio-lab) | 12 / 400 | 12 / **500** | | `.ch-item-time` | 11 / 400 | **12** / **500** | | `.ch-item-preview` | 12 / 400 | **13** / **500** | | `.payload-bar-label` | 12 / 400 | 12 / **500** | | `.stat-label` | 12 / 400 | 12 / **500** | | `.col-hidden-pill` | **10** / 700 | **12** / 700 | | `.skew-badge` | **10** / 600 | **12** / 600 | | `.filter-group .btn` | 12 / 400 | 12 / **500** | | `.timestamp-text` (new explicit rule) | inherits 12 / 400 | **13** / **500** | | `.data-table` (incl. mobile override) | 12-11 / 400 | **13** / **500** (mobile 12) | | `.mono` | 12 / 400 | **13** / **500** | Estimated thin-small violations cleared: **~5,800 of 6,313 MAJOR** in the M1 dataset (timestamps + table cells + nav links are the bulk). ## Letsmesh bar Letsmesh ships 12.5-15px paired with 600 weight on chips — our 12px+500 floor is one notch lighter but matches the operator-locked spec. ## Tests TDD: `test-issue-1668-m3-typography.js` (new) parses `style.css` + `audio-lab.js`, computes effective font-size/weight per selector with CSS cascade resolution, and asserts the floor on 12 high-impact selectors. Red commit fails 10/12 on master; green commit makes all 12 pass. Anti- tautology verified: reverted `.skew-badge` bump → test fails on the assertion (not a build error) → restored → test passes. ## Verified on staging (hot-patch) Computed styles AFTER patch: `.timestamp-text` 13/500, `.skew-badge` 12/600, `.nav-link` 12.8/500. ## Next - M4 — per-route polish + map legend - M5 — CI gate that re-runs the M1 probe and fails on regressions - M6 — A/B verification with the operator --------- Co-authored-by: openclaw-bot --- .github/workflows/deploy.yml | 1 + public/audio-lab.js | 2 +- public/style.css | 59 ++++++--- test-issue-1668-m3-typography.js | 205 +++++++++++++++++++++++++++++++ 4 files changed, 248 insertions(+), 19 deletions(-) create mode 100644 test-issue-1668-m3-typography.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1d85f643..e04629ea 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -141,6 +141,7 @@ jobs: node test-preflight-xss-gate.js node test-traces.js node test-issue-1648-m4-emoji-scan.js + node test-issue-1668-m3-typography.js - name: 🛡️ Preflight XSS gate — actual --diff check (PR only) # The fixture self-test above (test-preflight-xss-gate.js) only diff --git a/public/audio-lab.js b/public/audio-lab.js index cee3c48d..5779e6e9 100644 --- a/public/audio-lab.js +++ b/public/audio-lab.js @@ -44,7 +44,7 @@ border-radius: 6px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } .alab-type-hdr:hover { opacity: 0.8; } .alab-type-list { padding: 0; } - .alab-pkt { padding: 5px 8px 5px 16px; font-size: 12px; font-family: var(--mono); + .alab-pkt { padding: 5px 8px 5px 16px; font-size: 12px; font-weight: 500; font-family: var(--mono); cursor: pointer; border-radius: 4px; color: var(--text-muted); } .alab-pkt:hover { background: var(--hover-bg); } .alab-pkt.selected { background: var(--selected-bg); color: var(--text); font-weight: 600; } diff --git a/public/style.css b/public/style.css index 7b6bfe71..82507244 100644 --- a/public/style.css +++ b/public/style.css @@ -187,6 +187,15 @@ --fs-xl: clamp(18px, 0.50vw + 16px, 28px); --fs-2xl: clamp(22px, 0.75vw + 20px, 36px); + /* --- Typography weight tokens — #1668 M3 ---------------- + * Chip / badge / meta rule (M3): font-size >= 14px OR + * (font-size >= 12px AND font-weight >= 500). Use --fw-medium + * on anything that stays at 12-13px to keep it readable. */ + --fw-normal: 400; + --fw-medium: 500; + --fw-semibold: 600; + --fw-bold: 700; + /* --- Fluid radii ----------------------------------------- */ --radius-sm: clamp(3px, 0.1vw + 2px, 6px); --radius-md: clamp(6px, 0.2vw + 5px, 12px); @@ -764,6 +773,7 @@ img.brand-logo { .nav-link { color: var(--nav-text-muted); text-decoration: none; padding: 14px clamp(8px, 0.6vw + 4px, 14px); font-size: var(--fs-sm); + font-weight: var(--fw-medium); /* #1668 M3 — 12-14px fluid + 500 floor */ border-bottom: 2px solid transparent; transition: all .15s; background: none; border-top: none; border-left: none; border-right: none; cursor: pointer; font-family: var(--font); @@ -950,7 +960,7 @@ img.brand-logo { /* #1124 (MAJOR-3): each grouped cluster wraps as a single unit at narrow * widths instead of letting individual controls reflow across categories. */ .filter-bar .filter-group { flex-wrap: nowrap; } -.filter-group .btn { padding: 4px 10px; font-size: 12px; border-radius: 12px; border: 1px solid var(--border); background: var(--input-bg); color: var(--text); cursor: pointer; transition: background 0.15s, color 0.15s; height: 34px; min-height: 34px; box-sizing: border-box; line-height: 1; } +.filter-group .btn { padding: 4px 10px; font-size: 12px; font-weight: var(--fw-medium); border-radius: 12px; border: 1px solid var(--border); background: var(--input-bg); color: var(--text); cursor: pointer; transition: background 0.15s, color 0.15s; height: 34px; min-height: 34px; box-sizing: border-box; line-height: 1; } /* #1668 M3 — added 500 weight on chip */ .filter-group .btn.active { background: var(--accent-strong); color: var(--text-on-accent); border-color: var(--accent-strong); } .filter-group .btn:hover:not(.active) { background: var(--surface-2); } .filter-group + .filter-group { border-left: 1px solid var(--border); padding-left: 12px; margin-left: 6px; } @@ -982,9 +992,9 @@ img.brand-logo { * pill rendered inside thead (clickable to reveal) */ .data-table { - width: 100%; border-collapse: collapse; font-size: 12px; + width: 100%; border-collapse: collapse; font-size: 13px; font-weight: var(--fw-medium); table-layout: auto; -} +} /* #1668 M3 — was 12px/400, now 13px paired with 500 weight */ .data-table th { text-align: left; padding: 4px 6px; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .3px; color: var(--text-muted); @@ -1201,7 +1211,7 @@ img.brand-logo { padding: 1px 7px; border-radius: 999px; background: var(--accent-bg, rgba(96,165,250,0.18)); color: var(--accent, #60a5fa); - font-size: 10px; font-weight: 700; letter-spacing: .3px; + font-size: 12px; font-weight: var(--fw-bold); letter-spacing: .3px; /* #1668 M3 — was 10px */ cursor: pointer; user-select: none; vertical-align: middle; border: 1px solid var(--accent, #60a5fa); text-transform: none; @@ -1341,7 +1351,14 @@ body.scroll-locked { overflow: hidden; } font-family:var(--mono) } /* === Monospace === */ -.mono { font-family: var(--mono); font-size: 12px; } +.mono { font-family: var(--mono); font-size: 13px; font-weight: var(--fw-medium); } /* #1668 M3 — was 12px/inherit */ + +/* #1668 M3 — explicit floor for "Xs/Xm/Xd ago" timestamps used on every row + * of /packets, /nodes, /live. Was inheriting 12px/400 (thin-small). */ +.timestamp-text { + font-size: 13px; + font-weight: var(--fw-medium); +} /* === Detail Panel === */ .detail-title { @@ -1444,6 +1461,12 @@ body.scroll-locked { overflow: hidden; } * overrides it), so use `height` — table cells respect `height` as * min-height, locking the row to a single line of chip content * regardless of hop count. */ +/* #1668 M3 r1 (tufte #4): preserve PATH column width after the table body + * font bump (12 -> 13) widened sibling columns and squeezed PATH chips + * from 7-9 visible chars down to 3-4. Reserve enough room for two + * abbreviated 7-char chips + arrow. .path-hops already uses 11px font, so + * no chip-level font change is needed here. */ +.data-table td.col-path, .data-table th.col-path { min-width: 140px; } .data-table td.col-path { height: 28px; line-height: 22px; overflow: hidden; } /* === Modal === */ @@ -1682,7 +1705,7 @@ button.ch-item.selected { background: var(--selected-bg); } .ch-item-body { flex: 1; min-width: 0; } .ch-item-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 2px; } .ch-item-name { font-weight: 600; font-size: 14px; } -.ch-item-time { font-size: 11px; color: var(--text-muted); white-space: nowrap; } +.ch-item-time { font-size: 12px; font-weight: var(--fw-medium); color: var(--text-muted); white-space: nowrap; } /* #1668 M3 — was 11px/400 */ .ch-unread-badge { display: inline-block; min-width: 18px; @@ -1720,8 +1743,8 @@ button.ch-item:hover .ch-icon-btn { opacity: 1; } .ch-icon-btn:hover, .ch-icon-btn:focus { opacity: 1 !important; outline: none; } .ch-remove-btn:hover, .ch-remove-btn:focus { background: #8b3838; color: white; } .ch-share-btn:hover, .ch-share-btn:focus { color: var(--accent, #3b82f6); } -.ch-user-badge { font-size: 12px; margin-left: 2px; opacity: 0.85; flex-shrink: 0; } -.ch-item-preview { font-size: 12px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.ch-user-badge { font-size: 12px; font-weight: var(--fw-medium); margin-left: 2px; opacity: 0.85; flex-shrink: 0; } /* #1668 M3 r1 — chip floor, mirror row chips (adv #2) */ +.ch-item-preview { font-size: 13px; font-weight: var(--fw-medium); color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* #1668 M3 — was 12px/400 */ .ch-main { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; } @@ -2307,14 +2330,14 @@ button.ch-item:hover .ch-icon-btn { opacity: 1; } #chList .ch-share-btn { font-size: 0; } #chList .ch-share-btn .ph-icon, #chList .ch-share-btn svg { font-size: 13px; } #chList .ch-remove-btn { font-size: 12px; padding: 2px 6px; } - /* Hide the analytics-style time on mobile rows to free width for the name. */ - #chList .ch-item-time { font-size: 10px; } + /* Mobile-narrow timestamp — #1668 M3 chip floor (12px+500 carries from base) */ + #chList .ch-item-time { font-size: 12px; } /* Empty state must not dominate. */ .ch-empty { padding: 12px; font-size: 13px; max-height: 30vh; } - /* Tables: smaller text for mobile */ - .data-table { font-size: 11px; min-width: 0; } + /* Tables: smaller text for mobile — #1668 M3 chip floor (12px+500) */ + .data-table { font-size: 12px; min-width: 0; } .data-table td { padding: 5px 4px; max-width: 100px; } .data-table th { padding: 5px 4px; font-size: 10px; } .data-table .col-time { min-width: 64px; } @@ -2724,15 +2747,15 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); } /* Analytics v2 */ .analytics-tabs { display: flex; gap: 4px; margin-top: 12px; flex-wrap: wrap; } -.tab-btn { padding: 6px 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--card-bg); color: var(--text); cursor: pointer; font-size: 13px; transition: all .15s; } +.tab-btn { padding: 6px 14px; border: 1px solid var(--border); border-radius: 6px; background: var(--card-bg); color: var(--text); cursor: pointer; font-size: 13px; font-weight: var(--fw-medium); transition: all .15s; } /* #1668 M3 — 13px + 500 floor */ .tab-btn:hover { background: var(--hover-bg, rgba(0,0,0,.04)); } /* #1668 M2 — active tab was #fff on --accent (2.75:1 BLOCKER). */ .tab-btn.active { background: var(--accent-strong); color: var(--text-on-accent); border-color: var(--accent-strong); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 240px)); gap: 12px; margin-bottom: 16px; } .stat-card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; text-align: center; } .stat-value { font-size: 24px; font-weight: 700; color: var(--text); } -.stat-label { font-size: 12px; color: var(--text-muted); margin-top: 2px; } -.stat-detail { font-size: 11px; color: var(--text-muted); margin-top: 2px; } +.stat-label { font-size: 12px; font-weight: var(--fw-medium); color: var(--text-muted); margin-top: 2px; } /* #1668 M3 — added 500 weight */ +.stat-detail { font-size: 12px; font-weight: var(--fw-medium); color: var(--text-muted); margin-top: 2px; } /* #1668 M3 r1 — was 11px/400, below chip floor (adv #3) */ .stat-spark { margin-top: 6px; display: flex; justify-content: center; } /* #1058 — Analytics rows: fluid + auto-stacking via CSS Grid `auto-fit`. Cards stack automatically when the row can't fit two columns at the @@ -2750,8 +2773,8 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); } .rf-stats { display: flex; gap: 16px; flex-wrap: wrap; padding-top: 8px; font-size: 13px; } .payload-bars { display: flex; flex-direction: column; gap: 6px; } .payload-bar-row { display: flex; align-items: center; gap: 8px; } -.payload-bar-label { min-width: 100px; font-size: 12px; display: flex; align-items: center; gap: 4px; } -.payload-bar-value { min-width: 100px; text-align: right; font-size: 12px; } +.payload-bar-label { min-width: 100px; font-size: 12px; font-weight: var(--fw-medium); display: flex; align-items: center; gap: 4px; } /* #1668 M3 — added 500 weight */ +.payload-bar-value { min-width: 100px; text-align: right; font-size: 12px; font-weight: var(--fw-medium); } /* #1668 M3 r1 — match .payload-bar-label weight (adv #1) */ .repeater-list { display: flex; flex-direction: column; gap: 4px; } .repeater-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; } .repeater-name { min-width: 140px; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } @@ -4198,7 +4221,7 @@ th.sort-active { color: var(--accent, #60a5fa); } .sort-arrow { font-size: 0.75em; opacity: 0.8; } /* #690 — Clock Skew badges & fleet table */ -.skew-badge { display: inline-block; font-size: 10px; padding: 1px 5px; border-radius: 3px; margin-left: 4px; font-weight: 600; white-space: nowrap; } +.skew-badge { display: inline-block; font-size: 12px; padding: 1px 6px; border-radius: 3px; margin-left: 4px; font-weight: var(--fw-semibold); white-space: nowrap; } /* #1668 M3 — was 10px */ .skew-badge--ok { background: var(--status-green); color: #fff; } .skew-badge--warning { background: var(--status-yellow); color: #000; } .skew-badge--critical { background: var(--status-orange); color: #fff; } diff --git a/test-issue-1668-m3-typography.js b/test-issue-1668-m3-typography.js new file mode 100644 index 00000000..2b77b73d --- /dev/null +++ b/test-issue-1668-m3-typography.js @@ -0,0 +1,205 @@ +/** + * test-issue-1668-m3-typography.js + * + * Milestone 3 of #1668 — enforce a readable typography floor on chips, + * badges, table cells and meta labels flagged by the M1 a11y audit as + * `thin-small` (font-size < 14px AND font-weight < 500). + * + * Floor (operator-locked): + * - body text >= 14px, OR + * - chip/badge/meta >= 12px AND weight >= 500 + * + * This test scans the **last** CSS rule for each target selector in + * public/style.css (and the inline