Dismiss broken sessions, and hide ones untouched for a week
A broken session that will never be repaired kept the bar's warning dot lit forever. repair-session now takes --ignore/--unignore/--unignore-all/--ignored, recorded in ~/.claude-mode/ignored-sessions.json, and the scan hides broken transcripts older than --max-age days (default 7, 0 disables, CM_IGNORE_AGE_DAYS sets it). Hidden sessions move to a separate ignored[] list with a reason, and --all always names how many it held back. A repair clears the session's dismissal, and entries whose transcript is gone are pruned, so a session that breaks again is never silently hidden. The panel gets an Ignore button beside Repair and a collapsed "hidden (N)" section with Restore. The dot still counts broken sessions only. Bumps to 1.10.0 and fixes the widget manifest, which still said 1.8.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,12 @@ BarWidget {
|
||||
// to sit on a timer: ~60ms for 59 transcripts here, against 5s for the naive
|
||||
// version that read every byte of every one.
|
||||
property var brokenSessions: []
|
||||
// Dismissed, or broken but untouched for longer than the age limit. Kept
|
||||
// out of the dot on purpose: hiding one is how you tell the bar to stop
|
||||
// asking about it.
|
||||
property var ignoredSessions: []
|
||||
property int ignoreAgeDays: 7
|
||||
property bool rescanPending: false
|
||||
|
||||
Process {
|
||||
id: scanProc
|
||||
@@ -143,11 +149,24 @@ BarWidget {
|
||||
var d = null
|
||||
try { d = JSON.parse(String(text || "")) } catch (e) { d = null }
|
||||
root.brokenSessions = (d && d.broken) ? d.broken : []
|
||||
root.ignoredSessions = (d && d.ignored) ? d.ignored : []
|
||||
if (d && d.maxAgeDays !== undefined) root.ignoreAgeDays = Number(d.maxAgeDays)
|
||||
}
|
||||
}
|
||||
// A scan already under way was started before whatever asked for this
|
||||
// one, so its answer may predate the change - run once more after it.
|
||||
onExited: function (code) {
|
||||
if (root.rescanPending) {
|
||||
root.rescanPending = false
|
||||
Qt.callLater(root.scanSessions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scanSessions() { if (!scanProc.running) scanProc.running = true }
|
||||
function scanSessions() {
|
||||
if (scanProc.running) root.rescanPending = true
|
||||
else scanProc.running = true
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 20 * 60 * 1000
|
||||
|
||||
@@ -32,6 +32,7 @@ Panel {
|
||||
function open() {
|
||||
root.expandedMode = ""
|
||||
root.lastError = ""
|
||||
root.hiddenExpanded = false
|
||||
root.resetFlow()
|
||||
if (widget) widget.refresh()
|
||||
root.controller.show()
|
||||
@@ -183,6 +184,47 @@ Panel {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Hidden transcripts
|
||||
//
|
||||
// Ignoring only changes whether the scan counts a session - the transcript is
|
||||
// not touched and Restore is one click away - so unlike Repair it needs no
|
||||
// confirmation. Age-hidden ones are never called ignored: nobody chose that.
|
||||
readonly property var ignored: widget && widget.ignoredSessions ? widget.ignoredSessions : []
|
||||
readonly property var dismissedList: ignored.filter(function (e) { return String(e.reason) === "dismissed" })
|
||||
readonly property var staleList: ignored.filter(function (e) { return String(e.reason) !== "dismissed" })
|
||||
readonly property int ageDays: widget ? widget.ignoreAgeDays : 7
|
||||
property bool hiddenExpanded: false
|
||||
// The card clamps rather than scrolls, so a long list would be cut off at
|
||||
// the bottom edge instead of becoming reachable. The CLI has the full list.
|
||||
readonly property int hiddenCap: 4
|
||||
|
||||
function projectLabel(p) { return String(p).replace(/^-/, "").replace(/-/g, "/") }
|
||||
|
||||
function setIgnored(entry, on) {
|
||||
if (root.busy || !entry) return
|
||||
root.busy = true
|
||||
root.lastError = ""
|
||||
ignoreProc.command = root.cli(["repair-session", on ? "--ignore" : "--unignore", String(entry.sessionId)])
|
||||
ignoreProc.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: ignoreProc
|
||||
running: false
|
||||
stderr: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: {
|
||||
var msg = String(text || "").replace(/\x1b\[[0-9;]*m/g, "").trim()
|
||||
var lines = msg.split("\n").filter(function (l) { return l.trim() !== "" })
|
||||
if (lines.length > 0) root.lastError = lines[lines.length - 1].replace(/^\s*(FAIL|warn)\s*/, "").trim()
|
||||
}
|
||||
}
|
||||
onExited: function (code) {
|
||||
root.busy = false
|
||||
if (widget) widget.scanSessions()
|
||||
}
|
||||
}
|
||||
|
||||
function storeServerKey() {
|
||||
remedyProc.command = root.cli(["set-key", root.serverKeyRef, "--terminal"])
|
||||
remedyProc.running = true
|
||||
@@ -1014,13 +1056,14 @@ Panel {
|
||||
// point is to be noticed without going looking.
|
||||
Column {
|
||||
width: parent.width
|
||||
visible: root.stage === "list" && root.broken.length > 0
|
||||
visible: root.stage === "list" && (root.broken.length > 0 || root.ignored.length > 0)
|
||||
spacing: Style.space(4)
|
||||
|
||||
PanelSeparator { width: parent.width }
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.broken.length > 0
|
||||
text: root.broken.length === 1
|
||||
? "1 session cannot be resumed"
|
||||
: root.broken.length + " sessions cannot be resumed"
|
||||
@@ -1035,21 +1078,22 @@ Panel {
|
||||
model: root.stage === "list" ? root.broken : []
|
||||
|
||||
Item {
|
||||
id: brokenRow
|
||||
required property var modelData
|
||||
width: column.width
|
||||
height: Style.space(26)
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: repairBtn.left
|
||||
anchors.right: ignoreBtn.left
|
||||
anchors.rightMargin: Style.space(6)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: String(parent.parent.modelData.sessionId).substring(0, 8)
|
||||
+ " · " + String(parent.parent.modelData.project).replace(/^-/, "").replace(/-/g, "/")
|
||||
text: String(brokenRow.modelData.sessionId).substring(0, 8)
|
||||
+ " · " + root.projectLabel(brokenRow.modelData.project)
|
||||
color: Color.popups.text
|
||||
elide: Text.ElideMiddle
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
@@ -1058,7 +1102,7 @@ Panel {
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: parent.parent.modelData.dropLines + " turn lines after the last good message"
|
||||
text: brokenRow.modelData.dropLines + " turn lines after the last good message"
|
||||
color: Color.muted
|
||||
elide: Text.ElideRight
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
@@ -1066,15 +1110,156 @@ Panel {
|
||||
}
|
||||
}
|
||||
|
||||
PillButton {
|
||||
id: ignoreBtn
|
||||
anchors.right: repairBtn.left
|
||||
anchors.rightMargin: Style.space(5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
label: "Ignore"
|
||||
onTriggered: root.setIgnored(brokenRow.modelData, true)
|
||||
}
|
||||
|
||||
PillButton {
|
||||
id: repairBtn
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
label: "Repair"
|
||||
onTriggered: root.askRepair(parent.modelData)
|
||||
onTriggered: root.askRepair(brokenRow.modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Hidden: dismissed here, or broken but untouched for longer than
|
||||
// the age limit. Collapsed, because the point of hiding them was that
|
||||
// they stop taking up attention - but never gone without a trace.
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Style.space(20)
|
||||
visible: root.ignored.length > 0
|
||||
|
||||
Text {
|
||||
id: hiddenLabel
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "hidden (" + root.ignored.length + ")"
|
||||
color: hiddenArea.containsMouse ? Color.popups.text : Color.muted
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(10)
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.left: hiddenLabel.right
|
||||
anchors.leftMargin: Style.space(4)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: String.fromCodePoint(root.hiddenExpanded ? 0xF0140 : 0xF0142)
|
||||
color: hiddenLabel.color
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(11)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hiddenArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.hiddenExpanded = !root.hiddenExpanded
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
visible: root.hiddenExpanded && root.ignored.length > 0
|
||||
spacing: Style.space(3)
|
||||
|
||||
Text {
|
||||
visible: root.dismissedList.length > 0
|
||||
text: "Ignored (" + root.dismissedList.length + ")"
|
||||
color: Color.popups.text
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(10)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.hiddenExpanded ? root.dismissedList.slice(0, root.hiddenCap) : []
|
||||
|
||||
Item {
|
||||
id: dismissedRow
|
||||
required property var modelData
|
||||
width: column.width
|
||||
height: Style.space(26)
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.right: restoreBtn.left
|
||||
anchors.rightMargin: Style.space(6)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: String(dismissedRow.modelData.sessionId).substring(0, 8)
|
||||
+ " · " + root.projectLabel(dismissedRow.modelData.project)
|
||||
color: Color.muted
|
||||
elide: Text.ElideMiddle
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(10)
|
||||
}
|
||||
|
||||
PillButton {
|
||||
id: restoreBtn
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
label: "Restore"
|
||||
onTriggered: root.setIgnored(dismissedRow.modelData, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.dismissedList.length > root.hiddenCap
|
||||
text: "… and " + (root.dismissedList.length - root.hiddenCap)
|
||||
+ " more · claude-mode repair-session --ignored"
|
||||
color: Color.muted
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(9)
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.staleList.length > 0
|
||||
text: "Older than " + root.ageDays + " days (" + root.staleList.length + ")"
|
||||
color: Color.popups.text
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(10)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.hiddenExpanded ? root.staleList.slice(0, root.hiddenCap) : []
|
||||
|
||||
Text {
|
||||
required property var modelData
|
||||
width: column.width
|
||||
text: String(modelData.sessionId).substring(0, 8)
|
||||
+ " · " + Math.floor((Date.now() / 1000 - Number(modelData.mtime)) / 86400) + "d"
|
||||
+ " · " + root.projectLabel(modelData.project)
|
||||
color: Color.muted
|
||||
elide: Text.ElideMiddle
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(10)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.staleList.length > 0
|
||||
text: (root.staleList.length > root.hiddenCap
|
||||
? "… and " + (root.staleList.length - root.hiddenCap) + " more. " : "")
|
||||
+ "Not counted because nothing has touched them since. "
|
||||
+ "claude-mode repair-session --all --max-age 0 lists them."
|
||||
color: Color.muted
|
||||
opacity: 0.85
|
||||
wrapMode: Text.WordWrap
|
||||
font.family: root.bar ? root.bar.fontFamily : Style.font.family
|
||||
font.pixelSize: Style.space(9)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Confirming one repair.
|
||||
@@ -1193,7 +1378,7 @@ Panel {
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.busy
|
||||
? "switching…"
|
||||
? (ignoreProc.running ? "updating…" : (repairProc.running ? "repairing…" : "switching…"))
|
||||
: (root.known && root.stage === "list" ? "Restart claude to pick this up." : "")
|
||||
visible: text !== ""
|
||||
color: Color.muted
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"id": "smoido.claude-mode",
|
||||
"name": "Claude Mode",
|
||||
"version": "1.8.0",
|
||||
"version": "1.10.0",
|
||||
"author": "smoido",
|
||||
"description": "Which provider Claude Code is pointed at, and a one-click switch between them",
|
||||
"kinds": ["bar-widget"],
|
||||
|
||||
Reference in New Issue
Block a user