Files
CST-agent/app/qml/Theme.qml
T
WHC 3fd039cb51 v0.8.14: icons from the desktop's theme (Breeze), readable text, composer frame
The window drew its icons with unicode glyphs -- a clip emoji for attach, a
gear, a hamburger, a chevron, a multiplication sign for delete. Those render
in whichever font happens to cover them (colour emoji in a grey toolbar) and
are a box on a machine without that glyph. They now come from the icon theme,
the way every other desktop app gets them.

Icons
- app/src/ui/ThemeIcons.{h,cpp}: a QQuickImageProvider for
  image://csttheme/<name>?color=%23rrggbb -- QIcon::fromTheme() in the
  desktop's theme (whatever the platform theme set, else breeze-dark, breeze,
  hicolor), tinted with QPainter's SourceIn and rasterised at the requested
  size and the screen's device pixel ratio. Qt only builds its theme search
  paths from XDG once a theme name is set, so the provider fills them itself
  (GenericDataLocation/icons, ~/.icons and, on NixOS,
  /run/current-system/sw/share/icons, for a shell started outside the desktop
  session). An unknown name yields a null image, which QML reports as
  Image.Error.
- app/qml/ThemeIcon.qml: the one component that asks for an icon. Every use
  still names the old character as `glyph:`, so a host with no icon theme
  degrades to exactly what the window looked like before instead of going
  blank. installThemeIcons() is called by main() and by --uitest, so the test
  loads the same icons the app does.
- Replaced: nav (application-menu), settings (settings-configure), attach
  (mail-attachment), copy (edit-copy), session delete (edit-delete), new
  session (list-add), expand/collapse (arrow-right/arrow-down), jump to the end
  (go-down), the plan checklist (task-complete/box). A tool card also shows
  what kind of tool ran -- Theme.toolIcon(): pencil for patch/write, text page
  for read, folder for list/glob, magnifier for grep, braces for a shell,
  globe for web, list for a plan. Toolbar icons are 22 px (Breeze's toolbar
  size), card and row icons 14-16 px.

Colours (the "some text is unreadable" part)
- textFaint #6d757b -> #8d959c: ~3.4:1 -> ~5:1 on the card fill. It carries
  hints, provider URLs, session metadata and placeholders, so it is text, not
  decoration.
- A selected provider row and the open session row write on the accent fill;
  they now use accentText / accentTextDim / okOnAccent instead of faint grey
  and green on blue (named "accentText", not "onAccent": QML reads any `onFoo`
  property as a signal handler and refuses the assignment).
- The filled buttons (Send, Use this) take their ink from Theme.accentInk.

Composer
- The frame is one Theme.pad around its column on every side (anchors.fill,
  implicitHeight + 2*pad). With the top margin alone the last row -- the clip
  and Send -- hung over the lower border, which is what put the clip on the
  frame instead of inside it.

app/README.md gains an "Icons, colors" section (the provider, why the theme
search paths are filled by hand, the qtsvg requirement for rasterising Breeze,
the tool map, and the contrast rules).

Verified: --uitest 52/52, --selftest OK, and the 0.8.14 window relaunched in
the Plasma session (no QML warnings).

Version 0.8.14.

mode = coding
2026-10-11 12:53:49 +08:00

99 lines
4.2 KiB
QML

// One place for the look: colors, spacing, fonts. The window, the cards and
// the dialogs all read from here, so re-theming (or swapping in Kirigami's
// Kirigami.Theme) is a single file.
pragma Singleton
import QtQuick
QtObject {
// Breeze-dark-ish (the desktop the app is built for), no hard contrast.
readonly property color bg: "#15181a"
readonly property color surface: "#1e2225"
readonly property color surfaceAlt: "#262b2f"
readonly property color surfaceHi: "#313840"
readonly property color border: "#343b41"
readonly property color text: "#eff0f1"
readonly property color textDim: "#9aa2a8"
/* Faint is still text: small print (hints, secondary lines, a selected
* row's detail) has to stay readable on both the background and the
* accent fill, so it is a step lighter than it looks in a mockup. */
readonly property color textFaint: "#8d959c"
readonly property color accent: "#3daee9"
readonly property color accentDim: "#1d6f95"
/* What is written *on* the accent fill (a selection, the open session).
* Named "accentText...", not "onAccent...": QML reads any `onFoo` as a
* signal handler and refuses the assignment. */
readonly property color accentText: "#ffffff"
readonly property color accentTextDim: "#dceefb"
/* The dark ink a filled button carries (accent, ok, err are all light). */
readonly property color accentInk: "#12161a"
readonly property color ok: "#27ae60"
readonly property color warn: "#f67400"
readonly property color err: "#da4453"
/* Green on the accent fill is mud: the "in use" mark needs its own ink. */
readonly property color okOnAccent: "#d8f5e3"
readonly property int radius: 6
readonly property int radiusSm: 3
readonly property int gap: 8
readonly property int gapL: 14
readonly property int pad: 12
readonly property int padL: 16
readonly property int bodySize: 14
readonly property int smallSize: 12
readonly property int monoSize: 13
readonly property string mono: "monospace"
readonly property int sidebarWidth: 268
readonly property int contentMax: 1100
// Layout: below the breakpoint the sidebar slides over the transcript
// instead of taking a column, and controls stay thumb-sized above it
// (a finger needs ~34-40 px, a mouse is happy with 26).
readonly property int compactBreakpoint: 880
readonly property int touchTarget: 34
readonly property int segmentHeight: 30
readonly property int headerHeight: 42
readonly property int jumpButton: 40
// Which Breeze icon stands for a tool: the card shows at a glance what
// kind of thing ran. Unknown names still get one (no glyph fallbacks for
// tools -- a missing icon just leaves the name alone).
function toolIcon(name) {
const n = String(name).toLowerCase()
if (n.indexOf("patch") >= 0 || n.indexOf("write") >= 0 || n.indexOf("edit") >= 0)
return "document-edit"
if (n.indexOf("read") >= 0 || n.indexOf("cat") >= 0)
return "text-x-generic"
if (n.indexOf("list_dir") >= 0 || n.indexOf("glob") >= 0 ||
n.indexOf("find") >= 0 || n.indexOf("dir") >= 0 || n.indexOf("file") >= 0)
return "folder-open"
if (n.indexOf("grep") >= 0 || n.indexOf("search") >= 0)
return "edit-find"
if (n.indexOf("bash") >= 0 || n.indexOf("shell") >= 0 ||
n.indexOf("command") >= 0 || n.indexOf("run") >= 0)
return "code-context"
if (n.indexOf("web") >= 0 || n.indexOf("http") >= 0 || n.indexOf("fetch") >= 0)
return "internet-services"
if (n.indexOf("todo") >= 0 || n.indexOf("plan") >= 0)
return "format-list-unordered"
if (n.indexOf("ask") >= 0 || n.indexOf("question") >= 0 || n.indexOf("notif") >= 0)
return "help-hint"
return "code-context"
}
// The tool output / diff line colors, used by ToolCard.
function lineColor(line) {
if (line.startsWith("@@"))
return accent
if (line.startsWith("+++") || line.startsWith("---"))
return textDim
if (line.startsWith("+"))
return ok
if (line.startsWith("-"))
return err
return text
}
}