fix(dashboard): status-text shrinks correctly when sibling text grows - #15
Closed
cnighswonger wants to merge 119 commits into
Closed
fix(dashboard): status-text shrinks correctly when sibling text grows#15cnighswonger wants to merge 119 commits into
cnighswonger wants to merge 119 commits into
Conversation
…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
… detector reference
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
…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.
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. |
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
fetched 32s agowas 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-textusedflex: 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 hasflex-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-textbasis fromautoto0. Text starts at 0 width and grows to fill remaining space alongside other items..status-fetchedretains its natural width on the same line;.status-incidentstill wraps below via its ownflex-basis: 100%rule.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
Out of scope