Skip to content

fix(dashboard): status-text shrinks correctly when sibling text grows - #15

Closed
cnighswonger wants to merge 119 commits into
ArkNill:mainfrom
cnighswonger:fix/status-text-shrink-flex-basis
Closed

fix(dashboard): status-text shrinks correctly when sibling text grows#15
cnighswonger wants to merge 119 commits into
ArkNill:mainfrom
cnighswonger:fix/status-text-shrink-flex-basis

Conversation

@cnighswonger

Copy link
Copy Markdown
Contributor

Summary

One-character CSS fix for a status-banner layout regression that surfaces when a provider has both a longer-than-usual heading AND incident detail content (currently visible on the OpenAI tile during the active "Partial System Degradation" incident).

What was broken

On a 1/3-row banner width, the OpenAI tile rendered as:

● OpenAI: Minor disruption — Partial System Degradation
fetched 32s ago
   Partial Disruption of ChatGPT Workspace Connector Write Actions [minor] (identified)

fetched 32s ago was wrapping to its own line between the heading and the incident, instead of sitting beside the heading on line 1.

Root cause

.status-banner .status-text used flex: 1 1 auto, so its initial basis was the natural content width of the heading. When that natural width met or exceeded the available banner width, the flex algorithm gave the entire row to the text and pushed .status-fetched (which has flex-shrink: 0) onto a new flex line.

This was latent for a while but became visible after the recent font-size bump on .status-fetched (0.75rem → 0.88rem) made its rendered width wider — the threshold at which the layout breaks lowered.

Fix

Change .status-text basis from auto to 0. Text starts at 0 width and grows to fill remaining space alongside other items. .status-fetched retains its natural width on the same line; .status-incident still wraps below via its own flex-basis: 100% rule.

-.status-banner .status-text { font-weight: 600; flex: 1 1 auto; min-width: 0; }
+.status-banner .status-text { font-weight: 600; flex: 1 1 0; min-width: 0; }

Why this fix vs reverting the font-size bump

The font-size bump from 0.75rem to 0.88rem was deliberate and improves readability. Reverting it would throw that away. This fix addresses the underlying layout fragility — the banner now tolerates any sibling text width without breaking, so future font-size adjustments won't regress this again.

Verification

  • Tested locally against the live OpenAI "Partial System Degradation" incident text on a ~1/3 row width (3-tile dashboard layout).
  • Anthropic and Gemini tiles (no incident, short heading) continue to render unchanged — text and fetched on one line, no incident below.
  • Behavior matches the layout shown in the dashboard before the font-size bump regression surfaced this latent fragility.

Out of scope

  • HTML structure changes (none needed).
  • App.js changes (none needed).
  • Other status-banner styling (untouched).

ArkNill and others added 30 commits April 15, 2026 11:17
…kflow

