drexler 0.2.7 → 0.2.9
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 +10 -0
- package/package.json +1 -1
- package/src/ui/TranscriptViewport.tsx +71 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.9
|
|
4
|
+
|
|
5
|
+
- Removed transcript card side labels like `incoming memo` and `response ledger`.
|
|
6
|
+
- Made each transcript card use a consistent top and bottom border color.
|
|
7
|
+
|
|
8
|
+
## 0.2.8
|
|
9
|
+
|
|
10
|
+
- Improved transcript readability by wrapping long user and Drexler message lines instead of truncating them.
|
|
11
|
+
- Kept wrapped continuation rows visually aligned inside the existing turn blocks.
|
|
12
|
+
|
|
3
13
|
## 0.2.7
|
|
4
14
|
|
|
5
15
|
- Stabilized `/synergy` animation layout with fixed row budgeting, a capped centered event panel, and completion only at 100%.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box, Text } from "ink";
|
|
2
2
|
import { Children, memo, useMemo, type ReactNode } from "react";
|
|
3
|
-
import { displayWidth, fitDisplayText } from "./graphemes.ts";
|
|
3
|
+
import { displayWidth, fitDisplayText, splitGraphemes } from "./graphemes.ts";
|
|
4
4
|
import { useTheme } from "./ThemeContext.tsx";
|
|
5
5
|
|
|
6
6
|
export interface TranscriptViewportItem {
|
|
@@ -35,26 +35,77 @@ const ROLE_LABELS: Record<TranscriptViewportItem["role"], string> = {
|
|
|
35
35
|
system: "SYSTEM",
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const ROLE_DETAILS: Record<TranscriptViewportItem["role"], string> = {
|
|
39
|
-
user: "incoming memo",
|
|
40
|
-
assistant: "response ledger",
|
|
41
|
-
system: "system notice",
|
|
42
|
-
};
|
|
43
|
-
|
|
44
38
|
const ROLE_MARKERS: Record<TranscriptViewportItem["role"], string> = {
|
|
45
39
|
user: "›",
|
|
46
40
|
assistant: "│",
|
|
47
41
|
system: "!",
|
|
48
42
|
};
|
|
49
43
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
function wrapDisplayLine(input: string, maxWidth: number): string[] {
|
|
45
|
+
const width = Math.max(1, maxWidth);
|
|
46
|
+
if (input.length === 0) return [""];
|
|
47
|
+
if (displayWidth(input) <= width) return [input];
|
|
48
|
+
|
|
49
|
+
const parts = splitGraphemes(input);
|
|
50
|
+
const rows: string[] = [];
|
|
51
|
+
let current = "";
|
|
52
|
+
let lastBreakAt = -1;
|
|
53
|
+
|
|
54
|
+
for (const part of parts) {
|
|
55
|
+
const next = `${current}${part}`;
|
|
56
|
+
if (displayWidth(next) <= width) {
|
|
57
|
+
current = next;
|
|
58
|
+
if (/\s/u.test(part)) lastBreakAt = current.length;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (lastBreakAt > 0) {
|
|
63
|
+
const head = current.slice(0, lastBreakAt).trimEnd();
|
|
64
|
+
const tail = current.slice(lastBreakAt).trimStart();
|
|
65
|
+
rows.push(head);
|
|
66
|
+
current = `${tail}${part}`;
|
|
67
|
+
} else {
|
|
68
|
+
if (current.length > 0) rows.push(current);
|
|
69
|
+
current = part;
|
|
70
|
+
}
|
|
71
|
+
lastBreakAt = /\s/u.test(part) ? current.length : -1;
|
|
72
|
+
|
|
73
|
+
while (displayWidth(current) > width) {
|
|
74
|
+
let clipped = "";
|
|
75
|
+
for (const grapheme of splitGraphemes(current)) {
|
|
76
|
+
if (displayWidth(`${clipped}${grapheme}`) > width) break;
|
|
77
|
+
clipped += grapheme;
|
|
78
|
+
}
|
|
79
|
+
if (clipped.length === 0) {
|
|
80
|
+
const [first = ""] = splitGraphemes(current);
|
|
81
|
+
rows.push(fitDisplayText(first, width));
|
|
82
|
+
current = current.slice(first.length);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
rows.push(clipped);
|
|
86
|
+
current = current.slice(clipped.length);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
rows.push(current.trimEnd());
|
|
91
|
+
return rows.filter((row, index) => row.length > 0 || index === 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function wrappedContentRows(content: string, width: number): string[] {
|
|
95
|
+
return content
|
|
96
|
+
.split("\n")
|
|
97
|
+
.flatMap((line) => wrapDisplayLine(line, width));
|
|
53
98
|
}
|
|
54
99
|
|
|
55
|
-
function itemRows(
|
|
100
|
+
function itemRows(
|
|
101
|
+
item: TranscriptViewportItem,
|
|
102
|
+
compact: boolean,
|
|
103
|
+
cols: number,
|
|
104
|
+
): number {
|
|
56
105
|
if (compact) return 1;
|
|
57
|
-
|
|
106
|
+
const bodyPrefix = `${ROLE_MARKERS[item.role]} `;
|
|
107
|
+
const contentWidth = Math.max(1, cols - displayWidth(bodyPrefix));
|
|
108
|
+
return 2 + wrappedContentRows(item.content, contentWidth).length;
|
|
58
109
|
}
|
|
59
110
|
|
|
60
111
|
function roleAccentColor(
|
|
@@ -110,13 +161,8 @@ function DefaultTranscriptItem({
|
|
|
110
161
|
);
|
|
111
162
|
}
|
|
112
163
|
|
|
113
|
-
const detail = ROLE_DETAILS[item.role];
|
|
114
164
|
const headerPrefix = `╭─ ${label} `;
|
|
115
|
-
const
|
|
116
|
-
const headerRuleWidth = Math.max(
|
|
117
|
-
0,
|
|
118
|
-
cols - displayWidth(headerPrefix) - displayWidth(headerSuffix),
|
|
119
|
-
);
|
|
165
|
+
const headerRuleWidth = Math.max(0, cols - displayWidth(headerPrefix));
|
|
120
166
|
const footerWidth = Math.max(1, cols - 1);
|
|
121
167
|
const bodyPrefix = `${ROLE_MARKERS[item.role]} `;
|
|
122
168
|
const contentWidth = Math.max(1, cols - displayWidth(bodyPrefix));
|
|
@@ -125,21 +171,21 @@ function DefaultTranscriptItem({
|
|
|
125
171
|
<Box flexDirection="column" width={cols} flexShrink={1}>
|
|
126
172
|
<Text color={accent} bold wrap="truncate">
|
|
127
173
|
{fitDisplayText(
|
|
128
|
-
`${headerPrefix}${rule("─", headerRuleWidth)}
|
|
174
|
+
`${headerPrefix}${rule("─", headerRuleWidth)}`,
|
|
129
175
|
cols,
|
|
130
176
|
)}
|
|
131
177
|
</Text>
|
|
132
|
-
{item.content
|
|
178
|
+
{wrappedContentRows(item.content, contentWidth).map((line, index) => (
|
|
133
179
|
<Box key={index} width={cols} flexShrink={1}>
|
|
134
180
|
<Text color={accent} bold={item.role === "user"}>
|
|
135
|
-
{bodyPrefix}
|
|
181
|
+
{index === 0 || item.role === "assistant" ? bodyPrefix : " "}
|
|
136
182
|
</Text>
|
|
137
|
-
<Text color={roleBodyColor(item.role, t)}
|
|
138
|
-
{
|
|
183
|
+
<Text color={roleBodyColor(item.role, t)}>
|
|
184
|
+
{line}
|
|
139
185
|
</Text>
|
|
140
186
|
</Box>
|
|
141
187
|
))}
|
|
142
|
-
<Text color={item.role === "
|
|
188
|
+
<Text color={accent} bold={item.role === "user"} wrap="truncate">
|
|
143
189
|
{fitDisplayText(`╰${rule("─", footerWidth)}`, cols)}
|
|
144
190
|
</Text>
|
|
145
191
|
</Box>
|
|
@@ -172,7 +218,7 @@ function itemsToEntries({
|
|
|
172
218
|
) : (
|
|
173
219
|
<DefaultTranscriptItem item={item} compact={compact} cols={cols} />
|
|
174
220
|
),
|
|
175
|
-
estimatedRows: itemRows(item, compact),
|
|
221
|
+
estimatedRows: itemRows(item, compact, cols),
|
|
176
222
|
}));
|
|
177
223
|
}
|
|
178
224
|
|