opencode-sesh 0.1.6 → 0.1.7
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 +24 -1
- package/README.md +6 -1
- package/bin/sesh.sh +12 -0
- package/install.sh +109 -76
- package/package.json +2 -1
- package/tui/sesh-panel.tsx +24 -16
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.7] - 2026-09-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Sidebar and home session titles are capped to a consistent width so long
|
|
15
|
+
names no longer run into the timestamp. Removed OpenTUI's `truncate` prop,
|
|
16
|
+
which middle-truncated the already-shortened titles.
|
|
17
|
+
- The TUI picker no longer re-selects a session when the list scrolls under the
|
|
18
|
+
cursor, and its preview keeps the current transcript visible while the next
|
|
19
|
+
one loads instead of blanking on every cursor move.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Sidebar rows use muted `├`/`└` tree glyphs and directory headers get a blank
|
|
24
|
+
line, so sessions are grouped clearly: a gap between directories, tight rows
|
|
25
|
+
within one.
|
|
26
|
+
- `npm install` and upgrades refresh an already-installed TUI panel
|
|
27
|
+
(`install.sh --sync-panel`), and `sesh --check` reports when the installed
|
|
28
|
+
panel is out of date. Because opencode imports plugins once at startup,
|
|
29
|
+
install now states that a full quit/reopen is required (a window reload reuses
|
|
30
|
+
the running process).
|
|
31
|
+
|
|
10
32
|
## [0.1.6] - 2026-09-13
|
|
11
33
|
|
|
12
34
|
### Changed
|
|
@@ -119,7 +141,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
119
141
|
- Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
|
|
120
142
|
CI.
|
|
121
143
|
|
|
122
|
-
[Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.
|
|
144
|
+
[Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.7...HEAD
|
|
145
|
+
[0.1.7]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.6...v0.1.7
|
|
123
146
|
[0.1.6]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.5...v0.1.6
|
|
124
147
|
[0.1.5]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.4...v0.1.5
|
|
125
148
|
[0.1.4]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.1.3...v0.1.4
|
package/README.md
CHANGED
|
@@ -68,7 +68,12 @@ file it would overwrite is backed up first.
|
|
|
68
68
|
The npm package ships the same scripts and installer; the runtime tools below
|
|
69
69
|
are still required.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
Fully quit opencode and reopen it to load the panel — opencode imports plugins
|
|
72
|
+
once at startup, so reloading a window reuses the running process. Upgrades
|
|
73
|
+
refresh an already-installed panel automatically (the npm `postinstall` syncs
|
|
74
|
+
it), and `sesh --check` reports when the installed panel is out of date.
|
|
75
|
+
|
|
76
|
+
Then:
|
|
72
77
|
|
|
73
78
|
```bash
|
|
74
79
|
sesh # terminal picker
|
package/bin/sesh.sh
CHANGED
|
@@ -38,6 +38,18 @@ fi
|
|
|
38
38
|
if [ "${1:-}" = --check ]; then
|
|
39
39
|
"$SESH_JQ" -en '"2026-01-01T00:00:00Z" | fromdateiso8601 | type == "number"' >/dev/null || { echo 'jq capability check failed' >&2; exit 1; }
|
|
40
40
|
command -v sqlite3 >/dev/null 2>&1 || command -v "$OPENCODE_BIN" >/dev/null 2>&1 || { echo 'neither sqlite3 nor opencode is available' >&2; exit 1; }
|
|
41
|
+
# The TUI panel is a copied file that opencode imports once at startup. Report
|
|
42
|
+
# when the installed copy has fallen behind this package so upgrades are not
|
|
43
|
+
# silently stale.
|
|
44
|
+
panel_bundled="$SCRIPT_DIR/../tui/sesh-panel.tsx"
|
|
45
|
+
panel_installed="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/plugins/sesh-panel.tsx"
|
|
46
|
+
if [ -f "$panel_installed" ] && [ -f "$panel_bundled" ]; then
|
|
47
|
+
if cmp -s "$panel_bundled" "$panel_installed"; then
|
|
48
|
+
echo 'sesh: sidebar panel is current'
|
|
49
|
+
else
|
|
50
|
+
echo 'sesh: sidebar panel is out of date; run `sesh install`, then fully quit and reopen opencode' >&2
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
41
53
|
echo 'sesh: dependencies ready'
|
|
42
54
|
exit 0
|
|
43
55
|
fi
|
package/install.sh
CHANGED
|
@@ -19,6 +19,7 @@ BIN_DIR="${XDG_BIN_HOME:-$HOME/.local/bin}"
|
|
|
19
19
|
INSTALL_TOOL=1
|
|
20
20
|
INSTALL_TUI=1
|
|
21
21
|
UNINSTALL=0
|
|
22
|
+
SYNC_PANEL=0
|
|
22
23
|
|
|
23
24
|
# Fallback plugin version for when the opencode CLI is unavailable; the
|
|
24
25
|
# installer prefers the local opencode version so the panel matches its SDK.
|
|
@@ -43,6 +44,7 @@ Usage: bash install.sh [--bin-dir DIR] [--no-tool] [--no-tui] [--uninstall]
|
|
|
43
44
|
--no-tool Skip installing the sesh-list custom tool.
|
|
44
45
|
--no-tui Skip installing the in-TUI sessions panel plugin.
|
|
45
46
|
--uninstall Remove everything this script installed.
|
|
47
|
+
--sync-panel Refresh an already-installed TUI panel only (used by postinstall).
|
|
46
48
|
--help Show this help.
|
|
47
49
|
USAGE
|
|
48
50
|
}
|
|
@@ -53,6 +55,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
53
55
|
--no-tool) INSTALL_TOOL=0; shift ;;
|
|
54
56
|
--no-tui) INSTALL_TUI=0; shift ;;
|
|
55
57
|
--uninstall) UNINSTALL=1; shift ;;
|
|
58
|
+
--sync-panel) SYNC_PANEL=1; shift ;;
|
|
56
59
|
--help) usage; exit 0 ;;
|
|
57
60
|
*) fail "unknown option: $1" ;;
|
|
58
61
|
esac
|
|
@@ -110,62 +113,6 @@ launcher_is_ours() {
|
|
|
110
113
|
[ -f "$target" ] && grep -qF 'Pick an opencode session' "$target" 2>/dev/null
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
if [ "$UNINSTALL" = 1 ]; then
|
|
114
|
-
if [ -L "$LAUNCHER" ]; then
|
|
115
|
-
if launcher_is_ours "$LAUNCHER"; then
|
|
116
|
-
rm -f "$LAUNCHER" && note "removed $LAUNCHER"
|
|
117
|
-
else
|
|
118
|
-
note "left $LAUNCHER untouched (not a sesh launcher: $(readlink "$LAUNCHER" 2>/dev/null))"
|
|
119
|
-
fi
|
|
120
|
-
fi
|
|
121
|
-
remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed $COMMAND_DST"
|
|
122
|
-
remove_if_marker "$TOOL_DST" "list opencode sessions for the agent" && note "removed $TOOL_DST"
|
|
123
|
-
remove_if_marker "$PANEL_DST" "@jsxImportSource @opentui/solid" && note "removed $PANEL_DST"
|
|
124
|
-
remove_tui_entry && note "unregistered the panel from $TUI_JSON"
|
|
125
|
-
note "uninstalled (extraction cache and plugin dependencies left intact)"
|
|
126
|
-
exit 0
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
for path in \
|
|
130
|
-
bin/sesh \
|
|
131
|
-
bin/sesh.sh \
|
|
132
|
-
bin/sesh-list.sh \
|
|
133
|
-
bin/sesh-preview.sh \
|
|
134
|
-
bin/sesh-delete.sh \
|
|
135
|
-
bin/sesh-refresh-worker.sh \
|
|
136
|
-
bin/sesh-shortcuts.sh \
|
|
137
|
-
opencode/tools/sesh-list.ts \
|
|
138
|
-
tui/sesh-panel.tsx \
|
|
139
|
-
themes/glow-dark-clean.json; do
|
|
140
|
-
[ -f "$SOURCE_DIR/$path" ] || fail "installer bundle is incomplete: missing $path."
|
|
141
|
-
done
|
|
142
|
-
|
|
143
|
-
for tool in bash jq fzf; do
|
|
144
|
-
command -v "$tool" >/dev/null 2>&1 || fail "required tool '$tool' is unavailable."
|
|
145
|
-
done
|
|
146
|
-
fzf_version=$(fzf --version 2>/dev/null || true)
|
|
147
|
-
if [[ "$fzf_version" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))? ]]; then
|
|
148
|
-
if [ "$((10#${BASH_REMATCH[1]}))" -eq 0 ] && [ "$((10#${BASH_REMATCH[2]}))" -lt 73 ]; then
|
|
149
|
-
fail "fzf >= 0.73.0 is required (found $fzf_version)."
|
|
150
|
-
fi
|
|
151
|
-
else
|
|
152
|
-
fail "cannot parse fzf version from: $fzf_version."
|
|
153
|
-
fi
|
|
154
|
-
command -v sqlite3 >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 \
|
|
155
|
-
|| fail "either sqlite3 or the opencode CLI is required."
|
|
156
|
-
command -v opencode >/dev/null 2>&1 || note "opencode CLI not found: listing and previews work via sqlite3, but resume and delete need opencode."
|
|
157
|
-
|
|
158
|
-
umask 077
|
|
159
|
-
mkdir -p "$BIN_DIR"
|
|
160
|
-
if [ -e "$LAUNCHER" ] && [ ! -L "$LAUNCHER" ]; then
|
|
161
|
-
fail "$LAUNCHER already exists and is not a symlink. Move it aside, then retry."
|
|
162
|
-
fi
|
|
163
|
-
if [ -L "$LAUNCHER" ] && ! launcher_is_ours "$LAUNCHER"; then
|
|
164
|
-
fail "$LAUNCHER is a symlink to $(readlink "$LAUNCHER" 2>/dev/null), which is not a sesh install. Move it aside, then retry."
|
|
165
|
-
fi
|
|
166
|
-
ln -sfn "$SOURCE_DIR/bin/sesh" "$LAUNCHER"
|
|
167
|
-
note "linked $LAUNCHER -> $SOURCE_DIR/bin/sesh"
|
|
168
|
-
|
|
169
116
|
install_file() {
|
|
170
117
|
local src=$1 dst=$2
|
|
171
118
|
mkdir -p "$(dirname "$dst")"
|
|
@@ -181,26 +128,20 @@ install_file() {
|
|
|
181
128
|
note "installed $dst"
|
|
182
129
|
}
|
|
183
130
|
|
|
184
|
-
|
|
185
|
-
# agent; the TUI plugin now registers the slash itself. Drop the old file so the
|
|
186
|
-
# two do not collide.
|
|
187
|
-
remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed obsolete markdown command $COMMAND_DST"
|
|
188
|
-
[ "$INSTALL_TOOL" = 1 ] && install_file "$SOURCE_DIR/opencode/tools/sesh-list.ts" "$TOOL_DST"
|
|
189
|
-
|
|
190
|
-
opencode_version=''
|
|
191
|
-
if command -v opencode >/dev/null 2>&1; then
|
|
192
|
-
raw_version=$(opencode --version 2>/dev/null || true)
|
|
193
|
-
if [[ "$raw_version" =~ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then opencode_version="${BASH_REMATCH[1]}"; fi
|
|
194
|
-
fi
|
|
195
|
-
plugin_version="${opencode_version:-$PLUGIN_VERSION_FALLBACK}"
|
|
196
|
-
|
|
197
|
-
if [ "$INSTALL_TUI" = 1 ]; then
|
|
131
|
+
install_tui_panel() {
|
|
198
132
|
install_file "$SOURCE_DIR/tui/sesh-panel.tsx" "$PANEL_DST"
|
|
199
133
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
134
|
+
local opencode_version='' raw_version='' plugin_version=''
|
|
135
|
+
if command -v opencode >/dev/null 2>&1; then
|
|
136
|
+
raw_version=$(opencode --version 2>/dev/null || true)
|
|
137
|
+
if [[ "$raw_version" =~ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then opencode_version="${BASH_REMATCH[1]}"; fi
|
|
138
|
+
fi
|
|
139
|
+
plugin_version="${opencode_version:-$PLUGIN_VERSION_FALLBACK}"
|
|
140
|
+
|
|
141
|
+
# opencode has no persistent-sidebar API: the panel is an xlarge modal dialog
|
|
142
|
+
# (see tui/sesh-panel.tsx). Register it idempotently and drop the previous
|
|
143
|
+
# filename if an older install left it behind.
|
|
144
|
+
local tui_created=0 tui_tmp=''
|
|
204
145
|
if [ ! -f "$TUI_JSON" ]; then
|
|
205
146
|
printf '{\n "$schema": "https://opencode.ai/tui.json",\n "plugin": []\n}\n' > "$TUI_JSON"
|
|
206
147
|
tui_created=1
|
|
@@ -228,7 +169,7 @@ if [ "$INSTALL_TUI" = 1 ]; then
|
|
|
228
169
|
# Local plugins resolve their imports from the config directory, and opencode
|
|
229
170
|
# runs `bun install` there at startup. Keep the required packages declared
|
|
230
171
|
# without clobbering versions the user already pinned.
|
|
231
|
-
pkg_created=0
|
|
172
|
+
local pkg_created=0 pkg_tmp=''
|
|
232
173
|
if [ ! -f "$PACKAGE_JSON" ]; then
|
|
233
174
|
printf '{\n "dependencies": {}\n}\n' > "$PACKAGE_JSON"
|
|
234
175
|
pkg_created=1
|
|
@@ -257,12 +198,104 @@ if [ "$INSTALL_TUI" = 1 ]; then
|
|
|
257
198
|
rm -f "$pkg_tmp"
|
|
258
199
|
note "warning: could not update $PACKAGE_JSON; the TUI panel may fail to load"
|
|
259
200
|
fi
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# opencode imports plugins once at startup and has no hot reload, so a running
|
|
204
|
+
# instance keeps the old panel until it is fully quit (reloading a window reuses
|
|
205
|
+
# the same process). Say so explicitly and name the processes still holding it.
|
|
206
|
+
note_restart() {
|
|
207
|
+
note "restart opencode to load the updated panel: quit it completely"
|
|
208
|
+
note " (all windows and background processes), then reopen."
|
|
209
|
+
if command -v pgrep >/dev/null 2>&1; then
|
|
210
|
+
local pids
|
|
211
|
+
pids=$(pgrep -x opencode 2>/dev/null | tr '\n' ' ' | sed 's/ $//' || true)
|
|
212
|
+
[ -n "$pids" ] && note " still running: opencode PID(s) $pids"
|
|
213
|
+
fi
|
|
214
|
+
return 0
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if [ "$UNINSTALL" = 1 ]; then
|
|
218
|
+
if [ -L "$LAUNCHER" ]; then
|
|
219
|
+
if launcher_is_ours "$LAUNCHER"; then
|
|
220
|
+
rm -f "$LAUNCHER" && note "removed $LAUNCHER"
|
|
221
|
+
else
|
|
222
|
+
note "left $LAUNCHER untouched (not a sesh launcher: $(readlink "$LAUNCHER" 2>/dev/null))"
|
|
223
|
+
fi
|
|
224
|
+
fi
|
|
225
|
+
remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed $COMMAND_DST"
|
|
226
|
+
remove_if_marker "$TOOL_DST" "list opencode sessions for the agent" && note "removed $TOOL_DST"
|
|
227
|
+
remove_if_marker "$PANEL_DST" "@jsxImportSource @opentui/solid" && note "removed $PANEL_DST"
|
|
228
|
+
remove_tui_entry && note "unregistered the panel from $TUI_JSON"
|
|
229
|
+
note "uninstalled (extraction cache and plugin dependencies left intact)"
|
|
230
|
+
exit 0
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
for path in \
|
|
234
|
+
bin/sesh \
|
|
235
|
+
bin/sesh.sh \
|
|
236
|
+
bin/sesh-list.sh \
|
|
237
|
+
bin/sesh-preview.sh \
|
|
238
|
+
bin/sesh-delete.sh \
|
|
239
|
+
bin/sesh-refresh-worker.sh \
|
|
240
|
+
bin/sesh-shortcuts.sh \
|
|
241
|
+
opencode/tools/sesh-list.ts \
|
|
242
|
+
tui/sesh-panel.tsx \
|
|
243
|
+
themes/glow-dark-clean.json; do
|
|
244
|
+
[ -f "$SOURCE_DIR/$path" ] || fail "installer bundle is incomplete: missing $path."
|
|
245
|
+
done
|
|
246
|
+
|
|
247
|
+
if [ "$SYNC_PANEL" = 1 ]; then
|
|
248
|
+
# Postinstall path: refresh a panel the user already opted into, without
|
|
249
|
+
# touching the launcher or requiring fzf/sqlite. No-op when the panel is
|
|
250
|
+
# absent, so `npm install` never adds a TUI panel on its own.
|
|
251
|
+
[ -f "$PANEL_DST" ] || { note "sesh sidebar panel is not installed; nothing to sync."; exit 0; }
|
|
252
|
+
umask 077
|
|
253
|
+
install_tui_panel
|
|
254
|
+
note_restart
|
|
255
|
+
exit 0
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
for tool in bash jq fzf; do
|
|
259
|
+
command -v "$tool" >/dev/null 2>&1 || fail "required tool '$tool' is unavailable."
|
|
260
|
+
done
|
|
261
|
+
fzf_version=$(fzf --version 2>/dev/null || true)
|
|
262
|
+
if [[ "$fzf_version" =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))? ]]; then
|
|
263
|
+
if [ "$((10#${BASH_REMATCH[1]}))" -eq 0 ] && [ "$((10#${BASH_REMATCH[2]}))" -lt 73 ]; then
|
|
264
|
+
fail "fzf >= 0.73.0 is required (found $fzf_version)."
|
|
265
|
+
fi
|
|
266
|
+
else
|
|
267
|
+
fail "cannot parse fzf version from: $fzf_version."
|
|
268
|
+
fi
|
|
269
|
+
command -v sqlite3 >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 \
|
|
270
|
+
|| fail "either sqlite3 or the opencode CLI is required."
|
|
271
|
+
command -v opencode >/dev/null 2>&1 || note "opencode CLI not found: listing and previews work via sqlite3, but resume and delete need opencode."
|
|
272
|
+
|
|
273
|
+
umask 077
|
|
274
|
+
mkdir -p "$BIN_DIR"
|
|
275
|
+
if [ -e "$LAUNCHER" ] && [ ! -L "$LAUNCHER" ]; then
|
|
276
|
+
fail "$LAUNCHER already exists and is not a symlink. Move it aside, then retry."
|
|
277
|
+
fi
|
|
278
|
+
if [ -L "$LAUNCHER" ] && ! launcher_is_ours "$LAUNCHER"; then
|
|
279
|
+
fail "$LAUNCHER is a symlink to $(readlink "$LAUNCHER" 2>/dev/null), which is not a sesh install. Move it aside, then retry."
|
|
280
|
+
fi
|
|
281
|
+
ln -sfn "$SOURCE_DIR/bin/sesh" "$LAUNCHER"
|
|
282
|
+
note "linked $LAUNCHER -> $SOURCE_DIR/bin/sesh"
|
|
283
|
+
|
|
284
|
+
# Earlier versions shipped a markdown `/sesh` command that could only prompt the
|
|
285
|
+
# agent; the TUI plugin now registers the slash itself. Drop the old file so the
|
|
286
|
+
# two do not collide.
|
|
287
|
+
remove_if_marker "$COMMAND_DST" "rich interactive picker" && note "removed obsolete markdown command $COMMAND_DST"
|
|
288
|
+
[ "$INSTALL_TOOL" = 1 ] && install_file "$SOURCE_DIR/opencode/tools/sesh-list.ts" "$TOOL_DST"
|
|
289
|
+
|
|
290
|
+
if [ "$INSTALL_TUI" = 1 ]; then
|
|
291
|
+
install_tui_panel
|
|
260
292
|
fi
|
|
261
293
|
|
|
262
294
|
"$LAUNCHER" --check || fail "post-install check failed."
|
|
263
295
|
note "done"
|
|
264
296
|
note " picker: run 'sesh' in any terminal, or '/sesh' inside opencode"
|
|
265
|
-
note " sidebar:
|
|
297
|
+
note " sidebar: ctrl+o opens the full picker"
|
|
298
|
+
note_restart
|
|
266
299
|
case ":$PATH:" in
|
|
267
300
|
*":$BIN_DIR:"*) ;;
|
|
268
301
|
*) note " note: $BIN_DIR is not on PATH; add it to use the launcher anywhere." ;;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-sesh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Browse, search, preview and resume opencode sessions from the terminal and the TUI.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"prepare": "husky",
|
|
44
|
+
"postinstall": "bash install.sh --sync-panel || true",
|
|
44
45
|
"test": "bash tests/sesh.sh",
|
|
45
46
|
"test:picker": "python3 tests/picker.py",
|
|
46
47
|
"typecheck": "tsc -p tsconfig.json",
|
package/tui/sesh-panel.tsx
CHANGED
|
@@ -130,6 +130,13 @@ function truncate(text: string, width: number): string {
|
|
|
130
130
|
return text.slice(0, width - 1) + "…"
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// Sidebar and home rows should read at a similar length. The sidebar title
|
|
134
|
+
// column is ~2 chars narrower than the group column (status dot + timestamp),
|
|
135
|
+
// so cap it a bit lower to guarantee separation.
|
|
136
|
+
const SIDEBAR_TITLE_WIDTH = 27
|
|
137
|
+
const SIDEBAR_GROUP_WIDTH = 30
|
|
138
|
+
const HOME_TITLE_WIDTH = 30
|
|
139
|
+
|
|
133
140
|
const SEARCH_KEYS = "abcdefghijklmnopqrstuvwxyz0123456789-_.@/+".split("")
|
|
134
141
|
|
|
135
142
|
function searchBindings(append: (input: string) => void, exit: () => void) {
|
|
@@ -445,7 +452,7 @@ function createTranscriptPreview(api: TuiPluginApi) {
|
|
|
445
452
|
|
|
446
453
|
type TreeRow =
|
|
447
454
|
| { kind: "group"; dir: string; label: string; count: number }
|
|
448
|
-
| { kind: "item"; entry: Entry }
|
|
455
|
+
| { kind: "item"; entry: Entry; last: boolean }
|
|
449
456
|
|
|
450
457
|
function SidebarSessions(props: { api: TuiPluginApi }) {
|
|
451
458
|
const theme = () => props.api.theme.current
|
|
@@ -504,7 +511,9 @@ function SidebarSessions(props: { api: TuiPluginApi }) {
|
|
|
504
511
|
for (const group of ordered) {
|
|
505
512
|
rows.push({ kind: "group", dir: group.dir, label: prettyDir(group.dir, home), count: group.list.length })
|
|
506
513
|
if (collapsed()[group.dir]) continue
|
|
507
|
-
for (
|
|
514
|
+
for (let i = 0; i < group.list.length; i++) {
|
|
515
|
+
rows.push({ kind: "item", entry: group.list[i], last: i === group.list.length - 1 })
|
|
516
|
+
}
|
|
508
517
|
}
|
|
509
518
|
return rows
|
|
510
519
|
})
|
|
@@ -676,10 +685,10 @@ function SidebarSessions(props: { api: TuiPluginApi }) {
|
|
|
676
685
|
<For each={tree()}>
|
|
677
686
|
{(row) =>
|
|
678
687
|
row.kind === "group" ? (
|
|
679
|
-
<box flexDirection="row" onMouseDown={() => toggleGroup(row.dir)}>
|
|
688
|
+
<box flexDirection="row" paddingTop={1} onMouseDown={() => toggleGroup(row.dir)}>
|
|
680
689
|
<text flexGrow={1} flexShrink={1} wrapMode="none">
|
|
681
690
|
<span style={{ fg: theme().textMuted }}>{collapsed()[row.dir] ? "▸ " : "▾ "}</span>
|
|
682
|
-
<b>{row.label}</b>
|
|
691
|
+
<b>{truncate(row.label, SIDEBAR_GROUP_WIDTH)}</b>
|
|
683
692
|
</text>
|
|
684
693
|
<text flexShrink={0} style={{ fg: theme().textMuted }}>
|
|
685
694
|
{" "}({row.count})
|
|
@@ -700,14 +709,16 @@ function SidebarSessions(props: { api: TuiPluginApi }) {
|
|
|
700
709
|
onMouseOut={() => setHovered(undefined)}
|
|
701
710
|
onMouseDown={() => props.api.route.navigate("session", { sessionID: row.entry.id })}
|
|
702
711
|
>
|
|
703
|
-
<text
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
712
|
+
<text flexShrink={0}>
|
|
713
|
+
<span style={{ fg: theme().textMuted }}>{row.last ? "└" : "├"}</span>
|
|
714
|
+
<span
|
|
715
|
+
style={{ fg: row.entry.id === currentID() ? theme().accent : statusColor(row.entry.id) }}
|
|
716
|
+
>
|
|
717
|
+
{row.entry.id === currentID() ? "●" : "○"}
|
|
718
|
+
</span>
|
|
708
719
|
</text>
|
|
709
720
|
<text flexGrow={1} flexShrink={1} overflow="hidden" wrapMode="none" style={{ fg: theme().text }}>
|
|
710
|
-
{row.entry.title}
|
|
721
|
+
{truncate(row.entry.title, SIDEBAR_TITLE_WIDTH)}
|
|
711
722
|
</text>
|
|
712
723
|
<text flexShrink={0} style={{ fg: theme().textMuted }}>
|
|
713
724
|
{ago(row.entry.updated)}
|
|
@@ -865,7 +876,7 @@ function HomeSessions(props: { api: TuiPluginApi }) {
|
|
|
865
876
|
○
|
|
866
877
|
</text>
|
|
867
878
|
<text flexGrow={1} flexShrink={1} overflow="hidden" wrapMode="none" style={{ fg: theme().text }}>
|
|
868
|
-
{truncate(entry.title,
|
|
879
|
+
{truncate(entry.title, HOME_TITLE_WIDTH)}
|
|
869
880
|
</text>
|
|
870
881
|
<text flexShrink={0} style={{ fg: theme().textMuted }}>
|
|
871
882
|
{prettyDir(entry.dir, home)} · {ago(entry.updated)}
|
|
@@ -1032,7 +1043,8 @@ const tui: TuiPlugin = async (api) => {
|
|
|
1032
1043
|
setPreviewText(cached)
|
|
1033
1044
|
return
|
|
1034
1045
|
}
|
|
1035
|
-
|
|
1046
|
+
// Keep the previous transcript visible while the next one loads so the
|
|
1047
|
+
// preview does not blink away on every cursor move.
|
|
1036
1048
|
previewTimer = setTimeout(() => void loadPreview(id), 120)
|
|
1037
1049
|
})
|
|
1038
1050
|
|
|
@@ -1167,10 +1179,6 @@ const tui: TuiPlugin = async (api) => {
|
|
|
1167
1179
|
? api.theme.current.primary
|
|
1168
1180
|
: RGBA.fromInts(0, 0, 0, 0)
|
|
1169
1181
|
}
|
|
1170
|
-
onMouseOver={() => {
|
|
1171
|
-
const i = selectableEntries().findIndex((entry) => entry.id === row.entry.id)
|
|
1172
|
-
if (i >= 0 && i !== cursor()) setCursor(i)
|
|
1173
|
-
}}
|
|
1174
1182
|
onMouseDown={() => choose(row.entry)}
|
|
1175
1183
|
>
|
|
1176
1184
|
<Show when={row.entry.id === currentSessionID()}>
|