- Add /api/v1/display endpoint with session liveness detection (TTY-based)
- Add /api/v1/sessions/terminals for terminal metadata tracking
- Add dual-zone (absolute + ratio) token threshold classification
- Add session_turns MCP tool
- Add cc_cache_fix module for prompt cache stability
- Add dashboard turn monitor section with zone badges
- Add CI workflow for Python 3.9-3.12
- Add cc-turn-counter.sh helper script
- Proxy: lifespan watchdog, trailing-slash redirects for /display and /dashboard
- Add display.py with multi-CLI process detection (claude/codex/gemini)
- Add session file parsers for Codex JSONL and Gemini JSON formats
- Add discover_external_cli_sessions() to merge non-proxy sessions
- Add provider badges (Claude Code, Codex, Gemini) to display page
- Add /display/ standalone turn monitor page with provider-aware cards
- Add backward-compatible aliases (find_claude_pid_by_tty, is_cc_process_alive)
- 35 new tests for display module
- Note: Gemini CLI oauth-personal has a known 403 server-side bug (#25425)
- Remove Tailscale internal IP from docker-compose.yml
- Remove personal path references from project-config
- Add .env.example with documented configuration variables
- Add .gitignore entries for investigation files, backups, credentials
- Gemini CLI oauth-personal 403 noted as known server-side issue
- Expand README.md with install variants, quick start, CLI status table
- Add README.ko.md Korean documentation
- Add llms.txt (compact LLM-friendly reference)
- Add llms-full.txt (full API reference with all endpoints, tools, detectors)
Unicode em dash (U+2014) crashes on Windows terminals with cp949/cp932
encoding. Replace all occurrences in Python source with ASCII double dash.
- Click CLI: `llm-relay serve --port 8083`
- Fallback CLI: same command without click dependency
- Prints dashboard/display/proxy URLs on startup
- Graceful error if uvicorn not installed (guides to pip install llm-relay[proxy])
- Remove legacy_module import and interception logic from proxy.py
- Remove legacy_table table and legacy_fn from db.py
- Remove LEGACY_TOGGLE from docker-compose.yml and .env.example
- Remove legacy_module.py gitignore entry (file no longer referenced)
- Sanitize docker-compose.yml comments (remove internal deployment notes)
- FeatureFlags detector (detect/featureflags.py) retained — read-only flag analysis
…ic release

Remove all FeatureFlags feature flag analysis code per public release cleanup.
llm-relay is a session diagnostic + proxy monitoring tool -- flag analysis
is not part of the public scope.

- Delete featureflags.py detector and its tests
- Remove FeatureFlagsConfig model class and FullReport.featureflags field
- Remove load_featureflags_config() scanner function
- Remove featureflags parameter from BaseDetector.check() and all 7 detectors
- Remove featureflags parameter from analyze_session/analyze_all
- Clean up CLI entry points (click + argparse fallback)
- Remove featureflags_flags from JSON formatter output
- 262 tests pass, ruff 0 errors
- Delete legacy README.llm-relay-detect.md, README.llm-relay-detect.ko.md, CHANGELOG.llm-relay-detect.md
- Rename LLM_RELAY_* env vars to LLM_RELAY_* (12 variables)
- Rename ~/.llm-relay/ paths to ~/.llm-relay/
- Update all logger names, CLI display strings, prog names
- Update shell scripts (start.sh, stop.sh) module refs and comments
- Update docker-compose.yml, Dockerfile, .env.example
- Update docs and llms-full.txt
- 262 tests pass, ruff 0 errors
Extract CC session liveness logic from routes.py into reusable helpers
in display.py (collect_owned_cc_pids, check_cc_session_alive). Add
fd-open based alive detection for Codex/Gemini sessions so dead CLI
sessions drop out of both /display and /turns endpoints by default.
Use ?include_dead=1 to bypass.
Record full conversation turns (user messages, assistant responses,
tool calls with arguments) across CC, Codex, and Gemini providers.

- Diff-based storage by default, raw mode optional (LLM_RELAY_HISTORY=1)
- Compaction detection (message count drop + token drop >30%)
- 3 API endpoints: GET /history, /history/{id}, /history/{id}/compactions
- MCP tool: session_history with turn range filtering
- Web viewer at /history/ with session browser and turn-by-turn replay
- Navigation links between /display/, /dashboard/, and /history/
- 53 new tests (332 total), ruff clean
Replaces hardcoded Korean strings with locale-aware message lookups.
Korean remains as a fully supported locale alongside English.

Python backend:
- New llm_relay/i18n.py with message dict (en/ko), t() function,
  LLM_RELAY_LANG env var for server-side locale
- routes.py zone classifiers use t() instead of hardcoded strings
- mcp/server.py zone labels use t()
- New /api/v1/i18n endpoint returns messages for requested locale

JS frontends:
- Browser language detection (navigator.language)
- Inline locale dicts per page (dashboard, display, history)
- display: empty state + no-prompt placeholder
- dashboard: empty state
- history: date formatting uses detected locale, env var hint fixed
  (CC_RELAY_HISTORY → LLM_RELAY_HISTORY)
- display/index.html: lang attribute set to "en" (browser detection
  handles actual locale)
…detection

Context composition analysis:
- Classify context into 6 categories (user/assistant/tool_use/tool_result/thinking/system)
- Real-time pie chart + SNR/dupes metrics on /display/ page
- Popover tooltips explaining each metric
- In-memory cache (56ms compute, 0.05ms cache hit)
- CLI analysis script: scripts/context_composition.py

Connection type detection:
- Detect SSH/tmux/screen/mosh/tailscale/native from /proc/PID/environ + parent tree
- Combined labels (ssh+tmux, tailscale+tmux, etc.)
- Badge on display session cards

Terminal monitor (llm-relay top):
- btop-style Rich TUI consuming /api/v1/display
- Session panels with zone colors, composition, prompts
- 2-second auto-refresh via Rich Live

Display improvements:
- Prompt moved to top of session card
- border-radius 4px, border-left 2px
- 51 new tests (composition 24, connection 14, TUI 13)
feat: i18n support — browser locale detection with en/ko
After ArkNill#3 (i18n support), zone labels default to English.
Updated test_yellow assertion: "주의" → "caution".
- Features section: add composition, history, TUI, i18n, connection detection
- MCP tool count: 7 → 8 (session_history)
- Install section: add [cli] extra for Rich TUI
- Quick Start: add llm-relay top + serve commands
- Web pages: add /history/ description
- llms-full.txt: add session_history tool, history endpoints, composition/connection docs
- CHANGELOG.md: v0.3.0, v0.4.0, v0.5.0 release notes
- Classifier: Alpha → Beta
- Add discover_external_cli_sessions mock to TestHistorySessionsEndpoint
  (3 tests were picking up real Codex/Gemini session files from disk)
- Fix codex fallback test: use patch() instead of monkey-patch for
  _find_session_file, create .codex/ dir in tmp_path for path detection
- Remove unused imports in test_proxy/test_history.py (get_conn, _do_capture)
- Fix scripts/context_composition.py: E501 line length, F541 empty f-string
- Add composition field to /api/v1/turns response (reuses existing
  _get_composition_safe helper, requires LLM_RELAY_HISTORY=1)
- New Context Health section on /dashboard/ below Turn Monitor:
  summary boxes (avg SNR, worst SNR, dup reads, max result%)
  plus per-session cards with SNR/result%/dupes metrics
- 2s polling via existing loadTurnMonitor — no extra API calls
- 2 new tests for composition inclusion on /turns endpoint
Composition API now returns richer data for context health monitoring:

- duplicate_reads: {filepath: count} dict (was count-only)
- duplicate_read_warning: bool when any file exceeds threshold
- snr_recommendation: session split suggestion when SNR < threshold
- New env vars: DUPLICATE_READ_WARN_THRESHOLD (default 5),
  CC_SNR_WARNING (default 0.3)

UI updates across Display, Dashboard, TUI:
- Display: top 3 duplicate file basenames + warning highlight
- Display: SNR recommendation block (red border)
- Dashboard Context Health: dupes tooltip with filenames, warning row
- TUI: top 2 duplicate files, bold red SNR recommendation

5 new tests for duplicate_reads dict, warning threshold, SNR rec
ArkNill and others added 25 commits April 30, 2026 07:06
…hancements

Zone recalibration:
- CC Zone A: 500K/700K/900K/1M (aligned with B 50/70/90/100% of 1M)
- Codex: display/zone/official unified to Official 400K
- Codex Zone A: 200K/280K/360K/400K
- Zone B messages show actual ratio (e.g. 52% 206K/400K)
- Gemini recent_peak fix (track last 5 contexts)
- CC session response: add model_window/official_context_window fields

Display enhancements:
- Remove dead isCodex/else branch
- Add 6 placeholder metrics: TTL N/A, start/prompt timestamps, Next, Est
- Add metric labels for readability
- Always show Cache/TTL badges with muted placeholder
- Rename Window → Effective, remove redundant Official label
- Normalize font sizes across display and dashboard
- Add badge-muted CSS class

Other:
- CC_ → LLM_ env var rename, 2-tier .env.public + .env.local
- Dashboard: API health, status banners, context health panel
- Executor: subprocess stdin=DEVNULL safety
- 510 tests pass
…late cumul

Codex CLI reuses the same JSONL file across exit/restart cycles.
Each new run emits task_started — reset turns/ctx/peak/cache accumulators.
Switch cumul_unique from total_token_usage (never resets) to per-request
last_token_usage accumulation so it reflects the current run only.
Also increase _tail_lines max_bytes from 512KB to 4MB.
fix: i18n leaks in dashboard / history / display (regressions + pre-existing tooltips)
# Conflicts:
#	src/llm_relay/api/display.py
#	src/llm_relay/api/routes.py
#	tests/test_api/test_turns.py
fix: i18n leaks in backend zone classifiers
Add i18n.py with 27-key message dict (zone labels, messages, UI strings,
composition tooltips) and t(key, lang, **kwargs) resolver. Wire all zone
classifiers in routes.py, display.py, and mcp/server.py through t().
Add /api/v1/i18n endpoint for frontend message loading.
Env: LLM_RELAY_LANG (default "en").
proxy.py now reads .env.public → .env.local at module load via
os.environ.setdefault(), so LLM_RELAY_HISTORY and other env vars
work without explicit export. Docker env_file and explicit vars
take precedence. Add LLM_RELAY_LANG=en to .env.public defaults.
Add I18n (en/ko, LLM_RELAY_LANG, browser auto-detect) to feature lists
in README.md, README.ko.md, and llms.txt. Update MCP tool count from 7
to 8 (session_history added).
- Unify version to 0.9.2 across pyproject.toml, __init__.py, detect/__init__.py
- Fix MCP tool count: 7→8 in README.md/ko, 6→8 in design doc (4 references)
- Add CHANGELOG entries for v0.9.1 and v0.9.2
…rapping

Two cosmetic CSS fixes for the dashboard:

1. Context Health summary tiles (Avg SNR, Worst, Dup Reads, Max Result%)
   were touching the first agent row visually. Added margin-bottom: 1rem
   on .stats-grid so the summary visually separates from the per-session
   list below it.

2. The .status-banner row (Anthropic / OpenAI / Gemini status tiles in
   the header) word-wrapped the .status-incident text into a narrow
   vertical column when an upstream incident message was long, making
   the OpenAI tile unreadable during the 2026-04-30 ChatGPT Workspace
   Connector incident. Fixed by:
     - flex-wrap: wrap on the banner so children can break to a new row
     - .status-text: flex 1 1 auto + min-width 0 to fill the headline
       row and shrink gracefully
     - .status-fetched: order 1 to stay on the headline row
     - .status-incident:not(:empty): order 2 + flex-basis 100% so it
       drops to its own full-width row beneath the headline, with
       1.2rem indent past the dot so it associates with the title
   Empty incident spans (the all-green case) stay invisible — single
   tidy row exactly like before.
…lish-upstream

fix(dashboard): polish Context Health spacing and API status banner wrapping
Add SUPPORTED_LANGS = list(MESSAGES.keys()) to i18n module for
programmatic locale enumeration. Add 17-test suite covering en/ko
key symmetry, placeholder consistency, naming convention, and t()
function behavior.
GrowthBookDetector module was removed in v0.9.0 but the lazy import
in get_detectors_for_provider() was left behind, causing
ModuleNotFoundError when called with provider_id="claude-code".
proxy/__init__.py was stuck at 0.1.0 while every other module
had been bumped to 0.9.2.
Clean up F401 (unused import) and I001 (import sorting) violations
in test_compat.py and test_doctor.py.
Apply escapeHtml() to three unescaped innerHTML insertions:
- zone message in turn monitor cards
- prompt_preview in delegation history table
- snr_recommendation in context health rows
Add type and length validation for session_id (string, max 128),
cc_pid/term_pid (integer), and tty/term_name (string, max 256)
to reject malformed payloads early.
CLAUDE.md: API 10→22 endpoints, MCP 7→8 tools, 8→7 detectors.
README.md: 8→7 detectors. Update test count to 521.
All 16 catch-all handlers in routes.py now return "Internal server
error" instead of str(e). Server-side logger.error retains full
detail for debugging.
Add LLM_RELAY_CLAUDE_HOME, LLM_RELAY_CODEX_HOME,
LLM_RELAY_GEMINI_HOME as primary env vars with CCPULSE_*
fallback for backward compatibility. Affects 5 locations
across providers, scanner, and display modules.
Align with convention used by other modules (proxy, detect, orch,
mcp, api). Previously relied on PEP 420 namespace package behavior.
README.ko.md: 8→7 detectors (2 locations)
llms.txt: 8→7 detectors (2 locations)
CLAUDE.md: update verification section to 2026-05-04 with full
audit results (XSS, error responses, env migration, i18n, 3-tier sync)
README.md and README.ko.md referenced a non-existent .env.example
file. The actual public defaults file is .env.public.
Status banners with longer headings (e.g., 'OpenAI: Minor disruption —
Partial System Degradation') were wrapping the .status-fetched element
to its own line on tiles where the heading filled the row.

Root cause: .status-text used 'flex: 1 1 auto', so its initial basis
was the natural content width of the heading. When that exceeded the
banner width, the flex algorithm gave the entire row to the text and
pushed .status-fetched (which has flex-shrink: 0) onto a new line.

Visible regression after the .status-fetched font-size bump from 0.75rem
to 0.88rem — the wider rendered timestamp made the threshold easier to
cross.

Fix: change .status-text basis to 0. Text starts at 0 width and grows
to fill remaining space alongside other items, so .status-fetched fits
on the same line. .status-incident still wraps below via its own
flex-basis: 100% rule.

One-character change. No HTML structure changes. Tested against the
current OpenAI 'Partial System Degradation' incident text on a
1/3-row banner width.
ArkNill added a commit that referenced this pull request May 20, 2026
…#15)

Change .status-banner .status-text flex-basis from `auto` to `0`. With
the natural-width basis, when the heading rendered close to the row
width (visible on the OpenAI tile during the active "Partial System
Degradation" incident text), the flex algorithm gave the entire row
to the text and pushed .status-fetched onto a new line.

With basis 0 the text starts at 0 width and grows alongside other
items; .status-fetched keeps its natural width on line 1, and
.status-incident still wraps below via its own flex-basis: 100%.

The latent fragility was made visible by the recent font-size bump
on .status-fetched (0.75rem → 0.88rem), which lowered the threshold
at which the layout breaks.

Reported and diagnosed by @cnighswonger in #15. Applied directly as
a one-character patch rather than rebasing that branch — the diff
there carried 106 files of pre-v0.5.0 state that overlapped with #16
and would have needed manual conflict resolution. Credit retained
here.
@ArkNill

ArkNill commented May 20, 2026

Copy link
Copy Markdown
Owner

Fixed in 4d041c2. Just applied the one-char change directly — rebasing this branch against main was going to be messier than it was worth, since the v0.4.0/v0.5.0 composition work overlaps here and #16 (just merged in #17) touched the same file again.

Diagnosis was solid though, that's what made this a 5-minute job. Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants