+
+
{{ formatAge(run.msSinceLastEvent) }}
+
{{ t('backstage.detail.lastHeard') }}
-
-
{{ t('backstage.detail.audience') }}
-
-
- {{ t('backstage.detail.noOneListening') }}
-
- {{ t('backstage.detail.peopleListening', { n: run.subscriberCount }) }}
-
+
+
{{ run.subscriberCount }}
+
{{ t('backstage.detail.audience') }}
-
-
{{ t('backstage.detail.session') }}
-
#{{ shortId(run.conversationId) }}
+
+
#{{ shortId(run.conversationId) }}
+
{{ t('backstage.detail.session') }}
-
+
@@ -321,7 +330,7 @@ html.dark .focus-backdrop {
color: var(--mc-text-tertiary);
}
-/* ===== The one sentence ===== */
+/* ===== The one sentence + tool chip ===== */
.focus-saying {
font-size: 22px;
line-height: 1.45;
@@ -329,7 +338,37 @@ html.dark .focus-backdrop {
letter-spacing: -0.015em;
color: var(--mc-text-primary);
text-align: center;
- margin: 0 0 24px;
+ margin: 0 0 12px;
+}
+
+.focus-tool-row {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 24px;
+}
+
+.focus-tool-chip {
+ display: inline-flex;
+ align-items: center;
+ padding: 4px 12px;
+ border-radius: 8px;
+ background: var(--mc-accent-soft);
+ color: var(--mc-accent);
+ font-family: ui-monospace, SFMono-Regular, 'JetBrains Mono', Menlo, Consolas, monospace;
+ font-size: 13px;
+ font-weight: 500;
+ letter-spacing: 0.01em;
+ border: 1px solid transparent;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+html.dark .focus-tool-chip {
+ background: rgba(92, 166, 157, 0.14);
+ color: hsl(170, 35%, 72%);
+ border-color: rgba(92, 166, 157, 0.2);
}
.focus-callout {
@@ -467,49 +506,75 @@ html.dark .time-ring-warn::before {
font-weight: 500;
}
-/* ===== Secondary grid ===== */
-.focus-grid {
- margin: 0 0 8px;
- border-top: 1px solid var(--mc-border-light);
+/* ===== Secondary metadata as bento KPI tiles =====
+ * Three equal tiles: each is one glance — big value (mono tabular-nums),
+ * small caption beneath. Replaces the dt/dd list form, which buried the
+ * value behind a label and made the section read like a settings panel
+ * instead of a dashboard.
+ */
+.focus-tiles {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 8px;
+ margin-bottom: 8px;
}
-.focus-row {
+.focus-tile {
+ padding: 14px 10px 12px;
+ background: var(--mc-bg-muted);
+ border: 1px solid var(--mc-border-light);
+ border-radius: 12px;
display: flex;
- justify-content: space-between;
- align-items: baseline;
- padding: 13px 0;
- border-bottom: 1px solid var(--mc-border-light);
- margin: 0;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ min-width: 0;
}
-.focus-row dt {
- font-size: 12px;
- color: var(--mc-text-tertiary);
- margin: 0;
- letter-spacing: 0.01em;
+html.dark .focus-tile {
+ background: rgba(255, 255, 255, 0.04);
}
-.focus-row dd {
- font-size: 13.5px;
+.focus-tile-value {
+ font-size: 20px;
+ font-weight: 600;
+ letter-spacing: -0.02em;
color: var(--mc-text-primary);
- margin: 0;
- text-align: right;
font-variant-numeric: tabular-nums;
+ line-height: 1.1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 100%;
}
-.focus-warn {
- color: hsl(265, 50%, 50%);
-}
-
-html.dark .focus-warn {
- color: hsl(265, 55%, 70%);
-}
-
-.focus-id {
+.focus-tile-id {
font-family: ui-monospace, SFMono-Regular, 'JetBrains Mono', Menlo, Consolas, monospace;
- font-size: 12px;
+ font-size: 14px;
letter-spacing: 0.02em;
+ color: var(--mc-text-secondary);
+ font-weight: 500;
+ cursor: default;
+}
+
+.focus-tile-warn {
+ color: hsl(265, 50%, 52%);
+}
+
+html.dark .focus-tile-warn {
+ color: hsl(265, 55%, 72%);
+}
+
+.focus-tile-label {
+ font-size: 10.5px;
color: var(--mc-text-tertiary);
+ letter-spacing: 0.06em;
+ margin-top: 8px;
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 100%;
}
/* ===== Subagents ===== */
diff --git a/mateclaw-ui/src/composables/useBackstageAgent.ts b/mateclaw-ui/src/composables/useBackstageAgent.ts
index ed1fe25c..6a6a5f69 100644
--- a/mateclaw-ui/src/composables/useBackstageAgent.ts
+++ b/mateclaw-ui/src/composables/useBackstageAgent.ts
@@ -61,12 +61,20 @@ export function useBackstageAgent() {
return t('backstage.dotTitle.healthy')
}
+ /**
+ * One-sentence status. Deliberately does NOT inline the tool name —
+ * the tool deserves its own monospace chip in the UI, and embedding
+ * it inside a sentence both gets ellipsis-truncated on narrow cards
+ * AND duplicates information when the chip is also rendered.
+ * stuckCallout (used in the focus panel) is the one place that still
+ * mentions the tool name, because there it lives in a long-form alert.
+ */
function humanSentence(run: BackstageRunCard): string {
- if (run.stuckReason === 'tool_silent') return t('backstage.saying.toolSilent', { tool: run.runningToolName || t('backstage.aTool') })
+ if (run.stuckReason === 'tool_silent') return t('backstage.saying.toolSilentBare')
if (run.stuckReason === 'idle_silent') return t('backstage.saying.idleSilent')
if (run.stuckReason === 'hard_cap') return t('backstage.saying.hardCap')
if (run.currentPhase === 'awaiting_approval') return t('backstage.saying.awaitingApproval')
- if (run.runningToolName) return t('backstage.saying.usingTool', { tool: run.runningToolName })
+ if (run.runningToolName) return t('backstage.saying.usingToolBare')
if (run.currentPhase === 'executing_tool') return t('backstage.saying.usingSomething')
if (run.currentPhase === 'summarizing') return t('backstage.saying.wrappingUp')
if (run.currentPhase === 'planning') return t('backstage.saying.planning')
diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts
index fd685a52..633af86d 100644
--- a/mateclaw-ui/src/i18n/locales/en-US.ts
+++ b/mateclaw-ui/src/i18n/locales/en-US.ts
@@ -346,10 +346,10 @@ export default {
replying: 'Writing the reply...',
planning: 'Planning the next steps...',
summarizing: 'Wrapping up...',
- usingTool: 'Using {tool}...',
+ usingToolBare: 'Running',
usingSomething: 'Using a tool...',
awaitingApproval: 'Waiting for your approval.',
- toolSilent: 'Stuck on {tool}, no progress.',
+ toolSilentBare: 'Stuck on a tool, no progress.',
idleSilent: 'Quiet for too long.',
hardCap: 'Has been at this far too long.',
},
diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts
index daf87e14..1f302404 100644
--- a/mateclaw-ui/src/i18n/locales/zh-CN.ts
+++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts
@@ -1526,10 +1526,10 @@ export default {
replying: '正在写回复...',
planning: '正在规划下一步...',
summarizing: '正在收尾...',
- usingTool: '正在使用 {tool}...',
+ usingToolBare: '正在使用',
usingSomething: '正在使用某个工具...',
awaitingApproval: '在等你审批。',
- toolSilent: '卡在 {tool},没有进展。',
+ toolSilentBare: '卡在工具上,没有进展。',
idleSilent: '安静太久了。',
hardCap: '已经跑了太久。',
},
diff --git a/mateclaw-ui/src/views/Backstage.vue b/mateclaw-ui/src/views/Backstage.vue
index 5fb382c2..3a7f96a1 100644
--- a/mateclaw-ui/src/views/Backstage.vue
+++ b/mateclaw-ui/src/views/Backstage.vue
@@ -99,8 +99,14 @@
-
-
{{ humanSentence(run) }}
+
+
+ {{ humanSentence(run) }}
+
+ {{ run.runningToolName }}
+
+
@@ -257,14 +263,37 @@ const showFilterRow = computed(() => {
return s.stuck > 0 || s.orphan > 0
})
+/**
+ * Triage order: stuck first (you have to do something), then orphan (a run
+ * that nobody is watching), then working (healthy & boring). Within each
+ * tier, the older run wins — a 5-minute stuck run is more urgent than one
+ * stuck for 8 seconds.
+ *
+ * Without this, runs are listed in whatever order the snapshot returned
+ * them, and the one card you actually need to look at can be 4 rows down.
+ */
+function tierOf(r: BackstageRunCard): number {
+ if (r.stuckReason) return 0
+ if (r.orphan) return 1
+ return 2
+}
+
const visibleRuns = computed(() => {
const runs = snapshot.value?.runs ?? []
- switch (activeFilter.value) {
- case 'working': return runs.filter(isWorking)
- case 'attention': return runs.filter(isAttention)
- case 'quiet': return runs.filter(isQuiet)
- default: return runs
- }
+ const filtered = (() => {
+ switch (activeFilter.value) {
+ case 'working': return runs.filter(isWorking)
+ case 'attention': return runs.filter(isAttention)
+ case 'quiet': return runs.filter(isQuiet)
+ default: return runs
+ }
+ })()
+ return [...filtered].sort((a, b) => {
+ const ta = tierOf(a)
+ const tb = tierOf(b)
+ if (ta !== tb) return ta - tb
+ return b.ageMs - a.ageMs
+ })
})
// If the filter the user picked no longer matches any rows (e.g., the stuck
@@ -867,13 +896,20 @@ html.dark .agent-avatar {
50% { opacity: 0.7; transform: scale(0.85); }
}
-/* ===== Saying ===== */
+/* ===== Saying + tool chip ===== */
+.agent-saying-row {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 16px;
+ min-height: 22px;
+}
+
.agent-saying {
font-size: 14.5px;
line-height: 1.55;
color: var(--mc-text-secondary);
- min-height: 22px;
- margin-bottom: 16px;
letter-spacing: -0.005em;
}
@@ -886,6 +922,48 @@ html.dark .agent-card.is-stuck .agent-saying {
color: hsl(28, 80%, 76%);
}
+/*
+ * Tool chip — monospace, accent-soft tinted. The tool name was previously
+ * baked into the sentence and got truncated on narrow cards; promoting it
+ * to its own chip makes it ellipsis-resistant and visually weighty. It also
+ * stops competing with the sentence for the same line of attention.
+ */
+.tool-chip {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 9px;
+ border-radius: 6px;
+ background: var(--mc-accent-soft);
+ color: var(--mc-accent);
+ font-family: ui-monospace, SFMono-Regular, 'JetBrains Mono', Menlo, Consolas, monospace;
+ font-size: 11.5px;
+ font-weight: 500;
+ letter-spacing: 0.01em;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ border: 1px solid transparent;
+}
+
+html.dark .tool-chip {
+ background: rgba(92, 166, 157, 0.14);
+ color: hsl(170, 35%, 70%);
+ border-color: rgba(92, 166, 157, 0.18);
+}
+
+.agent-card.is-stuck .tool-chip {
+ background: hsla(20, 100%, 90%, 0.65);
+ color: hsl(20, 75%, 38%);
+ border-color: hsla(20, 80%, 55%, 0.25);
+}
+
+html.dark .agent-card.is-stuck .tool-chip {
+ background: hsla(20, 60%, 30%, 0.4);
+ color: hsl(28, 80%, 78%);
+ border-color: hsla(20, 70%, 55%, 0.3);
+}
+
/* ===== Meta + bar ===== */
.agent-meta-row {
display: flex;