diff --git a/docs/ai-plans/2026-06-26-ui-tooltip-system.html b/docs/ai-plans/2026-06-26-ui-tooltip-system.html new file mode 100644 index 0000000..2c669b1 --- /dev/null +++ b/docs/ai-plans/2026-06-26-ui-tooltip-system.html @@ -0,0 +1,254 @@ + + + + + + UI Tooltip System Plan + + + +
+
+

UI Tooltip System Plan

+

Date: 2026-06-26 · Scope: Health dashboard UI · Issue context: #157 follow-up

+
+ +
+

Task Summary

+

+ Make project tooltips readable by default and replace remaining browser-native + title tooltips with one consistent DOM/CSS tooltip pattern. +

+
+ +
+

Current Behavior

+ +
+ +
+

Desired Behavior

+ +
+ +
+

Assumptions

+ +
+ +
+

Likely File Changes

+ +
+ +
+

Implementation Steps

+
    +
  1. Inventory template-level title= usage again and classify each as DOM tooltip, accessibility label, or non-tooltip metadata.
  2. +
  3. Update .ui-tooltip CSS so the tooltip bubble uses an explicit opaque background and readable foreground instead of relying only on potentially translucent surface tokens.
  4. +
  5. Add small alignment modifiers if needed, for example right-aligned tooltips near card edges or table cells.
  6. +
  7. Convert title-only tooltip elements to class="ui-tooltip", data-tooltip, and aria-label; add tabindex="0" for focusable explanatory spans/cells.
  8. +
  9. Remove converted native title attributes to prevent duplicate tooltip UI.
  10. +
  11. Search the touched templates after conversion and verify that no converted .ui-tooltip element still carries title.
  12. +
  13. Update tests to assert the new tooltip contract.
  14. +
  15. Update this plan with actual changes, checks run, limitations, and follow-up notes after implementation.
  16. +
+
+ +
+

Test Plan

+ +
+ +
+

Manual QA Checklist

+ +
+ +
+

Risks

+ +
+ +
+

Rollback Plan

+ +
+ +
+

Open Questions

+ +
+ +
+

Approval Gate

+

+ Production code should not be changed until this plan is approved. + After approval, implementation will proceed on a new codex/ + branch and be delivered through a pull request. +

+
+ +
+

Implementation Update

+

Status: implemented on codex/ui-tooltip-system.

+ +
+ +
+

Checks Run

+ +
+ +
+

Known Limitations

