@hayasaka7/haya-pet 0.3.11 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/apps/companion/src/renderer/session-bubbles.js +4 -2
- package/apps/companion/test/session-bubbles.test.mjs +28 -0
- package/package.json +1 -1
- package/packages/session-core/src/bubble-view.js +7 -2
- package/packages/session-core/src/summaries.js +24 -8
- package/packages/session-core/test/bubble-view.test.mjs +11 -0
- package/packages/session-core/test/summaries.test.mjs +22 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ All notable changes to HAYA Pet are documented here. This project adheres to
|
|
|
7
7
|
> 0.2.0 npm publish; they are listed under 0.2.1, which is the first version that
|
|
8
8
|
> ships them.
|
|
9
9
|
|
|
10
|
+
## [0.3.12]
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **A long status or tool-call name no longer stretches the session bubble.** The
|
|
14
|
+
activity line is `white-space: nowrap`, and the bubble sizes to its content, so
|
|
15
|
+
a long summary (e.g. a tool call like `Read packages/session-core/src/...`)
|
|
16
|
+
dragged the bubble out to its max width before the CSS ellipsis could engage.
|
|
17
|
+
The status/activity text is now length-capped in the view model (`summaryLabel`,
|
|
18
|
+
32 chars + `...`) exactly like the project name (`projectLabel`), so it can't
|
|
19
|
+
widen the bubble; the full summary stays reachable on hover and in the expanded
|
|
20
|
+
task-talk popup.
|
|
21
|
+
|
|
10
22
|
## [0.3.11]
|
|
11
23
|
|
|
12
24
|
### Fixed
|
|
@@ -253,8 +253,10 @@ function applyBubble(el, bubble) {
|
|
|
253
253
|
project.textContent = bubble.projectLabel ?? bubble.projectName;
|
|
254
254
|
// Hover reveals the full, untruncated "Client · Project".
|
|
255
255
|
title.title = bubble.projectName ? `${bubble.clientName} · ${bubble.projectName}` : bubble.clientName;
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
// Compact label keeps a long status/tool name from stretching the bubble; the
|
|
257
|
+
// full summary stays reachable on hover (like the project name above).
|
|
258
|
+
activity.textContent = bubble.summaryLabel ?? bubble.summary;
|
|
259
|
+
activity.title = `${bubble.summary ?? bubble.statusLabel} · ${bubble.elapsedLabel}`;
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
// Picks the kind that should win the collapsed-folder dot: a failure or a
|
|
@@ -198,6 +198,34 @@ test("shows the compact project label and keeps the full name as a tooltip", ()
|
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
+
test("shows the compact summary label and keeps the full summary as a tooltip", () => {
|
|
202
|
+
const restoreDocument = installFakeDocument();
|
|
203
|
+
try {
|
|
204
|
+
const container = new FakeElement("div");
|
|
205
|
+
const listView = createBubbleList(container, { onRender: createHostOnRender(container) });
|
|
206
|
+
|
|
207
|
+
listView.render([{
|
|
208
|
+
sessionId: "s1",
|
|
209
|
+
statusKind: "working",
|
|
210
|
+
statusLabel: "Running tools",
|
|
211
|
+
clientName: "Claude Code",
|
|
212
|
+
projectName: "haya-pet",
|
|
213
|
+
projectLabel: "haya-pet",
|
|
214
|
+
summary: "Read packages/session-core/src/summaries.js",
|
|
215
|
+
summaryLabel: "Read packages/session-core/src/s...",
|
|
216
|
+
elapsedLabel: "1s"
|
|
217
|
+
}]);
|
|
218
|
+
|
|
219
|
+
const activity = findActivity(findBubble(container, "s1"));
|
|
220
|
+
// The bubble renders the capped label so a long tool name can't widen it...
|
|
221
|
+
assert.equal(activity.textContent, "Read packages/session-core/src/s...");
|
|
222
|
+
// ...while the full summary stays reachable on hover.
|
|
223
|
+
assert.equal(activity.title, "Read packages/session-core/src/summaries.js · 1s");
|
|
224
|
+
} finally {
|
|
225
|
+
restoreDocument();
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
201
229
|
test("clears everything when no sessions remain", () => {
|
|
202
230
|
const restoreDocument = installFakeDocument();
|
|
203
231
|
try {
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mapAiStateToPetAction } from "../../pet-core/src/atlas.js";
|
|
2
|
-
import { buildSessionSummary, buildStatusLabel, formatElapsed, truncateProjectName } from "./summaries.js";
|
|
2
|
+
import { buildSessionSummary, buildStatusLabel, formatElapsed, truncateProjectName, truncateSummary } from "./summaries.js";
|
|
3
3
|
|
|
4
4
|
// Collapses the full AI-state vocabulary into the four progress kinds the
|
|
5
5
|
// bubble panel renders: a spinning "working" circle, a "done" check mark (held
|
|
@@ -30,6 +30,7 @@ export function resolveBubbleStatusKind(state) {
|
|
|
30
30
|
|
|
31
31
|
export function buildBubbleView(session, now = Date.now(), options = {}) {
|
|
32
32
|
const elapsedMs = Math.max(0, numeric(now) - numeric(session.startedAt));
|
|
33
|
+
const summary = buildSessionSummary(session);
|
|
33
34
|
|
|
34
35
|
return {
|
|
35
36
|
sessionId: session.sessionId,
|
|
@@ -40,7 +41,11 @@ export function buildBubbleView(session, now = Date.now(), options = {}) {
|
|
|
40
41
|
state: session.state,
|
|
41
42
|
statusLabel: buildStatusLabel(session.state),
|
|
42
43
|
statusKind: resolveBubbleStatusKind(session.state),
|
|
43
|
-
summary
|
|
44
|
+
// Full summary stays for the detail popup + hover tooltip; summaryLabel is
|
|
45
|
+
// the compact form the bubble renders so a long status/tool name can't
|
|
46
|
+
// stretch the bubble (mirrors projectName / projectLabel).
|
|
47
|
+
summary,
|
|
48
|
+
summaryLabel: truncateSummary(summary),
|
|
44
49
|
petAction: safePetAction(session.state),
|
|
45
50
|
elapsedMs,
|
|
46
51
|
elapsedLabel: formatElapsed(elapsedMs),
|
|
@@ -26,18 +26,34 @@ export function buildSessionSummary(session) {
|
|
|
26
26
|
return buildStatusLabel(session?.state);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Ellipsis truncation shared by the bubble's title and activity lines: text up
|
|
30
|
+
// to `maxLength` characters shows whole; longer text is cut to `maxLength` and
|
|
31
|
+
// marked with a trailing "...". Non-strings coerce to "". The full text is kept
|
|
32
|
+
// elsewhere on the view model for a hover tooltip.
|
|
33
|
+
function truncateWithEllipsis(text, maxLength) {
|
|
34
|
+
const value = typeof text === "string" ? text : "";
|
|
35
|
+
if (value.length <= maxLength) {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
return `${value.slice(0, maxLength)}...`;
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
// Compacts a project name for the session bubble title, which sits beside the
|
|
30
|
-
// (always-full) client name in a narrow overlay.
|
|
31
|
-
// characters show whole; longer ones are cut to `maxLength` and marked with an
|
|
32
|
-
// ellipsis. The full name is kept elsewhere on the view model for a tooltip.
|
|
42
|
+
// (always-full) client name in a narrow overlay.
|
|
33
43
|
const DEFAULT_PROJECT_NAME_LENGTH = 10;
|
|
34
44
|
|
|
35
45
|
export function truncateProjectName(name, maxLength = DEFAULT_PROJECT_NAME_LENGTH) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
return truncateWithEllipsis(name, maxLength);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Compacts the activity/status line the same way, so a long status or tool-call
|
|
50
|
+
// summary can't stretch the bubble wider than its (already capped) title. The
|
|
51
|
+
// budget comfortably clears the longest built-in status label so those always
|
|
52
|
+
// show whole; only genuinely long custom summaries get the ellipsis.
|
|
53
|
+
const DEFAULT_SUMMARY_LENGTH = 32;
|
|
54
|
+
|
|
55
|
+
export function truncateSummary(text, maxLength = DEFAULT_SUMMARY_LENGTH) {
|
|
56
|
+
return truncateWithEllipsis(text, maxLength);
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
export function formatElapsed(ms) {
|
|
@@ -25,6 +25,8 @@ test("builds a bubble view model with label, summary, action, and elapsed", () =
|
|
|
25
25
|
assert.equal(view.state, "waiting_approval");
|
|
26
26
|
assert.equal(view.statusLabel, "Waiting for approval");
|
|
27
27
|
assert.equal(view.summary, "waiting for command approval");
|
|
28
|
+
// Fits the 32-char budget, so the compact label matches the full summary.
|
|
29
|
+
assert.equal(view.summaryLabel, "waiting for command approval");
|
|
28
30
|
assert.equal(view.petAction, "waiting");
|
|
29
31
|
assert.equal(view.elapsedMs, 64_000);
|
|
30
32
|
assert.equal(view.elapsedLabel, "1m 4s");
|
|
@@ -72,6 +74,15 @@ test("keeps a short project name whole in projectLabel", () => {
|
|
|
72
74
|
assert.equal(view.projectLabel, "api");
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
test("compacts a long status/tool summary into summaryLabel, keeping the full summary", () => {
|
|
78
|
+
const longSummary = "Read packages/session-core/src/summaries.js";
|
|
79
|
+
const view = buildBubbleView({ ...baseSession, state: "running_tool", summary: longSummary }, 6_000);
|
|
80
|
+
// Full text is preserved (for the detail popup + hover tooltip)...
|
|
81
|
+
assert.equal(view.summary, longSummary);
|
|
82
|
+
// ...while the bubble renders the capped form so it can't stretch the width.
|
|
83
|
+
assert.equal(view.summaryLabel, "Read packages/session-core/src/s...");
|
|
84
|
+
});
|
|
85
|
+
|
|
75
86
|
test("marks the selected/pinned session", () => {
|
|
76
87
|
const views = buildBubbleViews([baseSession], 6_000, { selectedSessionId: "sess_a" });
|
|
77
88
|
assert.equal(views[0].selected, true);
|
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
buildStatusLabel,
|
|
5
5
|
buildSessionSummary,
|
|
6
6
|
formatElapsed,
|
|
7
|
-
truncateProjectName
|
|
7
|
+
truncateProjectName,
|
|
8
|
+
truncateSummary
|
|
8
9
|
} from "../src/summaries.js";
|
|
9
10
|
|
|
10
11
|
test("maps every normalized state to a human status label", () => {
|
|
@@ -59,3 +60,23 @@ test("coerces missing or non-string project names to an empty string", () => {
|
|
|
59
60
|
assert.equal(truncateProjectName(null), "");
|
|
60
61
|
assert.equal(truncateProjectName(123), "");
|
|
61
62
|
});
|
|
63
|
+
|
|
64
|
+
test("keeps built-in status labels whole in the summary budget", () => {
|
|
65
|
+
// The longest built-in label ("Waiting for approval", 20 chars) and a
|
|
66
|
+
// slightly longer custom message must both fit without an ellipsis.
|
|
67
|
+
assert.equal(truncateSummary("Waiting for approval"), "Waiting for approval");
|
|
68
|
+
assert.equal(truncateSummary("waiting for command approval"), "waiting for command approval");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("truncates a long status or tool-call summary to 32 characters plus an ellipsis", () => {
|
|
72
|
+
assert.equal(
|
|
73
|
+
truncateSummary("Read packages/session-core/src/summaries.js"),
|
|
74
|
+
"Read packages/session-core/src/s..."
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("honours a custom summary max length and coerces non-strings", () => {
|
|
79
|
+
assert.equal(truncateSummary("running tools", 4), "runn...");
|
|
80
|
+
assert.equal(truncateSummary(undefined), "");
|
|
81
|
+
assert.equal(truncateSummary(123), "");
|
|
82
|
+
});
|