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 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
  {
2
2
  "name": "drexler",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "CLI chat with Drexler, a corporate-executive AI persona built on OpenRouter Gemma 4 31B.",
5
5
  "license": "MIT",
6
6
  "author": "showOS",
@@ -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 lineCount(input: string): number {
51
- if (input.length === 0) return 1;
52
- return input.split("\n").length;
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(item: TranscriptViewportItem, compact: boolean): number {
100
+ function itemRows(
101
+ item: TranscriptViewportItem,
102
+ compact: boolean,
103
+ cols: number,
104
+ ): number {
56
105
  if (compact) return 1;
57
- return 2 + lineCount(item.content);
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 headerSuffix = ` ${detail}`;
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)}${headerSuffix}`,
174
+ `${headerPrefix}${rule("─", headerRuleWidth)}`,
129
175
  cols,
130
176
  )}
131
177
  </Text>
132
- {item.content.split("\n").map((line, index) => (
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)} wrap="truncate">
138
- {fitDisplayText(line, contentWidth)}
183
+ <Text color={roleBodyColor(item.role, t)}>
184
+ {line}
139
185
  </Text>
140
186
  </Box>
141
187
  ))}
142
- <Text color={item.role === "assistant" ? t.primaryDim : t.dim} wrap="truncate">
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