+ +
+
+ + diff --git a/internal/ui/admin_readiness_redesign_contract_test.go b/internal/ui/admin_readiness_redesign_contract_test.go index cf06778..2018664 100644 --- a/internal/ui/admin_readiness_redesign_contract_test.go +++ b/internal/ui/admin_readiness_redesign_contract_test.go @@ -256,11 +256,11 @@ func TestFragmentAdminReadinessContract_RendersValueAndUnknown(t *testing.T) { if !strings.Contains(body, "unknown") { t.Errorf("fragment missing 'unknown' marker: %s", body) } - // Baseline reason now lives in the cell's title= attribute as + // Baseline reason now lives in the shared data-tooltip attribute as // part of "baseline=… · target=… · epoch=…" tooltip — confirm // it's the value-side label, not just the cell text. - if !strings.Contains(body, `title="baseline=`+storage.BaselineReasonWarmup) { - t.Errorf("fragment missing baseline_warmup title attribute: %s", body) + if !strings.Contains(body, `data-tooltip="baseline=`+storage.BaselineReasonWarmup) { + t.Errorf("fragment missing baseline_warmup tooltip attribute: %s", body) } // Pivot includes tenant column with the schema name. if !strings.Contains(body, schema) { diff --git a/internal/ui/style.go b/internal/ui/style.go index 600d71b..a388d77 100644 --- a/internal/ui/style.go +++ b/internal/ui/style.go @@ -13,6 +13,16 @@ const cssStyle = ` --card-bg: var(--surface); --card-border: var(--border); --fg: var(--text); + --tooltip-bg: #111827; + --tooltip-fg: #f9fafb; + --tooltip-border: rgba(255,255,255,0.16); + --tooltip-shadow: 0 14px 38px rgba(15,23,42,0.28); +} +[dark-mode] { + --tooltip-bg: #f8fafc; + --tooltip-fg: #111827; + --tooltip-border: rgba(15,23,42,0.18); + --tooltip-shadow: 0 18px 42px rgba(0,0,0,0.48); } body { min-height: 100vh; font-size: 15px; } @@ -173,17 +183,17 @@ body { min-height: 100vh; font-size: 15px; } .ui-tooltip::after { content: attr(data-tooltip); position: absolute; - z-index: 30; + z-index: 1000; left: 0; top: calc(100% + 8px); width: max-content; max-width: min(340px, calc(100vw - 32px)); padding: 9px 11px; - border: 1px solid var(--border); + border: 1px solid var(--tooltip-border); border-radius: 8px; - background: var(--surface); - color: var(--text); - box-shadow: var(--shadow); + background: var(--tooltip-bg); + color: var(--tooltip-fg); + box-shadow: var(--tooltip-shadow); font-size: 12px; font-weight: 500; line-height: 1.45; @@ -192,19 +202,33 @@ body { min-height: 100vh; font-size: 15px; } white-space: normal; text-align: left; opacity: 0; + visibility: hidden; pointer-events: none; transform: translateY(-2px); - transition: opacity 0.12s ease, transform 0.12s ease; + transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s ease; } +.ui-tooltip--right::after, .readiness-trust-badge.ui-tooltip::after { left: auto; right: 0; } +.ui-tooltip--center::after { + left: 50%; + transform: translate(-50%, -2px); +} +.ui-tooltip--cell { + cursor: help; +} .ui-tooltip:hover::after, .ui-tooltip:focus-visible::after { opacity: 1; + visibility: visible; transform: translateY(0); } +.ui-tooltip--center:hover::after, +.ui-tooltip--center:focus-visible::after { + transform: translate(-50%, 0); +} /* Webhook status badge on the settings page. Live-updates every 5s while state=pending; static otherwise. Colour-coded by state so the diff --git a/internal/ui/templates/nav.html b/internal/ui/templates/nav.html index 641faee..e10f6ed 100644 --- a/internal/ui/templates/nav.html +++ b/internal/ui/templates/nav.html @@ -14,12 +14,16 @@ {{T .Lang "all_metrics"}} - + {{T .Lang "nav_settings"}} {{if .IsAdmin}} - + {{T .Lang "admin_title"}} @@ -28,7 +32,9 @@ {{.Label}} {{end}}
-
diff --git a/internal/ui/templates/pages/dashboard.html b/internal/ui/templates/pages/dashboard.html index b2b7f02..1983484 100644 --- a/internal/ui/templates/pages/dashboard.html +++ b/internal/ui/templates/pages/dashboard.html @@ -11,13 +11,11 @@
{{T .Lang "readiness_today_label"}} {{T .Lang "methodology_status_heuristic_personalized"}} {{if .ReadinessServing.Has}} {{.ReadinessServing.Label}} @@ -91,8 +89,9 @@
{{T .Lang "energy_label"}} - {{T .Lang "methodology_status_heuristic_prescriptive"}} {{.EnergyBank.Current}}/{{.EnergyBank.Capacity}} @@ -103,7 +102,7 @@
- {{if lt .EnergyBank.Capacity 100}}
{{end}} + {{if lt .EnergyBank.Capacity 100}}
{{end}}
{{if .EnergyBank.Components}} diff --git a/internal/ui/templates/partials/admin_readiness_contract.html b/internal/ui/templates/partials/admin_readiness_contract.html index f4609e9..046de03 100644 --- a/internal/ui/templates/partials/admin_readiness_contract.html +++ b/internal/ui/templates/partials/admin_readiness_contract.html @@ -23,10 +23,10 @@ {{.Tenant}} {{.Date}} - {{.Recovery.Text}} - {{.Passive.Text}} - {{.Chronic.Text}} - {{.Acute.Text}} + {{.Recovery.Text}} + {{.Passive.Text}} + {{.Chronic.Text}} + {{.Acute.Text}} {{end}} diff --git a/internal/ui/templates/partials/admin_readiness_monitoring.html b/internal/ui/templates/partials/admin_readiness_monitoring.html index 5c17058..3c51ba0 100644 --- a/internal/ui/templates/partials/admin_readiness_monitoring.html +++ b/internal/ui/templates/partials/admin_readiness_monitoring.html @@ -34,7 +34,7 @@ {{T $.Lang "admin_monitoring_signal_coverage"}} {{.SubScore}}/{{.TargetKind}} {{.Status}} - + {{.Coverage}}{{if .TopReason}} · {{.TopReason}}{{end}}
{{T $.Lang "admin_monitoring_window"}} {{.Window}}
{{if .Freshness}}
{{.Freshness}}
{{end}}