drexler 0.2.8 → 0.2.10
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/App.tsx +3 -3
- package/src/ui/SynergyEvent.tsx +31 -14
- package/src/ui/TranscriptViewport.tsx +24 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.10
|
|
4
|
+
|
|
5
|
+
- Closed transcript turn blocks with right-side borders and corners so user and Drexler cards align with the input frame.
|
|
6
|
+
- Aligned the status row with the main chat chrome.
|
|
7
|
+
|
|
8
|
+
## 0.2.9
|
|
9
|
+
|
|
10
|
+
- Removed transcript card side labels like `incoming memo` and `response ledger`.
|
|
11
|
+
- Made each transcript card use a consistent top and bottom border color.
|
|
12
|
+
|
|
3
13
|
## 0.2.8
|
|
4
14
|
|
|
5
15
|
- Improved transcript readability by wrapping long user and Drexler message lines instead of truncating them.
|
package/package.json
CHANGED
package/src/ui/App.tsx
CHANGED
|
@@ -124,7 +124,7 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
124
124
|
const mode = useMemo(() => pickLayout(cols), [cols]);
|
|
125
125
|
const inputWidth = useMemo(() => Math.max(1, cols), [cols]);
|
|
126
126
|
const chromeWidth = useMemo(() => Math.max(1, cols), [cols]);
|
|
127
|
-
const statusBarWidth =
|
|
127
|
+
const statusBarWidth = inputWidth;
|
|
128
128
|
const isCompact = mode === "very-narrow";
|
|
129
129
|
const maxTranscriptRows = useMemo(
|
|
130
130
|
() => transcriptRowsForTerminalRows(rows),
|
|
@@ -303,7 +303,7 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
303
303
|
setWitticism(event.finalLine);
|
|
304
304
|
addItem("system", event.transcriptLine);
|
|
305
305
|
}
|
|
306
|
-
},
|
|
306
|
+
}, 45);
|
|
307
307
|
}, [addItem]);
|
|
308
308
|
|
|
309
309
|
const runLLM = useCallback(async (instruction?: string) => {
|
|
@@ -776,7 +776,7 @@ export function App({ conversation, config, mood = "neutral", fetchFn }: AppProp
|
|
|
776
776
|
width={inputWidth}
|
|
777
777
|
/>
|
|
778
778
|
</Box>
|
|
779
|
-
<Box
|
|
779
|
+
<Box>
|
|
780
780
|
<StatusBar
|
|
781
781
|
messageCount={msgCount}
|
|
782
782
|
witticism={witticism}
|
package/src/ui/SynergyEvent.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Box, Text } from "ink";
|
|
2
|
-
import { memo } from "react";
|
|
2
|
+
import { memo, useMemo } from "react";
|
|
3
3
|
import { displayWidth, fitDisplayText } from "./graphemes.ts";
|
|
4
4
|
import { useTheme } from "./ThemeContext.tsx";
|
|
5
5
|
|
|
@@ -142,6 +142,17 @@ function frameProgress(frame: number): number {
|
|
|
142
142
|
return Math.max(0, Math.min(1, frame / (SYNERGY_EVENT_FRAMES - 1)));
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// Cubic ease-out: fast reveal, soft settle. Feels less mechanical than linear.
|
|
146
|
+
function easeOutCubic(p: number): number {
|
|
147
|
+
const clamped = Math.max(0, Math.min(1, p));
|
|
148
|
+
const inv = 1 - clamped;
|
|
149
|
+
return 1 - inv * inv * inv;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function easedProgress(frame: number): number {
|
|
153
|
+
return easeOutCubic(frameProgress(frame));
|
|
154
|
+
}
|
|
155
|
+
|
|
145
156
|
function bar(progress: number, width: number): string {
|
|
146
157
|
const safeWidth = Math.max(1, width);
|
|
147
158
|
const filled = Math.max(0, Math.min(safeWidth, Math.round(progress * safeWidth)));
|
|
@@ -149,7 +160,7 @@ function bar(progress: number, width: number): string {
|
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
function stageAt(event: SynergyEventDefinition, frame: number): string {
|
|
152
|
-
const progress =
|
|
163
|
+
const progress = easedProgress(frame);
|
|
153
164
|
const idx = Math.min(
|
|
154
165
|
event.stages.length - 1,
|
|
155
166
|
Math.floor(progress * event.stages.length),
|
|
@@ -157,13 +168,10 @@ function stageAt(event: SynergyEventDefinition, frame: number): string {
|
|
|
157
168
|
return event.stages[idx]!;
|
|
158
169
|
}
|
|
159
170
|
|
|
160
|
-
function
|
|
161
|
-
const progress =
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
Math.ceil(progress * Math.min(event.art.length, FULL_EVENT_ART_ROWS)),
|
|
165
|
-
);
|
|
166
|
-
return event.art.slice(0, count);
|
|
171
|
+
function visibleArtCount(event: SynergyEventDefinition, frame: number): number {
|
|
172
|
+
const progress = easedProgress(frame);
|
|
173
|
+
const cap = Math.min(event.art.length, FULL_EVENT_ART_ROWS);
|
|
174
|
+
return Math.max(1, Math.ceil(progress * cap));
|
|
167
175
|
}
|
|
168
176
|
|
|
169
177
|
function kpiAt(event: SynergyEventDefinition, frame: number): string {
|
|
@@ -185,9 +193,18 @@ function SynergyEventInner({
|
|
|
185
193
|
}: Props) {
|
|
186
194
|
const t = useTheme();
|
|
187
195
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
188
|
-
const progress =
|
|
196
|
+
const progress = easedProgress(frame);
|
|
189
197
|
const done = frame >= SYNERGY_EVENT_FRAMES - 1;
|
|
190
198
|
const tiny = safeWidth < 38 || compact;
|
|
199
|
+
// Title pulse: alert-style flicker during early reveal, calm bright on done.
|
|
200
|
+
const earlyPulse = !done && frame < 6 && frame % 2 === 0;
|
|
201
|
+
const titleColor = done ? t.primaryLight : earlyPulse ? t.primary : t.warning;
|
|
202
|
+
|
|
203
|
+
const artFrames = useMemo(
|
|
204
|
+
() => event.art.slice(0, FULL_EVENT_ART_ROWS),
|
|
205
|
+
[event],
|
|
206
|
+
);
|
|
207
|
+
const revealedRows = visibleArtCount(event, frame);
|
|
191
208
|
|
|
192
209
|
if (tiny) {
|
|
193
210
|
const label = done ? event.finalLine : stageAt(event, frame);
|
|
@@ -195,7 +212,7 @@ function SynergyEventInner({
|
|
|
195
212
|
const line = `SYNC ${bar(progress, miniBarWidth)} ${label}`;
|
|
196
213
|
return (
|
|
197
214
|
<Box width={safeWidth} flexShrink={1}>
|
|
198
|
-
<Text color={
|
|
215
|
+
<Text color={titleColor} bold wrap="truncate">
|
|
199
216
|
{fitDisplayText(line, safeWidth)}
|
|
200
217
|
</Text>
|
|
201
218
|
</Box>
|
|
@@ -223,7 +240,7 @@ function SynergyEventInner({
|
|
|
223
240
|
flexShrink={1}
|
|
224
241
|
>
|
|
225
242
|
<Box>
|
|
226
|
-
<Text color={
|
|
243
|
+
<Text color={titleColor} bold>
|
|
227
244
|
SYNERGY EVENT
|
|
228
245
|
</Text>
|
|
229
246
|
<Text color={t.primaryDim}> ─ </Text>
|
|
@@ -232,8 +249,8 @@ function SynergyEventInner({
|
|
|
232
249
|
</Text>
|
|
233
250
|
</Box>
|
|
234
251
|
<Box flexDirection="column">
|
|
235
|
-
{
|
|
236
|
-
const revealed = idx <
|
|
252
|
+
{artFrames.map((line, idx) => {
|
|
253
|
+
const revealed = idx < revealedRows;
|
|
237
254
|
return (
|
|
238
255
|
<Text
|
|
239
256
|
key={`${event.id}-${idx}`}
|
|
@@ -35,12 +35,6 @@ 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: "│",
|
|
@@ -109,8 +103,12 @@ function itemRows(
|
|
|
109
103
|
cols: number,
|
|
110
104
|
): number {
|
|
111
105
|
if (compact) return 1;
|
|
112
|
-
const bodyPrefix =
|
|
113
|
-
const
|
|
106
|
+
const bodyPrefix = "│ › ";
|
|
107
|
+
const bodySuffix = " │";
|
|
108
|
+
const contentWidth = Math.max(
|
|
109
|
+
1,
|
|
110
|
+
cols - displayWidth(bodyPrefix) - displayWidth(bodySuffix),
|
|
111
|
+
);
|
|
114
112
|
return 2 + wrappedContentRows(item.content, contentWidth).length;
|
|
115
113
|
}
|
|
116
114
|
|
|
@@ -167,37 +165,42 @@ function DefaultTranscriptItem({
|
|
|
167
165
|
);
|
|
168
166
|
}
|
|
169
167
|
|
|
170
|
-
const detail = ROLE_DETAILS[item.role];
|
|
171
168
|
const headerPrefix = `╭─ ${label} `;
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
const headerRuleWidth = Math.max(0, cols - displayWidth(headerPrefix) - 1);
|
|
170
|
+
const footerWidth = Math.max(0, cols - 2);
|
|
171
|
+
const bodyPrefix = item.role === "user" ? "│ › " : "│ ";
|
|
172
|
+
const continuationPrefix = "│ ";
|
|
173
|
+
const bodySuffix = " │";
|
|
174
|
+
const contentWidth = Math.max(
|
|
175
|
+
1,
|
|
176
|
+
cols - displayWidth(bodyPrefix) - displayWidth(bodySuffix),
|
|
176
177
|
);
|
|
177
|
-
const footerWidth = Math.max(1, cols - 1);
|
|
178
|
-
const bodyPrefix = `${ROLE_MARKERS[item.role]} `;
|
|
179
|
-
const contentWidth = Math.max(1, cols - displayWidth(bodyPrefix));
|
|
180
178
|
|
|
181
179
|
return (
|
|
182
180
|
<Box flexDirection="column" width={cols} flexShrink={1}>
|
|
183
181
|
<Text color={accent} bold wrap="truncate">
|
|
184
182
|
{fitDisplayText(
|
|
185
|
-
`${headerPrefix}${rule("─", headerRuleWidth)}
|
|
183
|
+
`${headerPrefix}${rule("─", headerRuleWidth)}╮`,
|
|
186
184
|
cols,
|
|
187
185
|
)}
|
|
188
186
|
</Text>
|
|
189
187
|
{wrappedContentRows(item.content, contentWidth).map((line, index) => (
|
|
190
188
|
<Box key={index} width={cols} flexShrink={1}>
|
|
191
189
|
<Text color={accent} bold={item.role === "user"}>
|
|
192
|
-
{index === 0
|
|
190
|
+
{index === 0 ? bodyPrefix : continuationPrefix}
|
|
193
191
|
</Text>
|
|
194
192
|
<Text color={roleBodyColor(item.role, t)}>
|
|
195
|
-
{line}
|
|
193
|
+
{fitDisplayText(line, contentWidth)}
|
|
194
|
+
</Text>
|
|
195
|
+
<Text color={accent} bold={item.role === "user"}>
|
|
196
|
+
{`${" ".repeat(
|
|
197
|
+
Math.max(0, contentWidth - displayWidth(line)),
|
|
198
|
+
)}${bodySuffix}`}
|
|
196
199
|
</Text>
|
|
197
200
|
</Box>
|
|
198
201
|
))}
|
|
199
|
-
<Text color={item.role === "
|
|
200
|
-
{fitDisplayText(`╰${rule("─", footerWidth)}
|
|
202
|
+
<Text color={accent} bold={item.role === "user"} wrap="truncate">
|
|
203
|
+
{fitDisplayText(`╰${rule("─", footerWidth)}╯`, cols)}
|
|
201
204
|
</Text>
|
|
202
205
|
</Box>
|
|
203
206
|
);
|