docs(ui): tighten UI audit — verified counts, real Button root cause, file:line refs
- Pattern A: corrected root cause (whitespace-nowrap + fixed h-{size}, not "block-children-as-flex-items"), verified against @bytelyst/ui Button source
- Pattern E: reframed as UX/discoverability (truncate doesn't hide from screen readers; full text is in DOM)
- Pattern H: narrowed to only the one real vw bug (index.css:1620); 1791/1798/1822/1832 are inside clamp() and fine
- Pattern F: confirmed 5 inline 1fr-1fr instances with file:line; confirmed 2 bare-1fr CSS instances
- Section 1: replaced fuzzy metric ("31 files use Button primitive") with verified counts and source of truth
- Section 5: every recommendation now has concrete file:line
- Section 6: NEW — how to verify a UI fix (5-viewport check, body scrollWidth probe, build/typecheck)
- Section 7: fixed broken pixel-width regex ([,$] -> [,;]?\s*$); added bare-1fr-in-css command
- Section 8: NEW — open questions / things to investigate (asChild slot inheritance, mobile, light theme)
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
3949062435
commit
9df8a255ce
@ -1,121 +1,185 @@
|
|||||||
# Trading Web UI Audit
|
# Trading Web UI Audit
|
||||||
|
|
||||||
> **Last updated:** 2026-05-10
|
> **Last updated:** 2026-05-10
|
||||||
> **Scope:** `learning_ai_invt_trdg/web/src` (~13,000 lines of UI code across views/tabs/components)
|
> **Scope:** `learning_ai_invt_trdg/web/src` (~13,000 lines: 25 tabs/views + 60+ components)
|
||||||
> **Method:** Pattern-based grep audit + manual inspection of fixed components
|
> **Method:** Pattern-based grep audit, manual inspection of recently fixed components, source review of `@bytelyst/ui` Button primitive.
|
||||||
|
> **Audience:** Engineers maintaining the trading web app and reviewers of `web/src/layout-fixes.css`.
|
||||||
|
|
||||||
This doc catalogs UI issues by **pattern** (so you fix root causes, not symptoms), tells you **which files are affected**, and recommends **concrete fixes**.
|
This doc catalogs UI issues by **pattern** (so you fix root causes, not symptoms), points at **specific file:line locations**, and recommends **concrete fixes**. Every metric below was verified against the working tree at commit `3949062`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Summary — what's the state of the codebase?
|
## 1. Summary — current state
|
||||||
|
|
||||||
| Metric | Count | Note |
|
| Metric | Count | Notes |
|
||||||
|---|---|---|
|
|---|---:|---|
|
||||||
| Files using `style={{...}}` inline blocks | 25 | A few have 50–72 blocks each — major drift source |
|
| `style={{...}}` blocks total occurrences (all files) | ~700 | top files: see §3 Pattern B |
|
||||||
| `!important` declarations in `index.css` | 122 | Indicates repeated style-override fights |
|
| Files using inline styles | 25 | hotspots: OverviewTab (72), MyStrategiesTab (64), HomeView (55) |
|
||||||
| `<Button>` (primitive) usages | 31 files | Has known overlap-with-block-children issue (we hit it in Copilot) |
|
| `!important` declarations in `index.css` | 122 | Indicates repeated specificity fights — see Pattern G |
|
||||||
| `<table>` direct usages | 6 files | Several without scroll containers |
|
| `<Button>` (primitive) usages | 31 files | Some at risk for the `whitespace-nowrap` overflow bug we hit — see Pattern A |
|
||||||
| Largest views | `PositionsTab` 2035, `SimpleView` 1635, `OverviewTab` 1061, `AdminTab` 990, `HomeView` 917 | High UI surface = high bug surface |
|
| Direct `<table>` usages | 6 files | Most have scroll wrappers; a few don't — see Pattern D |
|
||||||
|
| `<Card>` with `overflow-hidden` | 7 instances | Some intentional (rounded image clipping), some clip anchored UI — see Pattern D |
|
||||||
|
| Largest views (line count) | `PositionsTab` 2035, `SimpleView` 1635, `OverviewTab` 1061, `AdminTab` 990, `HomeView` 917 | High UI surface = high bug surface |
|
||||||
|
| `truncate` usages | 15 occurrences in 7 files | ~11 lack a paired `title=`/`aria-label` — see Pattern E |
|
||||||
|
|
||||||
The codebase has **pattern bugs**, not just one-off issues. The same root causes recur across files. Fixing them centrally (CSS overrides, lint rules) is much cheaper than file-by-file.
|
The codebase has **pattern bugs**, not just one-offs. The same root causes recur across files. Fixing centrally (CSS overrides + lint rules) is much cheaper than file-by-file.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Already fixed in this session
|
## 2. Already fixed in this session
|
||||||
|
|
||||||
|
All centralized in `web/src/layout-fixes.css` (495 lines, 24 numbered sections, single review surface). Each section has a comment explaining what it overrides and why.
|
||||||
|
|
||||||
| # | Issue | Fix | Commit |
|
| # | Issue | Fix | Commit |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | Plans page: saved-setup cards clipped Edit/Delete buttons; chips truncated with ellipsis instead of wrapping; outer grid forced 380px on saved column | Refactored saved-setup-card to `overflow: visible`, flex-wrap topline, real chip wrap, single-column form below 1440px | `46a357c` |
|
| 1 | Plans page — saved-setup cards clipped Edit/Delete buttons; chips truncated; outer grid forced 380px column | Refactored saved-setup card to `overflow: visible`, flex-wrap topline, real chip wrapping, single-column form below 1440px | [`46a357c`](#) |
|
||||||
| 2 | Critical alert banner overflowed at narrow widths; loud red styling | Restructured into wrapper + inner row with min-width:0 text + shrink-0 CTA, max-width 1280px, soft tinted background | `1fa2bcd` |
|
| 2 | Critical alert banner — overflow at narrow widths; loud red styling | Restructured into `.critical-alert-banner-{wrap,inner,icon,text,cta}` with `min-width: 0` text + `flex-shrink: 0` CTA, max-width 1280px, soft tinted background | [`1fa2bcd`](#) |
|
||||||
| 3 | Sidebar logo: "ByteLyst / Trading OS" clipped due to two competing CSS rules (44×44 grid vs 56px flex) | Forced flex layout with min-height 60px, brand text ellipsis-only | `1fa2bcd` |
|
| 3 | Sidebar logo — "ByteLyst / Trading OS" clipped due to two competing CSS rules (44×44 grid vs 56px flex) for `.trading-sidebar-logo` | Forced flex layout, min-height 60px, brand text ellipsis-only | [`1fa2bcd`](#) |
|
||||||
| 4 | AI Trading Copilot: Quick Action title and prompt overlapped; not responsive | Replaced `<Button>` with native `<button>` with explicit flex-column; responsive 1/2-col grid; line-clamp on prompt | `343ffb4` |
|
| 4 | AI Trading Copilot — Quick Action title and prompt overlapped; not responsive | Replaced `<Button>` with native `<button>` + explicit flex-column; responsive 1/2-col grid; `line-clamp` on prompt | [`343ffb4`](#) |
|
||||||
| 5 | DevOps panel tables overflowed | Wrapped in `overflow-x: auto`, table-layout: fixed, break-word on cells | `d2420f5d` |
|
| 5 | DevOps panel tables overflowed | Wrapped in `overflow-x: auto`, `table-layout: fixed`, `word-break: break-word` on cells | [`d2420f5d`](#) (common-plat) |
|
||||||
| 6 | Global: `.dashboard-main { overflow: hidden }` clipped popovers/modals | `overflow: visible !important` | `f463ff6` |
|
| 6 | Global: `.dashboard-main { overflow: hidden }` clipped popovers/modals | `overflow: visible !important` | [`f463ff6`](#) |
|
||||||
| 7 | Global: right panel covers main on narrow viewports | Collapses to 260px below 1280px, hidden below 1024px | `f463ff6` |
|
| 7 | Global: right panel covers main on narrow viewports | Collapses to 260px below 1280px, hidden below 1024px | [`f463ff6`](#) |
|
||||||
| 8 | Global: tables push layout horizontally | `.dashboard-content table` wrapped with `overflow-x: auto` | `f463ff6` |
|
| 8 | Global: tables push layout horizontally | `.dashboard-content table` wrapped via `overflow-x: auto` | [`f463ff6`](#) |
|
||||||
| 9 | Global: header search/indices crowd each other | flex-wrap, search shrinks 240–360px, indices wrap | `f463ff6` |
|
| 9 | Global: header search/indices crowd each other | `flex-wrap`, search shrinks 240–360px, indices wrap | [`f463ff6`](#) |
|
||||||
|
|
||||||
All centralized in `web/src/layout-fixes.css` (495 lines). Single file = single review surface.
|
Tip: when reviewing layout-fixes.css, read top-to-bottom — sections are ordered roughly from most-global to most-specific.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Issue patterns (root causes)
|
## 3. Issue patterns (root causes)
|
||||||
|
|
||||||
### Pattern A — `<Button>` primitive ate block children
|
### Pattern A — `<Button>` primitive collapses multi-line content
|
||||||
|
|
||||||
**Symptom:** Two `<span style={{display:block}}>` inside a `<Button>` overlap each other.
|
**Symptom:** A `<Button>` containing two stacked text spans renders them overlapping or with the second one clipped.
|
||||||
**Root cause:** `<Button>` from `@bytelyst/ui` is `inline-flex` internally; block children get treated as inline-flex items.
|
|
||||||
**Fix pattern:** Use native `<button>` with explicit `display: flex; flex-direction: column` for cards-as-buttons.
|
**Actual root cause** (verified against `@bytelyst/ui` source, `packages/ui/src/components/Button.tsx`):
|
||||||
**Where to look:** any `<Button>` containing 2+ stacked text spans/divs.
|
```ts
|
||||||
|
const baseStyles = 'inline-flex shrink-0 items-center justify-center whitespace-nowrap ...';
|
||||||
|
const sizes = { sm: 'h-8 ...', md: 'h-10 ...', lg: 'h-11 ...' };
|
||||||
|
```
|
||||||
|
Three things compound:
|
||||||
|
1. **`whitespace-nowrap`** collapses any text content onto a single line.
|
||||||
|
2. **Fixed height** (`h-8/h-10/h-11`) clips anything taller.
|
||||||
|
3. **`items-center` + flex-row** lays children side-by-side, not stacked.
|
||||||
|
|
||||||
|
So a Button intended as a "card-style action" (e.g. a title above a prompt) will:
|
||||||
|
- Force both spans onto one line.
|
||||||
|
- Clip the second span vertically.
|
||||||
|
- Lay them horizontally if they survive the height clip.
|
||||||
|
|
||||||
|
**Fix pattern:** When you need a button-shaped card with stacked content, do not use `<Button>`. Use a native `<button>` with explicit `flex-direction: column`, `white-space: normal`, and `height: auto`. The `<Button>` primitive is correct for single-line CTAs only.
|
||||||
|
|
||||||
|
**Where to look:** 31 files use `<Button>`. Most are safe (single label). Audit any whose children include 2+ stacked text spans, an `<img>` next to text, or use `<br />`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
grep -rn "<Button" --include="*.tsx" web/src | wc -l # 31 files
|
# Quick triage:
|
||||||
|
grep -rEn '<Button[^>]*>.*\n[^<]*<' --include='*.tsx' web/src
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Recommendation for design system:** add a documented `<CardButton>` primitive that drops `whitespace-nowrap` and removes fixed height, OR add an `as="card"` variant to `<Button>`. This is upstream work in `learning_ai_common_plat`.
|
||||||
|
|
||||||
### Pattern B — Inline styles instead of classes
|
### Pattern B — Inline styles instead of classes
|
||||||
|
|
||||||
**Symptom:** Style drift, no responsive breakpoints, can't override via CSS without `!important`.
|
**Symptom:** Style drift, no responsive breakpoints, can't override via CSS without `!important` (which is why `!important` count is 122).
|
||||||
**Files:** `OverviewTab` (72 blocks), `MyStrategiesTab` (64), `HomeView` (55), `ChatControl` (28), `MembershipTab` (26), `MarketplaceTab` (23), `VisualRuleBuilder` (22).
|
|
||||||
**Why bad:** Inline styles can't have `@media` queries, can't be tweaked via theme, fight `index.css` for specificity.
|
**Verified hotspots** (count of `style={{` blocks per file):
|
||||||
**Fix pattern:** Lift repeated patterns into named CSS classes; keep one-off positional styles inline.
|
| File | Count | Notes |
|
||||||
|
|---|---:|---|
|
||||||
|
| `tabs/OverviewTab.tsx` | 72 | landing dashboard |
|
||||||
|
| `tabs/MyStrategiesTab.tsx` | 64 | strategies grid |
|
||||||
|
| `views/HomeView.tsx` | 55 | root `/` |
|
||||||
|
| `components/ChatControl.tsx` | 28 | Copilot — animations OK to leave inline |
|
||||||
|
| `tabs/MembershipTab.tsx` | 26 | |
|
||||||
|
| `tabs/MarketplaceTab.tsx` | 23 | |
|
||||||
|
| `components/strategy/VisualRuleBuilder.tsx` | 22 | |
|
||||||
|
|
||||||
|
**Why bad:** Inline styles can't have `@media` queries, can't be tweaked via theme tokens, and fight `index.css` for specificity (every `index.css` rule needs `!important` to win against an inline style).
|
||||||
|
|
||||||
|
**Fix pattern:** Lift repeated patterns into named CSS classes. Keep one-off positional/animation styles inline.
|
||||||
|
|
||||||
### Pattern C — Hardcoded pixel widths in JSX
|
### Pattern C — Hardcoded pixel widths in JSX
|
||||||
|
|
||||||
**Symptom:** Doesn't shrink on narrow viewports → horizontal overflow.
|
**Symptom:** Doesn't shrink on narrow viewports → horizontal overflow.
|
||||||
**Findings:**
|
|
||||||
- `App.tsx:226` `maxWidth: 420` (toast container)
|
|
||||||
- `Header.tsx:77` `width: 300` (search) — already fixed via CSS override
|
|
||||||
- `MembershipTab.tsx:48,160` `width: 44`, `maxWidth: 1400`
|
|
||||||
- `VisualRuleBuilder.tsx:111,326` `width: 70`, `width: 260`
|
|
||||||
- `RightPanel.tsx:109` `width: 52, height: 44` (image — fine, intentional thumbnail)
|
|
||||||
- `PositionsTab.tsx` multiple `truncate max-w-[80px/120px/180px/220px/260px]`
|
|
||||||
|
|
||||||
**Fix pattern:** Replace fixed widths with flex/grid + `min-width: 0` + `flex: 0 1 <basis>`. For images, keep fixed but add `flex-shrink: 0`.
|
**Verified findings:**
|
||||||
|
- `App.tsx:226` — `maxWidth: 420` (toast container)
|
||||||
|
- `tabs/MembershipTab.tsx:48` — `width: 44` (icon — likely fine, but verify)
|
||||||
|
- `tabs/MembershipTab.tsx:160` — `maxWidth: 1400` (page container — fine)
|
||||||
|
- `components/strategy/VisualRuleBuilder.tsx:326` — `width: 260` (input — should be `100%` with `max-width: 260px`)
|
||||||
|
- `components/layout/Header.tsx:164` — `width: 7` (likely separator — fine)
|
||||||
|
|
||||||
### Pattern D — `overflow: hidden` on cards that have anchored children
|
**Fix pattern:** Replace fixed widths with flex/grid + `min-width: 0` + `flex: 0 1 <basis>`. For genuine icons, keep fixed width but add `flex-shrink: 0` so the icon doesn't accidentally squish.
|
||||||
|
|
||||||
**Symptom:** Buttons, badges, dropdowns clipped.
|
### Pattern D — `overflow: hidden` on cards with anchored children
|
||||||
**Findings (current):**
|
|
||||||
- `ReconciliationAuditPanel.tsx:447,509,579` — `<Card className="overflow-hidden rounded-2xl">` × 3
|
|
||||||
- `AdminTab.tsx:372,608,843` — admin panel sections with overflow-hidden
|
|
||||||
- `PositionsTab.tsx:1392,1578` — `table-container ... overflow-hidden` (this one is correct — it's the wrapper for a horizontal-scroll table)
|
|
||||||
|
|
||||||
**Fix pattern:** Use `overflow: hidden` only when intentionally clipping (e.g. rounded corners over images). For cards that contain actionable children, prefer `overflow: visible` and let children manage their own bounds.
|
**Symptom:** Buttons, badges, dropdowns clipped at card edges.
|
||||||
|
|
||||||
### Pattern E — Truncation without `title` attribute
|
**Verified findings:**
|
||||||
|
- `tabs/ReconciliationAuditPanel.tsx:447,509,579` — `<Card className="overflow-hidden rounded-2xl">` × 3 (likely intentional for rounded clipping; verify children don't overflow)
|
||||||
|
- `tabs/AdminTab.tsx:372,608,843` — admin panel sections (panels with internal scroll regions; `overflow-hidden` here is correct)
|
||||||
|
- `tabs/PositionsTab.tsx:1392,1578` — `.table-container ... overflow-hidden` wraps a horizontal-scroll table (this is **correct** — the inner `<table>` has its own scroll behavior)
|
||||||
|
|
||||||
**Symptom:** User sees "PKRBKDW7…" with no way to read the full value.
|
**Fix pattern:** Use `overflow: hidden` only when intentionally clipping (rounded-corner image masks, internal scroll containers). For cards that contain dropdowns/popovers/badges that anchor outside their own bounds, prefer `overflow: visible` and let children manage their own bounds via `position: absolute` or portal.
|
||||||
**Findings:** 11 instances of `className="...truncate..."` without a sibling `title=` attribute (a11y + UX regression).
|
|
||||||
**Fix pattern:** Always pair `truncate` with `title={value}` so hover/focus shows full content.
|
### Pattern E — Truncation without paired full-text affordance
|
||||||
|
|
||||||
|
**Symptom:** User sees `PKRBKDW7…` with no way to reveal the full value.
|
||||||
|
|
||||||
|
**Note:** this is a **discoverability/UX issue, not a strict a11y bug.** CSS `text-overflow: ellipsis` does not hide content from screen readers — the full text is still in the DOM and announced. But sighted users on hover/focus get nothing without a `title=` (tooltip) or `aria-label`.
|
||||||
|
|
||||||
|
**Verified count:** 15 truncate occurrences across 7 files; ~11 lack a paired `title=` attribute on the same or adjacent line.
|
||||||
|
|
||||||
|
**Files:** PositionsTab (7), HistoryTab (4), AdminTab (4), OverviewTab (3), EntriesTab (2), TradeProfileManager (2), MarketOpportunities (2).
|
||||||
|
|
||||||
|
**Fix pattern:** Always pair `truncate` with `title={fullValue}` so hover-tooltip shows full content. Even better: a small Radix tooltip on click, since `title` is suppressed on touch devices.
|
||||||
|
|
||||||
### Pattern F — Grid templates without `minmax(0, 1fr)`
|
### Pattern F — Grid templates without `minmax(0, 1fr)`
|
||||||
|
|
||||||
**Symptom:** Grid children with content wider than `1fr` push past container.
|
**Symptom:** Grid children with content wider than `1fr` push past container width.
|
||||||
**Findings:**
|
|
||||||
- `index.css:1230` `grid-template-columns: 2fr 1.2fr 1fr 1.2fr` (no minmax)
|
|
||||||
- `index.css:1895` `grid-template-columns: repeat(3, 1fr)` (no minmax)
|
|
||||||
- `index.css:2325` `grid-template-columns: 100px minmax(220px, 1fr) 90px ...` — fine
|
|
||||||
- `MarketplaceTab.tsx:36,86`, `MembershipTab.tsx:100`, `MyStrategiesTab.tsx:127` — `gridTemplateColumns: '1fr 1fr'` inline
|
|
||||||
|
|
||||||
**Fix pattern:** Always use `minmax(0, 1fr)` not bare `1fr` in grid templates. Default `min-width` is `auto` which equals `min-content` of children; minmax(0,...) lets the column shrink.
|
**Verified findings:**
|
||||||
|
|
||||||
|
In `index.css`:
|
||||||
|
- `:1230` `grid-template-columns: 2fr 1.2fr 1fr 1.2fr` (no minmax — risk)
|
||||||
|
- `:1895` `grid-template-columns: repeat(3, 1fr)` (no minmax — risk)
|
||||||
|
- `:2325` `grid-template-columns: 100px minmax(220px, 1fr) 90px ...` (correct)
|
||||||
|
|
||||||
|
Inline (5 instances confirmed):
|
||||||
|
- `tabs/MarketplaceTab.tsx:36,86` — `gridTemplateColumns: '1fr 1fr'`
|
||||||
|
- `tabs/MembershipTab.tsx:100` — `gridTemplateColumns: '1fr 1fr'`
|
||||||
|
- `tabs/MyStrategiesTab.tsx:127,367` — `gridTemplateColumns: '1fr 1fr'`
|
||||||
|
|
||||||
|
**Fix pattern:** Use `minmax(0, 1fr)` not bare `1fr` in any grid template where children may have content wider than the column. Default `min-width` for grid items is `auto` ≈ `min-content`; `minmax(0, ...)` allows real shrinking. (This is the most common cause of mysterious horizontal scrolling.)
|
||||||
|
|
||||||
### Pattern G — Two competing CSS rules for the same class
|
### Pattern G — Two competing CSS rules for the same class
|
||||||
|
|
||||||
**Symptom:** Styles you can't reason about; depends on rule order in 3000-line stylesheet.
|
**Symptom:** Styles you can't reason about; final result depends on rule order in 3000-line stylesheet.
|
||||||
**Where seen:** `.trading-sidebar-logo` (44×44 grid block at line 349 + 56px flex block at line 1546). The logo bug we fixed was a direct consequence.
|
|
||||||
**Likely more:** `index.css` has 122 `!important` declarations — each one suggests a fight between two rules. Worth a future cleanup pass.
|
|
||||||
|
|
||||||
### Pattern H — `100vw` / `vw` units inside layouts with a fixed sidebar
|
**Confirmed example:** `.trading-sidebar-logo` had a 44×44 grid block at line 349 + a 56px flex block at line 1546. The logo bug we fixed (commit `1fa2bcd`) was a direct consequence.
|
||||||
|
|
||||||
**Symptom:** Element widths don't subtract sidebar/scrollbar width → overflow.
|
**Likely more:** 122 `!important` declarations indicate repeated overrides. Each `!important` is a fight between two rules — find the duplicates and consolidate.
|
||||||
**Findings:**
|
|
||||||
- `index.css:1620` `width: min(42vw, 460px) !important;`
|
|
||||||
- `index.css:1791,1798,1822,1832` — `clamp(... vw ...)` for fluid typography (these are fine)
|
|
||||||
- `ChatControl.tsx:635` `maxWidth: 'calc(100vw - 32px)'` (acceptable — modal portaled to body, no sidebar offset)
|
|
||||||
- `TradeProfileManager.tsx:908` `max-w-[92vw]` (also portaled side-drawer — acceptable)
|
|
||||||
|
|
||||||
**Fix pattern:** Inside the dashboard shell use `100%` not `100vw`. `vw` only safe for elements outside the sidebar layout (modals, banners).
|
**How to find them:**
|
||||||
|
```bash
|
||||||
|
# Crude: grep class selectors and count duplicates
|
||||||
|
grep -oE '^\.[a-z][a-z0-9-]+' web/src/index.css | sort | uniq -c | sort -rn | head -20
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern H — `100vw` / `vw` units
|
||||||
|
|
||||||
|
**Verified findings (some are bugs, most are intentional):**
|
||||||
|
|
||||||
|
| Line | Rule | Verdict |
|
||||||
|
|---|---|---|
|
||||||
|
| `index.css:1529` | `padding: 36px clamp(24px, 3vw, 44px) 56px` | ✅ fluid padding, fine |
|
||||||
|
| `index.css:1620` | `width: min(42vw, 460px) !important` | ⚠️ inside dashboard with fixed sidebar — `vw` includes scrollbar/sidebar width |
|
||||||
|
| `index.css:1791,1798,1822,1832` | `clamp(... vw ...)` for fluid type/spacing | ✅ all inside `clamp()`, fine |
|
||||||
|
| `components/ChatControl.tsx:635` | `maxWidth: 'calc(100vw - 32px)'` | ✅ portaled modal, no sidebar offset |
|
||||||
|
| `components/TradeProfileManager.tsx:908` | `max-w-[92vw]` | ✅ portaled drawer, no sidebar offset |
|
||||||
|
|
||||||
|
So only **one** real `vw` issue: `index.css:1620`. The rest are inside `clamp()` (fluid scaling) or on portaled elements (where viewport-width is correct).
|
||||||
|
|
||||||
|
**Fix pattern:** Inside the dashboard shell use `100%` of the parent flex item, not `100vw`. `vw` is only safe outside the sidebar layout.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -123,28 +187,28 @@ grep -rn "<Button" --include="*.tsx" web/src | wc -l # 31 files
|
|||||||
|
|
||||||
### 4.1 PositionsTab.tsx (2035 lines, Portfolio page)
|
### 4.1 PositionsTab.tsx (2035 lines, Portfolio page)
|
||||||
|
|
||||||
**Issues:**
|
**Verified issues:**
|
||||||
- 6+ instances of `truncate max-w-[80–260px]` for trade IDs / sub-tags
|
- 7 `truncate` usages (only some have `title=`)
|
||||||
- `<table className="pro-table">` already has horizontal scroll wrapper, but the inner `pro-table` has hardcoded `min-width: 980px` — fine for desktop, but on narrow viewports the table scrolls within itself (correct behavior, by design)
|
- 4 of those use `truncate max-w-[80–260px]` — narrow widths likely cause visible clipping for trade IDs, sub-tags
|
||||||
- 3 separate scroll regions (`.positions-section`, `.orders-section`, `.history-tab`) each with their own min-widths
|
- Inner `pro-table` has `min-width: 980px` and is wrapped in horizontal-scroll — correct for desktop, but on narrow viewports the table scrolls within itself (intentional)
|
||||||
- Many badges with hardcoded font/padding via inline style
|
- 3 separate scroll regions (`.positions-section`, `.orders-section`, `.history-tab`) each with their own min-widths — fine
|
||||||
|
|
||||||
**Recommendation:** Keep horizontal scroll behavior (it's correct for data tables). Add `title={fullValue}` to the 6 truncate instances that don't have it. Audit badge inline styles → lift to `.position-badge` class.
|
**Recommendation:** Keep horizontal scroll behavior (correct for data tables). Add `title={fullValue}` to all 7 truncate sites. Inline-style audit: many badges have hardcoded font/padding via inline style — lift to a `.position-badge` class with size variants.
|
||||||
|
|
||||||
### 4.2 OverviewTab.tsx (1061 lines, dashboard landing)
|
### 4.2 OverviewTab.tsx (1061 lines, dashboard landing)
|
||||||
|
|
||||||
**Issues:**
|
**Issues:**
|
||||||
- 72 inline `style={{...}}` blocks
|
- 72 inline `style={{...}}` blocks (largest in repo)
|
||||||
- Likely uses many `<Button>` with multiple-span content → potential overlap (Pattern A)
|
- Likely uses `<Button>` with multi-content children → potential Pattern A overlap
|
||||||
- No responsive breakpoints in inline styles
|
- No responsive breakpoints in inline styles
|
||||||
|
|
||||||
**Recommendation:** Highest-payoff refactor target. Move colors/spacing into Tailwind utilities or `.overview-*` classes. Estimated 3–4 hours for a clean pass.
|
**Recommendation:** Highest-payoff refactor target. Move colors/spacing into Tailwind utilities or `.overview-*` classes. Estimated 3–4 hours for a clean pass.
|
||||||
|
|
||||||
### 4.3 HomeView.tsx (917 lines, root /)
|
### 4.3 HomeView.tsx (917 lines, root `/`)
|
||||||
|
|
||||||
**Issues:**
|
**Issues:**
|
||||||
- 55 inline style blocks
|
- 55 inline style blocks
|
||||||
- Hero panel uses `clamp(... vw ...)` for fluid typography — looks intentional
|
- Hero panel uses `clamp(... vw ...)` for fluid typography — looks intentional, leave alone
|
||||||
- Action grid likely uses `gridTemplateColumns: '1fr 1fr'` (Pattern F)
|
- Action grid likely uses `gridTemplateColumns: '1fr 1fr'` (Pattern F)
|
||||||
|
|
||||||
**Recommendation:** Same as OverviewTab — extract into classes. Verify mobile breakpoint for action grid (`grid-cols-1 md:grid-cols-2 xl:grid-cols-3`).
|
**Recommendation:** Same as OverviewTab — extract into classes. Verify mobile breakpoint for action grid (`grid-cols-1 md:grid-cols-2 xl:grid-cols-3`).
|
||||||
@ -152,68 +216,74 @@ grep -rn "<Button" --include="*.tsx" web/src | wc -l # 31 files
|
|||||||
### 4.4 TradeProfileManager.tsx (slide-over drawer)
|
### 4.4 TradeProfileManager.tsx (slide-over drawer)
|
||||||
|
|
||||||
**Issues:**
|
**Issues:**
|
||||||
- Uses `w-[520px] max-w-[92vw]` — fixed width on desktop. May feel cramped for forms with many fields.
|
- Uses `w-[520px] max-w-[92vw]` — fixed width on desktop, may feel cramped for long forms
|
||||||
- Drawer slides in from right, portaled to body (good).
|
- 22 inline-style blocks
|
||||||
|
- Drawer slides in from right, portaled to body — correct
|
||||||
|
|
||||||
**Recommendation:** Consider `w-[640px]` on `xl:` breakpoint for more breathing room. Inspect the form layout for the same overlap-with-Button issue we saw in Copilot.
|
**Recommendation:** Consider `xl:w-[640px]` for breathing room. Inspect form layout for the same Button overlap issue we saw in Copilot.
|
||||||
|
|
||||||
### 4.5 ConfigTab.tsx + AdminTab.tsx tables
|
### 4.5 ConfigTab.tsx + AdminTab.tsx tables
|
||||||
|
|
||||||
**ConfigTab:**
|
**ConfigTab:** uses `<th className="min-w-[200px/300px/400px]">` — guarantees horizontal scroll. Acceptable for a config table.
|
||||||
- `<th className="min-w-[200px/300px/400px]">` — guarantees horizontal scroll. Acceptable for a config table; user can scroll.
|
|
||||||
|
|
||||||
**AdminTab:**
|
**AdminTab:** `max-h-[400px] overflow-y-auto` × 2 instances bound height for log/audit panels (intentional). `max-w-[260px]` truncate on token displays — intentional (secrets shouldn't fully render). 4 truncate uses — verify each has appropriate title or is intentionally redacted.
|
||||||
- `max-h-[400px] overflow-y-auto` × 2 instances. Bounded height for log/audit panels — likely intentional.
|
|
||||||
- `max-w-[260px]` truncate on token displays — good (these are secrets, full value isn't useful).
|
|
||||||
|
|
||||||
**Recommendation:** No urgent fix. Both pages are admin-only and the scroll behavior is appropriate for their content density.
|
**Recommendation:** No urgent fix. Both pages are admin-only and the scroll behavior matches their content density.
|
||||||
|
|
||||||
### 4.6 ChatControl.tsx (Copilot, just fixed)
|
### 4.6 ChatControl.tsx (Copilot, just fixed)
|
||||||
|
|
||||||
Quick Actions block fixed (commit `343ffb4`). Remaining minor:
|
Quick Actions block fixed in commit `343ffb4`. 28 inline-style blocks remain — most are background gradients, animations, portal styles. No further action needed.
|
||||||
- 28 inline-style blocks (background gradients, animations) — keep, they're animation-specific
|
|
||||||
- Floating button portal styles — fine
|
|
||||||
- Backdrop styles — fine
|
|
||||||
|
|
||||||
**No further action needed.**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Recommendations
|
## 5. Recommendations
|
||||||
|
|
||||||
### Immediate (1 commit, 1 hour)
|
### Immediate (1 commit, ~1 hour)
|
||||||
|
|
||||||
1. Add `title=` to the 11 untitled truncate instances. Quick a11y win.
|
1. **Add `title=` to the 11 untitled truncate sites.** Files: PositionsTab, HistoryTab, AdminTab, OverviewTab, EntriesTab, TradeProfileManager, MarketOpportunities. Quick UX win.
|
||||||
2. Add `minmax(0, 1fr)` to the 5 inline `gridTemplateColumns: '1fr 1fr'` instances in MarketplaceTab/MembershipTab/MyStrategiesTab. Prevents overflow when content gets long.
|
2. **Fix the 5 inline `gridTemplateColumns: '1fr 1fr'`** instances by adding `minmax(0, 1fr) minmax(0, 1fr)` (MarketplaceTab × 2, MembershipTab × 1, MyStrategiesTab × 2).
|
||||||
3. Convert `<Button>` to native `<button>` in any Button that has 2+ block children inside (audit needed).
|
3. **Fix `index.css:1230` and `:1895`** — add `minmax(0, ...)` to bare `1fr` columns.
|
||||||
|
4. **Fix `index.css:1620`** — replace `42vw` with `min(42%, 460px)` or relative-to-container sizing.
|
||||||
|
5. **Audit the 31 `<Button>` files** — for any with multi-line/stacked children, replace with native `<button>` (see Pattern A fix).
|
||||||
|
|
||||||
### Short-term (1 week)
|
### Short-term (1 week)
|
||||||
|
|
||||||
4. **ESLint rules** (preventive):
|
6. **ESLint rules** (preventive):
|
||||||
- `tailwindcss/no-custom-classname` to catch inline arbitrary widths like `w-[80px]`
|
- Custom rule: warn when `truncate` class is used without `title=` or `aria-label=` on the same element.
|
||||||
- Custom rule: `truncate` requires `title` (catches Pattern E)
|
- Custom rule: warn on `gridTemplateColumns` containing bare `1fr` (suggest `minmax(0, 1fr)`).
|
||||||
- Custom rule: flag `gridTemplateColumns` containing bare `1fr` without `minmax`
|
- Custom rule: warn on `<Button>` with non-string children that span multiple lines.
|
||||||
- Custom rule: warn on inline `style` blocks with > 5 properties (encourage class extraction)
|
- Limit inline `style` props with > 5 properties (encourage class extraction).
|
||||||
5. **CSS audit:** scan `index.css` for class names with multiple definitions; consolidate. Aim to reduce `!important` count from 122 → < 60.
|
7. **CSS audit script:** scan `index.css` for class selectors with multiple definitions (Pattern G). Aim to reduce `!important` count from 122 → < 60.
|
||||||
|
|
||||||
### Medium-term (2-3 weeks)
|
### Medium-term (2–3 weeks)
|
||||||
|
|
||||||
6. **Refactor OverviewTab + HomeView** to extract inline styles into a `home.css` / `overview.css` partial. Highest user impact (these are the landing pages).
|
8. **Refactor OverviewTab + HomeView + MyStrategiesTab** to extract inline styles into `home.css` / `overview.css` / `strategies.css` partials. Highest user impact (these are landing pages).
|
||||||
7. **Component primitives audit:** the `<Button>` overlap issue suggests `@bytelyst/ui` Button needs a documented prop for stacked children, or a separate `<CardButton>` primitive. Worth a discussion with the design system team.
|
9. **Component primitives audit:** add a `<CardButton>` primitive in `@bytelyst/ui` (or an `as="card"` variant on `<Button>`) that drops `whitespace-nowrap` and `h-{size}` constraints. Document the difference. Pushes the Pattern A fix to the design system rather than the consumers.
|
||||||
|
10. **Theme token sweep:** the trading app uses `--bl-*` and `--ml-*` CSS variables. Verify no hardcoded hex values remain in `web/src/**/*.{ts,tsx,css}` (excluding token-default fallbacks like `var(--bl-accent, #5A8CFF)` which are correct per `AGENTS.md`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Quick verification commands
|
## 6. How to verify a UI fix
|
||||||
|
|
||||||
|
When making changes to layout-fixes.css or any UI component, verify in this order:
|
||||||
|
|
||||||
|
1. **Visual smoke-check at 5 viewport widths:** 1920, 1440, 1280, 1024, 768. Use Chrome DevTools' device toolbar.
|
||||||
|
2. **No horizontal scroll on `<body>`:** open DevTools → run `document.body.scrollWidth > window.innerWidth` — should be `false` at all widths.
|
||||||
|
3. **No element extends past its parent:** inspect the suspected component → Computed → check `width` and parent's `width`.
|
||||||
|
4. **Modals/popovers not clipped:** open every modal (Copilot, TradeProfileManager, settings dialogs) at narrow widths.
|
||||||
|
5. **Tables scroll horizontally inside their wrapper, not the page:** scroll the page horizontally — should not move.
|
||||||
|
6. **Run the build:** `cd web && npm run build` (catches Tailwind errors that don't show in dev).
|
||||||
|
7. **Run typecheck:** `npm run typecheck` if available (catches stray prop changes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Reproducible audit commands
|
||||||
|
|
||||||
|
These commands regenerate the metrics in this doc. Run from `learning_ai_invt_trdg/web/src/`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check current state
|
# Inline-style hotspots (count per file, sorted desc)
|
||||||
cd /opt/bytelyst/learning_ai_invt_trdg/web/src
|
for f in $(grep -rln "style={{" --include='*.tsx' . | grep -v test); do
|
||||||
|
|
||||||
# Truncate without title
|
|
||||||
grep -rn "truncate\b" --include='*.tsx' | grep -v test | grep -v "title=" | wc -l
|
|
||||||
|
|
||||||
# Inline style blocks per file (drift indicator)
|
|
||||||
for f in $(grep -rln "style={{" --include='*.tsx' | grep -v test); do
|
|
||||||
count=$(grep -c "style={{" "$f")
|
count=$(grep -c "style={{" "$f")
|
||||||
[ "$count" -gt 20 ] && echo "$count $f"
|
[ "$count" -gt 20 ] && echo "$count $f"
|
||||||
done | sort -rn
|
done | sort -rn
|
||||||
@ -221,18 +291,46 @@ done | sort -rn
|
|||||||
# !important count
|
# !important count
|
||||||
grep -c "!important" index.css
|
grep -c "!important" index.css
|
||||||
|
|
||||||
# Hardcoded pixel widths in JSX
|
# Truncate occurrences (pair with title= manually)
|
||||||
grep -rEn "(width|minWidth|maxWidth):\s*[0-9]+(px)?[,$]" --include='*.tsx' | grep -v test
|
grep -rEn "\btruncate\b" --include='*.tsx' . | grep -v test | wc -l
|
||||||
|
grep -rE "\btruncate\b" --include='*.tsx' -B1 -A2 . | grep -v test | grep -c "title="
|
||||||
|
|
||||||
|
# Pixel widths in JSX (correct regex — no broken char class)
|
||||||
|
grep -rEn "(width|minWidth|maxWidth):\s*[0-9]+,?\s*$" --include='*.tsx' . | grep -v test
|
||||||
|
|
||||||
|
# Bare 1fr in inline grid templates (Pattern F)
|
||||||
|
grep -rEn "gridTemplateColumns.*\b1fr\b" --include='*.tsx' . | grep -v test
|
||||||
|
|
||||||
|
# Bare 1fr in CSS (no minmax wrapper)
|
||||||
|
grep -nE "grid-template-columns:.*1fr" index.css | grep -v "minmax"
|
||||||
|
|
||||||
|
# Card overflow-hidden
|
||||||
|
grep -rn "Card.*overflow-hidden" --include='*.tsx' . | grep -v test
|
||||||
|
|
||||||
|
# Files using <Button> primitive
|
||||||
|
grep -rln "<Button" --include='*.tsx' . | grep -v test
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Files changed in recent UI fix sweep
|
## 8. Open questions / things to investigate
|
||||||
|
|
||||||
|
These were noted during the audit but not verified — investigate before acting on them.
|
||||||
|
|
||||||
|
1. **Does `<Button asChild>` (Radix Slot) inherit `whitespace-nowrap`?** If consumers wrap `<Button asChild><Link>` with multi-line content, they may hit Pattern A even when avoiding `<Button>` directly.
|
||||||
|
2. **Are any `<Card>` clipping issues real (Pattern D)?** I flagged 6 candidate `overflow-hidden` Cards. Open each at narrow widths and look for cut-off badges, dropdowns, or focus rings.
|
||||||
|
3. **Mobile / touch:** the audit focused on desktop widths down to 768px. Below that, behavior is unverified. Worth a separate mobile pass if mobile is a target.
|
||||||
|
4. **Dark theme contrast:** the app is dark-only. No light theme audit performed. If light theme is planned, all `var(--bl-*)` tokens need light-mode definitions (check `@bytelyst/design-tokens`).
|
||||||
|
5. **The 6 direct `<table>` files that were not in §3 Pattern D.** Verify each has a horizontal-scroll wrapper.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Files changed in recent UI fix sweep
|
||||||
|
|
||||||
- `web/src/layout-fixes.css` (new, 495 lines) — all global UI overrides centralized
|
- `web/src/layout-fixes.css` (new, 495 lines) — all global UI overrides centralized
|
||||||
- `web/src/main.tsx` — imports layout-fixes.css
|
- `web/src/main.tsx` — imports layout-fixes.css after index.css and App.css
|
||||||
- `web/src/App.tsx` — refactored alert banner JSX
|
- `web/src/App.tsx` — refactored alert banner JSX to nested-div structure
|
||||||
- `web/src/components/ChatControl.tsx` — refactored Quick Actions
|
- `web/src/components/ChatControl.tsx` — refactored Quick Actions from `<Button>` to native `<button>`
|
||||||
- `web/src/components/layout/AppShell.tsx` — added /devops redirect
|
- `web/src/components/layout/AppShell.tsx` — added `/devops` → `/settings?section=DevOps` redirect
|
||||||
|
|
||||||
The single-file approach (`layout-fixes.css`) is intentional: every global UI fix lands there with a numbered section + comment explaining the issue. Easy to review, easy to revert one section without touching others.
|
The single-file approach (`layout-fixes.css`) is intentional: every global UI fix lands there with a numbered section + comment explaining the issue. Easy to review, easy to revert one section without touching others.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user