Skip to content
This repository was archived by the owner on Nov 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mseries/OS9-HANDOFF.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
12 changes: 9 additions & 3 deletions mseries/codexbar-wingpanel/Indicator.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand All @@ -138,14 +138,17 @@ 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 () {
child = pages,
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);
Expand Down Expand Up @@ -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");
Expand Down
111 changes: 50 additions & 61 deletions mseries/dock-taskbar/TaskbarT3RPopover.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 ("");
Expand All @@ -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");
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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 };
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion mseries/dock-taskbar/prepare-source.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
Loading
Loading