diff --git a/mseries/OS9-HANDOFF.md b/mseries/OS9-HANDOFF.md index 91df22c91..5f5df0ab6 100644 --- a/mseries/OS9-HANDOFF.md +++ b/mseries/OS9-HANDOFF.md @@ -1,3 +1,7 @@ +## T3 panel menu HIG audit — 2026-10-27 + +Audit of the unified T3 Wingpanel menu against the elementary HIG: [`t3-wingpanel/HIG-AUDIT.md`](t3-wingpanel/HIG-AUDIT.md). Fixed in source: nested scrollers that hid the second plan window and running threads, menu width jumping between tabs, idle tooltip "0 running" and a never-updated accessible name, raw token counts/cost/timestamps, and the double use of "Activity". Fixture tests pass (`t3-wingpanel/test.sh`); before/after renders in `t3-wingpanel/receipts/2026-10-27-hig-audit/`. Not installed or loaded into the live panel. Owner then approved H1–H5, done in the same PR: one tab bar, plain menu-item footer (Open T3, Activity, Fleet, T3 Settings…), 16 px embedded icons, system text size, badge only for approvals. + ## Public messaging status — 2026-10-27 The public homepage and support matrix now distinguish the scoped October 20 BlueFerry texting observation from the owner’s October 22 decision that the Messages and Signal experience is incomplete and deferred. See [MESSAGING-STATUS.md](MESSAGING-STATUS.md). This documentation update does not change or qualify the apps, phone pairing or hardware. diff --git a/mseries/codexbar-wingpanel/Indicator.vala b/mseries/codexbar-wingpanel/Indicator.vala index 5a2401294..094d07626 100644 --- a/mseries/codexbar-wingpanel/Indicator.vala +++ b/mseries/codexbar-wingpanel/Indicator.vala @@ -112,7 +112,7 @@ namespace MSeries { chip_label.add_css_class ("chip"); refresh_display = new Gtk.Stack (); - refresh_display.add_named (new Gtk.Image.from_icon_name ("view-refresh-symbolic") { pixel_size = 36 }, "idle"); + refresh_display.add_named (new Gtk.Image.from_icon_name ("view-refresh-symbolic") { pixel_size = dock_icon_size () }, "idle"); refresh_spinner = new Gtk.Spinner (); refresh_display.add_named (refresh_spinner, "busy"); refresh_button = new Gtk.Button () { @@ -138,6 +138,9 @@ namespace MSeries { pages.add_titled (spend_box, "spend", "Spend"); var switcher = new Gtk.StackSwitcher () { stack = pages, halign = Gtk.Align.FILL }; switcher.add_css_class ("mseries-switcher"); + // Inside the T3 menu, its own tab bar is the only one: show Quota; Sessions and + // Spend stay one click away through the Usage and Spending details buttons below. + switcher.visible = !on_demand; menu.append (switcher); var scroll = new Gtk.ScrolledWindow () { @@ -145,7 +148,7 @@ namespace MSeries { hscrollbar_policy = Gtk.PolicyType.NEVER, vscrollbar_policy = Gtk.PolicyType.AUTOMATIC, propagate_natural_height = true, - min_content_height = 300, + min_content_height = on_demand ? -1 : 300, max_content_height = 600 }; menu.append (scroll); @@ -303,9 +306,12 @@ namespace MSeries { Gtk.StyleContext.add_provider_for_display (display, style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } + /* 36 px matches the standalone double-size dropdown; the T3 menu uses normal text, so 16 px. */ + private int dock_icon_size () { return on_demand ? 16 : 36; } + private Gtk.Button make_dock_button (string label, string icon_name, string action) { var button = new Gtk.Button () { - child = new Gtk.Image.from_icon_name (icon_name) { pixel_size = 36 }, + child = new Gtk.Image.from_icon_name (icon_name) { pixel_size = dock_icon_size () }, tooltip_text = label }; button.add_css_class ("flat"); diff --git a/mseries/dock-taskbar/TaskbarT3RPopover.vala b/mseries/dock-taskbar/TaskbarT3RPopover.vala index 716b6523b..318bcc525 100644 --- a/mseries/dock-taskbar/TaskbarT3RPopover.vala +++ b/mseries/dock-taskbar/TaskbarT3RPopover.vala @@ -66,7 +66,8 @@ public class Dock.TaskbarT3RPopover : GLib.Object { margin_end = 12 }; - pages = new Gtk.Stack () { hhomogeneous = false, vhomogeneous = false }; + // Every tab shares one width so the menu does not resize when switching tabs. + pages = new Gtk.Stack () { hhomogeneous = true, vhomogeneous = false }; var switcher = new Gtk.StackSwitcher () { stack = pages, halign = Gtk.Align.FILL, margin_top = 8, margin_start = 8, margin_end = 8 }; var shell = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); shell.append (switcher); @@ -113,12 +114,12 @@ public class Dock.TaskbarT3RPopover : GLib.Object { var token_card = new Gtk.Box (Gtk.Orientation.VERTICAL, 4); token_card.add_css_class ("taskbar-t3r-card"); - var token_heading = new Gtk.Label ("Tokens · provider total") { xalign = 0 }; + var token_heading = new Gtk.Label ("Tokens") { xalign = 0 }; token_heading.add_css_class ("heading"); token_total = new Gtk.Label ("Token history unavailable") { xalign = 0 }; token_total.add_css_class ("title-3"); token_counts = detail_label ("Input — · Output —\nCached input — · Reasoning —"); - token_scope = detail_label ("Provider total across all accounts; not selected-account usage."); + token_scope = detail_label ("All accounts, not just the selected one"); token_period = detail_label ("Date window unavailable · Unknown"); token_source = detail_label ("T3 server token history · not read yet"); token_cost = detail_label (""); @@ -135,43 +136,17 @@ public class Dock.TaskbarT3RPopover : GLib.Object { var usage_heading = new Gtk.Label ("Plan windows · selected account") { xalign = 0 }; usage_heading.add_css_class ("heading"); root.append (usage_heading); + // The whole menu scrolls; a nested scroller here clipped the second plan window. usage_windows = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); - root.append (new Gtk.ScrolledWindow () { - child = usage_windows, - hscrollbar_policy = Gtk.PolicyType.NEVER, - vscrollbar_policy = Gtk.PolicyType.AUTOMATIC, - propagate_natural_height = true, - max_content_height = 210 - }); + root.append (usage_windows); - var activity_title = new Gtk.Label ("Activity") { xalign = 0 }; + // "Threads", not "Activity": the Activity button below opens Shiplog, a different view. + var activity_title = new Gtk.Label ("Threads") { xalign = 0 }; activity_title.add_css_class ("heading"); root.append (activity_title); + // At most three threads are listed, so no nested scroller (it hid the running thread). activity = new Gtk.Box (Gtk.Orientation.VERTICAL, 2); - var activity_scroll = new Gtk.ScrolledWindow () { - child = activity, - hscrollbar_policy = Gtk.PolicyType.NEVER, - vscrollbar_policy = Gtk.PolicyType.AUTOMATIC, - propagate_natural_height = true, - min_content_height = 32, - max_content_height = 180 - }; - root.append (activity_scroll); - - var actions = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); - var usage = new Gtk.Button.with_label ("Usage"); - usage.clicked.connect (() => run_action ("usage")); - var repositories = new Gtk.Button.with_label ("Repositories"); - repositories.clicked.connect (() => run_action ("repositories")); - var settings = new Gtk.Button.with_label ("Settings"); - settings.clicked.connect (() => run_action ("settings")); - var shiplog = new Gtk.Button.with_label ("Activity"); - shiplog.tooltip_text = "Shiplog · today's commits, merged PRs and closed issues"; - shiplog.clicked.connect (() => run_action ("activity")); - actions.append (usage); - actions.append (repositories); - actions.append (settings); - root.append (actions); + root.append (activity); freshness = new Gtk.Label ("No status snapshot yet") { xalign = 0 }; freshness.add_css_class ("caption"); @@ -184,20 +159,15 @@ public class Dock.TaskbarT3RPopover : GLib.Object { error_label.margin_start = 12; error_label.margin_end = 12; - var open_full = new Gtk.Button.with_label ("Open T3"); - open_full.add_css_class ("suggested-action"); - open_full.clicked.connect (() => run_action ("open-full")); - var footer = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6) { - margin_start = 12, margin_end = 12, margin_bottom = 10 - }; - shiplog.hexpand = true; - open_full.hexpand = true; - var machines = new Gtk.Button.with_label ("Fleet"); - machines.tooltip_text = "Remote machine and agent status over SSH"; - machines.clicked.connect (() => run_action ("fleet")); - footer.append (shiplog); - footer.append (machines); - footer.append (open_full); + // Like Sound or Network: content first, then plain menu items, settings last. + // The tabs above already cover Usage and GitHub, so no duplicate buttons for them. + var footer = new Gtk.Box (Gtk.Orientation.VERTICAL, 0) { margin_bottom = 3 }; + footer.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL) { margin_top = 3, margin_bottom = 3 }); + footer.append (menu_item ("Open T3", "open-full", null)); + footer.append (menu_item ("Activity", "activity", "Shiplog · today's commits, merged PRs and closed issues")); + footer.append (menu_item ("Fleet", "fleet", "Remote machine and agent status over SSH")); + footer.append (new Gtk.Separator (Gtk.Orientation.HORIZONTAL) { margin_top = 3, margin_bottom = 3 }); + footer.append (menu_item ("T3 Settings…", "settings", null)); shell.append (footer); content = new Gtk.ScrolledWindow () { child = shell, hscrollbar_policy = Gtk.PolicyType.NEVER, @@ -269,6 +239,15 @@ public class Dock.TaskbarT3RPopover : GLib.Object { public Gtk.Widget get_host_widget () { return content; } + private Gtk.Button menu_item (string label, string action, string? tooltip) { + var item = new Gtk.Button.with_label (label) { tooltip_text = tooltip }; + item.add_css_class (Granite.STYLE_CLASS_MENUITEM); + item.add_css_class ("flat"); + ((Gtk.Label) item.child).xalign = 0; + item.clicked.connect (() => run_action (action)); + return item; + } + public void open_host () { if (!hosted) return; host_open = true; @@ -368,7 +347,6 @@ public class Dock.TaskbarT3RPopover : GLib.Object { if (style_installed) return; var style = new Gtk.CssProvider (); style.load_from_string (""" - .taskbar-t3r-popup { font-size: 1.2em; } .taskbar-t3r-popup .mseries-codexbar, .taskbar-t3r-popup .mseries-repobar { font-size: 1em; } popover.taskbar-t3r-popup > contents { @@ -473,7 +451,7 @@ public class Dock.TaskbarT3RPopover : GLib.Object { connection.label = "Pair with a T3 server in Settings"; token_total.label = "Token history unavailable"; token_counts.label = "Input — · Output —\nCached input — · Reasoning —"; - token_scope.label = "Provider total across all accounts; not selected-account usage."; + token_scope.label = "All accounts, not just the selected one"; token_period.label = "Date window unavailable · Unavailable"; token_source.label = "T3 server token history · not read yet"; token_cost.visible = false; @@ -495,7 +473,18 @@ public class Dock.TaskbarT3RPopover : GLib.Object { if (!usage.has_member (key)) return "—"; var node = usage.get_member (key); if (node.get_value_type () != typeof (int64) || node.get_int () < 0) return "—"; - return node.get_int ().to_string (); + return grouped (node.get_int ()); + } + + /* 18436221 → "18,436,221": long token counts are unreadable without separators. */ + private static string grouped (int64 value) { + var digits = value.to_string (); + var text = new StringBuilder (); + for (int i = 0; i < digits.length; i++) { + if (i > 0 && (digits.length - i) % 3 == 0) text.append_c (','); + text.append_c (digits[i]); + } + return text.str; } private void render_tokens (Json.Object feed, string selected_id, bool live) { @@ -504,7 +493,7 @@ public class Dock.TaskbarT3RPopover : GLib.Object { if (usage == null || selected_id.length == 0) { token_total.label = "Token history unavailable"; token_counts.label = "Input — · Output —\nCached input — · Reasoning —"; - token_scope.label = "Provider total across all accounts; not selected-account usage."; + token_scope.label = "All accounts, not just the selected one"; token_period.label = "Date window unavailable · Unavailable"; token_source.label = "T3 server token history · not read yet"; return; @@ -520,18 +509,17 @@ public class Dock.TaskbarT3RPopover : GLib.Object { token_count (usage, "cachedInputTokens"), token_count (usage, "reasoningTokens")) : "Input — · Output —\nCached input — · Reasoning —"; string kind = MSeries.JsonBoundary.text (usage, "providerKind"); - token_scope.label = (kind.length > 0 ? kind + " provider total" : "Provider total") + - " · all accounts\nNot selected-account usage."; + token_scope.label = (kind.length > 0 ? kind : "Provider") + " · all accounts, not just the selected one"; string since = MSeries.JsonBoundary.text (usage, "sinceDay"); string until = MSeries.JsonBoundary.text (usage, "untilDay"); token_period.label = (since.length > 0 && until.length > 0 ? since + " – " + until : "Date window unavailable") + " · " + freshness; string read_at = MSeries.JsonBoundary.text (usage, "readAt"); token_source.label = "T3 server token history · " + - (read_at.length > 0 ? "read " + read_at : "not read yet"); + (read_at.length > 0 ? freshness_text (read_at).replace ("Updated", "read") : "not read yet"); double cost = MSeries.JsonBoundary.number (usage, "estimatedCostUsd", -1); if (known && cost.is_finite () && cost >= 0) { - token_cost.label = "Estimated cost · $%.4f USD".printf (cost); + token_cost.label = "Estimated cost · $%.2f".printf (cost); token_cost.visible = true; } } @@ -615,7 +603,8 @@ public class Dock.TaskbarT3RPopover : GLib.Object { if (!live) { clear_activity (); append_message (total == 0 ? "No live activity is available while offline." : - "Last sync showed %u active item(s). Reconnect to refresh.".printf (total)); + total == 1 ? "Last sync showed 1 active thread. Reconnect to refresh." : + "Last sync showed %u active threads. Reconnect to refresh.".printf (total)); } else if (total == 0) { append_message ("No active work right now."); } else if (total > 3) { @@ -628,9 +617,8 @@ public class Dock.TaskbarT3RPopover : GLib.Object { string id = item.get_string_member_with_default ("id", ""); string model = item.get_string_member_with_default ("model", ""); int64 requests = item.has_member ("count") ? int64.max (1, item.get_int_member ("count")) : 1; - string detail = pending ? "Needs approval · %s request(s)".printf (requests.to_string ()) : - "Running"; - if (model.length > 0) detail += " · Model: %s".printf (model); + string detail = !pending ? "Running" : requests == 1 ? "Needs approval" : + "Needs approval · %s requests".printf (requests.to_string ()); var text = new Gtk.Box (Gtk.Orientation.VERTICAL, 1); var primary = new Gtk.Label (title) { xalign = 0, ellipsize = Pango.EllipsizeMode.END }; var secondary = new Gtk.Label (detail) { xalign = 0, ellipsize = Pango.EllipsizeMode.END }; @@ -640,6 +628,7 @@ public class Dock.TaskbarT3RPopover : GLib.Object { text.append (secondary); if (live && id.length > 0) { var button = new Gtk.Button () { child = text, hexpand = true }; + if (model.length > 0) button.tooltip_text = "Model: " + model; button.add_css_class ("flat"); button.clicked.connect (() => open_thread (id)); activity.append (button); diff --git a/mseries/dock-taskbar/prepare-source.sh b/mseries/dock-taskbar/prepare-source.sh index 93a0295ee..fc7bdf0f7 100644 --- a/mseries/dock-taskbar/prepare-source.sh +++ b/mseries/dock-taskbar/prepare-source.sh @@ -78,7 +78,7 @@ for filename, required in { 'src/meson.build': ['Taskbar.vala', 'StartMenu.vala', 'TaskbarCodexBar.vala', 'codexbar/Indicator.vala', 'TaskbarRepoBar.vala', 'repobar/Indicator.vala', 'TaskbarNotifications.vala', 'AirDropPanel.vala', 'TaskbarAirDrop.vala', 'TaskbarT3RPopover.vala'], 'src/Taskbar.vala': ['new TaskbarT3', 'new TaskbarNotifications', 'new TaskbarAirDrop', 'end_box.append (status_box)'], 'src/AppSystem/Launcher.vala': ['new TaskbarT3RPopover (this, app)', 't3r_popup.toggle_from_icon ()'], - 'src/TaskbarT3RPopover.vala': ['"tokenUsage"', '"usedPercent"', '"repositories"', 'Timeout.add (250', 'Timeout.add (450'], + 'src/TaskbarT3RPopover.vala': ['"tokenUsage"', '"usedPercent"', '"settings"', 'Timeout.add (250', 'Timeout.add (450'], 'src/TaskbarAirDrop.vala': ['new MSeries.AirDropPanel'], 'src/AirDropPanel.vala': ['Receive Files'], 'src/MainWindow.vala': ['taskbar-mode', 'TaskbarRow'], diff --git a/mseries/t3-wingpanel/HIG-AUDIT.md b/mseries/t3-wingpanel/HIG-AUDIT.md new file mode 100644 index 000000000..83e377923 --- /dev/null +++ b/mseries/t3-wingpanel/HIG-AUDIT.md @@ -0,0 +1,96 @@ +# T3 panel menu: UI/UX and elementary HIG audit (2026-10-27) + +Scope: the unified T3 Wingpanel indicator (`mseries/t3-wingpanel/Indicator.vala`) and the +menu it hosts, `mseries/dock-taskbar/TaskbarT3RPopover.vala`, which embeds the CodexBar +(`mseries/codexbar-wingpanel/Indicator.vala`) and RepoBar widgets as its Usage and GitHub tabs. +The same menu also opens from the dock, so every fix here lands in both places. + +Evidence: source reading plus fixture screenshots in light and dark +(`receipts/2026-10-27-hig-audit/`, before on the left, after on the right). Not checked: a live +Wingpanel load, real keyboard focus order in the panel, or a screen reader. + +Line numbers refer to `origin/dev` after the fix PR unless marked. + +## Fixed in this PR + +| # | Finding | Where | Fix | +|---|---------|-------|-----| +| F1 | **Plan windows and threads were hidden.** Both lists sat in their own scroll area inside the menu's scroll area. GTK gave them almost no height, so only the first plan window showed (its gauge cut off) and the second thread never appeared. A 92 % weekly window was invisible. | `TaskbarT3RPopover.vala` usage/activity scrollers (were lines 139–159) | Removed the inner scrollers; the menu itself scrolls. | +| F2 | **The menu changed width on every tab switch** (512 → 416 → 488 px), so the popover jumped under the pointer. | `TaskbarT3RPopover.vala:71` (`hhomogeneous = false`) | One width for all tabs (`hhomogeneous = true`). | +| F3 | **Idle tooltip said "T3 · 0 running".** The accessible name never changed after start-up, so a screen reader always heard "Usage, GitHub, Activity and Fleet", never the status. | `t3-wingpanel/Indicator.vala:18–30` | Native two-line Granite tooltip (`T3` / status in secondary text), "Idle" when nothing runs, correct plurals, and the same text as the accessible name. | +| F4 | **Unreadable numbers.** "18436221 tokens", "$41.2873 USD", and a raw "read 2026-10-27T23:38:48.929679Z" timestamp. | `render_tokens` | Digit grouping (18,436,221), cents ($41.29), relative time ("read just now"). | +| F5 | **Two different things called "Activity".** The thread list heading and the footer button (which opens Shiplog) shared one name. | thread list heading | Heading renamed to "Threads". | +| F6 | **Machine-style wording.** "2 request(s)", "1 active item(s)", "Tokens · provider total" plus a two-line repeat of the same scope, and "· Model: claude-opus-…" truncating every thread subtitle. | `append_thread`, `render_activity`, token card | Real plurals, "Tokens" heading with one scope line ("Claude · all accounts, not just the selected one"), model moved to the row tooltip. | + +## Owner-approved layout changes (H1–H5, done) + +The owner approved these the same day; they are in the same PR. + +| # | Was | Now | +|---|-----|-----| +| H1 | Two stacked tab bars on the Usage tab (Usage/T3 server/GitHub, then Quota/Sessions/Spend) and three button rows. | One tab bar. Embedded CodexBar shows Quota only (its switcher is hidden when `on_demand`); Sessions and Spend open from its Usage details / Spending details buttons. The footer is a separator and plain menu items: Open T3, Activity, Fleet, then a separator and "T3 Settings…". The duplicate Usage / Repositories buttons are gone (M2). | +| H2 | 36 px icons in the embedded CodexBar row. | 16 px when embedded (`dock_icon_size ()`); the standalone double-size dropdown keeps 36 px. Its 300 px minimum height is also dropped when embedded. | +| H3 | Menu text at 1.2em. | System text size, like Sound and Network. | +| H4 | Blue suggested-action "Open T3" button. | Plain menu item (`Granite.STYLE_CLASS_MENUITEM` + `flat`). | +| H5 | Badge showed the running-thread count. | Badge only for approvals waiting; running work shows in the tooltip only. | + +The GitHub tab's Dashboard / Pinned / Attention / All row is a list filter, not page navigation, so it stays. + +## Still open + +### Medium + +**M1. The default tab is Usage, which needs CodexBar to be signed in.** When it is not (fixture, +fresh install), the menu opens to "No quota data yet" with a centred 64 px icon, even when T3 is +live with threads waiting. Fix: open on "T3 server" (status first), or switch to it when a thread +needs approval. + +**M2. (Done with H1.) Duplicate actions.** The row "Usage / Repositories / Settings" on the T3 server tab opens app +pages that duplicate the Usage and GitHub tabs one click above. Fix: drop "Usage" and +"Repositories"; keep "Settings…" (with an ellipsis, because it opens a window) as a menu item. + +**M3. Naming drifts:** "T3-R Pantheon" (header), "T3" (tooltip, Open T3), "T3-R application" +(error text), "RepoBar"/"CodexBar" inside user-facing strings ("…no repositories RepoBar can read"). +Fix: one product name in the UI; internal component names stay out of copy. + +**M4. Full-colour 28 px app icon and title header** on the server tab +(`TaskbarT3RPopover.vala:86–105`). Wingpanel menus have no app header; the panel icon already says +which menu this is. Fix: drop the header and lead with the connection line and account. + +**M5. Forced minimum height of 420 px** in hosted mode (`TaskbarT3RPopover.vala:194`) leaves +blank space under short pages (GitHub empty state). Fix: let the popover size to content up to the +640 px cap. + +**M6. Opening a thread spawns the binary directly** (`TaskbarT3RPopover.vala:693`) without a launch +context, so there is no startup feedback and focus may not move to the app. Fix: activate the +app's `open-thread` action through `DesktopAppInfo`/`GApplication` with the display launch context, +as `run_action` already does. + +**M7. Thread rows have no visual affordance** beyond a flat button; the pending state relies on +colour alone (`warning` class). The text "Needs approval" carries meaning, so this is acceptable, +but an `dialog-warning-symbolic` 16 px icon at the start of pending rows would match how elementary +marks attention. + +### Low + +**L1. No translation setup.** Strings are plain literals; there is no gettext domain. Already noted +in `README.md`. Fix when the panel is packaged: wrap in `_()` and add `po/`. + +**L2. Deprecated API.** `Granite.STYLE_CLASS_MENU` (`TaskbarT3RPopover.vala:57`, deprecated in +Granite 7.7 in favour of `Gtk.PopoverMenu`) and `Gtk.StyleContext.add_provider_for_display`. +Harmless today; migrate with the H1 rework. + +**L3. Custom card styling** (`alpha(currentColor, 0.045)` backgrounds, 10 px radius, +`TaskbarT3RPopover.vala:368–373`) reads fine in both schemes (checked in screenshots) but is a +local look. Granite's `card` style class would follow the system theme and accent automatically. + +**L4. Dates as ISO days** ("2026-10-21 – 2026-10-27"). A locale date ("Oct 21 – 27") is easier to +read. + +## What is already right + +- Symbolic panel icon, recoloured by the panel in light and dark. +- Gauges use theme semantic colours (success / warning / error), not hard-coded hex. +- Stale and offline states keep last-known data with an age and say how to recover. +- No polling while closed; Escape and outside click are owned by Wingpanel. +- Wrapping and ellipsizing are set on dynamic labels. diff --git a/mseries/t3-wingpanel/Indicator.vala b/mseries/t3-wingpanel/Indicator.vala index 7b123522d..7abb62c32 100644 --- a/mseries/t3-wingpanel/Indicator.vala +++ b/mseries/t3-wingpanel/Indicator.vala @@ -15,22 +15,29 @@ namespace MSeries { badge = new Gtk.Label ("") { visible = false }; badge.add_css_class ("caption"); display.append (badge); - display.update_property (Gtk.AccessibleProperty.LABEL, "T3 · Usage, GitHub, Activity and Fleet", -1); - display.tooltip_text = "T3 · Usage, GitHub, Activity and Fleet"; + set_status ("Usage, GitHub, Activity and Fleet"); menu = new Dock.TaskbarT3RPopover (display, 28, true); menu.request_close.connect (() => close ()); menu.status_changed.connect ((live, pending, running) => { - badge.label = live && pending > 0 ? pending.to_string () : - live && running > 0 ? running.to_string () : ""; + // A number only when something needs you; running work stays quiet (tooltip says it). + badge.label = live && pending > 0 ? pending.to_string () : ""; badge.visible = badge.label.length > 0; - display.tooltip_text = !live ? "T3 · No live server status" : pending > 0 ? - "T3 · %s awaiting approval".printf (pending.to_string ()) : - "T3 · %s running".printf (running.to_string ()); + set_status (!live ? "No live server status" : + pending == 1 ? "1 thread needs approval" : + pending > 1 ? "%s threads need approval".printf (pending.to_string ()) : + running == 1 ? "1 thread running" : + running > 1 ? "%s threads running".printf (running.to_string ()) : "Idle"); }); notification_owner = new RepoBarIndicator (); visible = true; } + /* Native two-line panel tooltip; screen readers get the same status, not a stale label. */ + private void set_status (string status) { + display.tooltip_markup = "T3\n" + Granite.TOOLTIP_SECONDARY_TEXT_MARKUP.printf (Markup.escape_text (status)); + display.update_property (Gtk.AccessibleProperty.LABEL, "T3, " + status, -1); + } + public override Gtk.Widget get_display_widget () { return display; } public override Gtk.Widget? get_widget () { return menu.get_host_widget (); } public override void opened () { menu.open_host (); } diff --git a/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/README.md b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/README.md new file mode 100644 index 000000000..128b886cd --- /dev/null +++ b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/README.md @@ -0,0 +1,9 @@ +# HIG audit receipts — 2026-10-27 + +Fixture renders of the T3 panel menu, before (left) and after (right) the fix PR including H1–H5, in light and +dark: `server-*`, `usage-*`, `github-*`. Made under Xvfb with the cairo renderer from a private +HOME, a fixture `status.json` (Claude selected, 59 % session and 92 % weekly windows, one thread +needing approval and one running) and inert CodexBar/RepoBar collectors, so the Usage and GitHub +tabs show their empty states. These are not live Wingpanel captures. + +Findings and recommendations: [../../HIG-AUDIT.md](../../HIG-AUDIT.md). diff --git a/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/github-dark.png b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/github-dark.png new file mode 100644 index 000000000..fb3a2e139 Binary files /dev/null and b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/github-dark.png differ diff --git a/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/github-light.png b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/github-light.png new file mode 100644 index 000000000..d1a240e09 Binary files /dev/null and b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/github-light.png differ diff --git a/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/server-dark.png b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/server-dark.png new file mode 100644 index 000000000..42e4dc0c2 Binary files /dev/null and b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/server-dark.png differ diff --git a/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/server-light.png b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/server-light.png new file mode 100644 index 000000000..540c42fe6 Binary files /dev/null and b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/server-light.png differ diff --git a/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/usage-dark.png b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/usage-dark.png new file mode 100644 index 000000000..0cdfa364f Binary files /dev/null and b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/usage-dark.png differ diff --git a/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/usage-light.png b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/usage-light.png new file mode 100644 index 000000000..0d36a48ec Binary files /dev/null and b/mseries/t3-wingpanel/receipts/2026-10-27-hig-audit/usage-light.png differ diff --git a/mseries/ui-scale/ui-size-allowlist.json b/mseries/ui-scale/ui-size-allowlist.json index bb4f2b811..177ab6425 100644 --- a/mseries/ui-scale/ui-size-allowlist.json +++ b/mseries/ui-scale/ui-size-allowlist.json @@ -33,7 +33,7 @@ "file": "mseries/codexbar-wingpanel/Indicator.vala", "rule": "icon-px", "match": "pixel_size = 36", - "count": 3, + "count": 1, "reason": "Dropdown icon glyph, doubled with the popover text (2em). Phase 3/4 (#332): confirm it should follow the ladder, then derive it." }, {