opencode-sesh 0.9.0 → 0.9.1
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/AGENTS.md +7 -5
- package/CHANGELOG.md +12 -1
- package/README.md +7 -7
- package/bin/sesh-list.sh +4 -2
- package/bin/sesh-shortcuts.sh +2 -2
- package/bin/sesh.sh +2 -2
- package/package.json +1 -1
- package/tui/sesh-panel.tsx +52 -51
package/AGENTS.md
CHANGED
|
@@ -83,8 +83,8 @@ the schema below. Keystrokes never touch the database:
|
|
|
83
83
|
epoch, not a boolean).
|
|
84
84
|
- Session and directory pins live in `${XDG_DATA_HOME:-$HOME/.local/share}/sesh/pins.json`
|
|
85
85
|
(`SESH_PINS_FILE` overrides it for tests), shared by fzf and the TUI. Writes
|
|
86
|
-
use a sibling lock directory and atomic rename.
|
|
87
|
-
session;
|
|
86
|
+
use a sibling lock directory and atomic rename. Ctrl-S toggles a selected
|
|
87
|
+
session; Ctrl-D toggles its directory. Pinned directories sort before groups
|
|
88
88
|
containing pinned sessions, with pinned sessions first inside each group.
|
|
89
89
|
|
|
90
90
|
**Output contract** (TSV, 6 fields):
|
|
@@ -105,7 +105,8 @@ Resume happens in place (`exec` after `cd` to the session's cwd).
|
|
|
105
105
|
## Conventions
|
|
106
106
|
|
|
107
107
|
- Default scope is **all** sessions, every directory. `--cwd`/Ctrl-G narrows.
|
|
108
|
-
Archived sessions and fork children
|
|
108
|
+
Archived sessions and child sessions (fork children, spawned subagents) are
|
|
109
|
+
hidden unless `--archived`; metadata and cache pruning still see them.
|
|
109
110
|
- **Text parts only** in search/preview; reasoning and tool payloads are
|
|
110
111
|
excluded (asserted in tests).
|
|
111
112
|
- Validate session ids (`^ses_[A-Za-z0-9]+$`) before SQL interpolation — never
|
|
@@ -141,8 +142,9 @@ Resume happens in place (`exec` after `cd` to the session's cwd).
|
|
|
141
142
|
- **Sidebar:** `api.slots.register` on `sidebar_content` (append mode — native
|
|
142
143
|
sidebar content stays). Pinned directories sort first, then directory groups
|
|
143
144
|
by recency; sessions within each group sort by last update regardless of pins.
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
Row markers alone signal session state: a running agent animates a loading
|
|
146
|
+
spinner marker, the open session shows its own ●, and every other idle row
|
|
147
|
+
stays neutral (polled every 15 s); Ctrl-P previews
|
|
146
148
|
a row and ctrl+x deletes one (armed, then confirmed). The section lists every
|
|
147
149
|
session without a row cap and flex-grows to fill the sidebar, with overflow
|
|
148
150
|
scrolling inside; the picker remains the place for transcript search.
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.1] - 2026-09-25
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Sidebar rows show state only in the marker column: a running agent animates
|
|
15
|
+
a loading spinner; idle rows and their titles go back to neutral.
|
|
16
|
+
- Pin keybinds move from Option-S/Option-D to Ctrl-S/Ctrl-D in the terminal
|
|
17
|
+
picker, the sidebar, and the picker dialog.
|
|
18
|
+
- The terminal picker hides child sessions (fork children, spawned subagents)
|
|
19
|
+
like the panel does; `--archived` still opts them back in.
|
|
10
20
|
## [0.9.0] - 2026-09-24
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -342,7 +352,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
342
352
|
- Fixture-database regression suite, TypeScript typechecking, and ShellCheck in
|
|
343
353
|
CI.
|
|
344
354
|
|
|
345
|
-
[Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.9.
|
|
355
|
+
[Unreleased]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.9.1...HEAD
|
|
356
|
+
[0.9.1]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.9.0...v0.9.1
|
|
346
357
|
[0.9.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.8.0...v0.9.0
|
|
347
358
|
[0.8.0]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.3...v0.8.0
|
|
348
359
|
[0.7.3]: https://github.com/ozandogrultan/opencode-sesh/compare/v0.7.2...v0.7.3
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ grouped by directory, full-text searchable, and previewable from anywhere.
|
|
|
28
28
|
|
|
29
29
|
- **Every session, everywhere.** Sessions are grouped by project directory and
|
|
30
30
|
sorted by pins, then recency — across *all* your projects, not just the current one.
|
|
31
|
-
- **Pin what matters.**
|
|
31
|
+
- **Pin what matters.** Ctrl-S pins a session and Ctrl-D pins its directory in
|
|
32
32
|
either picker. Pins are shared across the terminal and opencode TUI and
|
|
33
33
|
persist across restarts; pinned directories and sessions sort first.
|
|
34
34
|
- **Full-text search.** Type to match against session titles *and* the text of
|
|
@@ -115,7 +115,7 @@ bash install.sh --uninstall # from source
|
|
|
115
115
|
| Enter | Resume the selected session in this terminal |
|
|
116
116
|
| Ctrl-F | Resume as a fork (the original is untouched) |
|
|
117
117
|
| Ctrl-G | Toggle current-directory scope / all sessions |
|
|
118
|
-
|
|
|
118
|
+
| Ctrl-S / Ctrl-D | Pin or unpin the selected session / directory |
|
|
119
119
|
| Ctrl-P | Toggle the transcript preview |
|
|
120
120
|
| `?` (empty query) | Toggle shortcut help |
|
|
121
121
|
| Ctrl-X | Delete the selected session (asks to confirm) |
|
|
@@ -176,21 +176,21 @@ non-interactive `sesh-delete.sh` refuses unless given `--yes`.
|
|
|
176
176
|
| Ctrl-F | Fork the selected session |
|
|
177
177
|
| Ctrl-G | Toggle scope: the selected session's project, or every project |
|
|
178
178
|
| Option-W / Option-P | Show only sessions needing input / pinned sessions |
|
|
179
|
-
|
|
|
179
|
+
| Ctrl-S / Ctrl-D | Pin or unpin the selected session / directory |
|
|
180
180
|
| Esc | Close |
|
|
181
181
|
|
|
182
182
|
Click the sidebar's `search…` box to filter recent sessions in place; click
|
|
183
183
|
elsewhere or press Esc to leave search. Click the **Sessions** heading to drive
|
|
184
184
|
the list from the keyboard instead of the mouse (`↑`/`↓` to move, Enter to open,
|
|
185
|
-
Ctrl-P to preview,
|
|
185
|
+
Ctrl-P to preview, Ctrl-S/Ctrl-D to pin, Ctrl-X to delete, Esc to leave). Pinned
|
|
186
186
|
directories rise first, while sessions within each directory are ordered by
|
|
187
187
|
last update, newest first. The section lists every session and scrolls to fill
|
|
188
188
|
the sidebar, so click **▾ hide** to collapse it when you need the room.
|
|
189
189
|
Sessions waiting on you (unanswered agent questions, runs stuck mid-tool) pin
|
|
190
190
|
themselves to a **Needs input** group above the directories, with the count in
|
|
191
|
-
the heading; `sesh --needs-input` lists the same set in the terminal. Row
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
the heading; `sesh --needs-input` lists the same set in the terminal. Row
|
|
192
|
+
markers are the only status signal: a loading spinner marks the session whose
|
|
193
|
+
agent is working; the open session shows ● and every other row stays neutral.
|
|
194
194
|
Hovering a row while keyboard navigation is active selects it for the next
|
|
195
195
|
shortcut. Clicking (or Enter on) a session opens it here. The full picker is one
|
|
196
196
|
keystroke away.
|
package/bin/sesh-list.sh
CHANGED
|
@@ -276,7 +276,7 @@ refresh() {
|
|
|
276
276
|
historical="$scratch/historical.jsonl"; needs_extract="$scratch/needs-extract.json"
|
|
277
277
|
out="$scratch/snapshot.jsonl"
|
|
278
278
|
|
|
279
|
-
db_json "SELECT id, directory, title, time_created, time_updated, COALESCE(time_archived, 0) AS archived FROM session ORDER BY time_updated DESC;" > "$sessions" 2>/dev/null || {
|
|
279
|
+
db_json "SELECT id, directory, title, time_created, time_updated, COALESCE(time_archived, 0) AS archived, COALESCE(parent_id, '') AS parent FROM session ORDER BY time_updated DESC;" > "$sessions" 2>/dev/null || {
|
|
280
280
|
if [ -f "$SNAPSHOT" ]; then set_status 'stale: opencode database query failed; showing last good snapshot'; else set_status 'unavailable: opencode database query failed'; fi
|
|
281
281
|
return 0
|
|
282
282
|
}
|
|
@@ -388,7 +388,9 @@ refresh() {
|
|
|
388
388
|
| ($hist | map({key: .sessionId, value: .}) | from_entries) as $histById
|
|
389
389
|
| [$meta[]
|
|
390
390
|
| select((.id | type) == "string" and (.id | test("^ses_[A-Za-z0-9]+$")))
|
|
391
|
-
|
|
391
|
+
# Child sessions (fork children, spawned subagents) surface only with
|
|
392
|
+
# --archived; metadata still sees them, so cache pruning stays safe.
|
|
393
|
+
| select($archived == 1 or (.archived == 0 and .parent == ""))
|
|
392
394
|
| select($scope == "" or .directory == $scope)
|
|
393
395
|
| . as $m | ($histById[$m.id] // {}) as $h
|
|
394
396
|
| {sessionId: $m.id, malformedRecords: 0, agentId: "", agentState: "",
|
package/bin/sesh-shortcuts.sh
CHANGED
|
@@ -8,8 +8,8 @@ sesh shortcuts
|
|
|
8
8
|
Ctrl-F Resume the selected session as a fork
|
|
9
9
|
Ctrl-G Toggle current-directory scope / all sessions
|
|
10
10
|
Ctrl-P Toggle transcript preview
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
Ctrl-S Pin / unpin selected session
|
|
12
|
+
Ctrl-D Pin / unpin selected directory
|
|
13
13
|
? Toggle this help
|
|
14
14
|
Ctrl-X Delete the selected session (asks to confirm)
|
|
15
15
|
Escape Exit the picker
|
package/bin/sesh.sh
CHANGED
|
@@ -146,8 +146,8 @@ while :; do
|
|
|
146
146
|
--bind="$help_bind" \
|
|
147
147
|
--bind="ctrl-x:execute($delete_cmd)+transform-header($header_cmd)+reload($cache_cmd)" \
|
|
148
148
|
--bind="ctrl-g:execute($toggle_cmd)+transform-header($header_cmd)+reload($cache_cmd)" \
|
|
149
|
-
--bind="
|
|
150
|
-
--bind="
|
|
149
|
+
--bind="ctrl-s:execute-silent($pin_session_cmd)+reload($cache_cmd)" \
|
|
150
|
+
--bind="ctrl-d:execute-silent($pin_directory_cmd)+reload($cache_cmd)" \
|
|
151
151
|
--bind="change:reload:$cache_cmd" \
|
|
152
152
|
--bind="start,every(3):transform-header($header_cmd)+reload-sync:$cache_cmd")
|
|
153
153
|
status=$?
|
package/package.json
CHANGED
package/tui/sesh-panel.tsx
CHANGED
|
@@ -248,19 +248,20 @@ function ensureRootMouse(renderer: TuiPluginApi["renderer"]) {
|
|
|
248
248
|
|
|
249
249
|
type Entry = { id: string; title: string; dir: string; group: string; updated: number }
|
|
250
250
|
type EntryResult = { entries: Entry[]; truncated: boolean }
|
|
251
|
-
type
|
|
251
|
+
type SidebarMarker = "current" | "running" | "idle"
|
|
252
|
+
|
|
253
|
+
// Loading-spinner frames for the marker of a session whose agent is working.
|
|
254
|
+
const SIDEBAR_SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
252
255
|
|
|
253
256
|
function newestFirst(entries: Entry[]): Entry[] {
|
|
254
257
|
return [...entries].sort((a, b) => b.updated - a.updated || a.id.localeCompare(b.id))
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
// A
|
|
258
|
-
//
|
|
259
|
-
function
|
|
260
|
-
if (pending || reason === "question") return "waiting"
|
|
260
|
+
// A working agent is the only state the marker displays: everything that
|
|
261
|
+
// finished goes back to the neutral row, with the open session marked inside.
|
|
262
|
+
function sidebarMarker(current: boolean, live?: string): SidebarMarker {
|
|
261
263
|
if (live === "busy" || live === "retry") return "running"
|
|
262
|
-
|
|
263
|
-
return current ? "active" : "idle"
|
|
264
|
+
return current ? "current" : "idle"
|
|
264
265
|
}
|
|
265
266
|
|
|
266
267
|
const SESSION_PAGE_LIMIT = 200
|
|
@@ -808,8 +809,6 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
808
809
|
const ids = waiting()
|
|
809
810
|
return newestFirst(shownEntries().filter((entry) => ids.has(entry.id)))
|
|
810
811
|
})
|
|
811
|
-
const waitingReason = (id: string) =>
|
|
812
|
-
waiting().get(id) === "question" ? "awaiting answer" : waiting().get(id) === "stuck" ? "run stuck" : "needs input"
|
|
813
812
|
|
|
814
813
|
const tree = createMemo<TreeRow[]>(() => {
|
|
815
814
|
const rows: TreeRow[] = []
|
|
@@ -884,26 +883,34 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
884
883
|
previousRows = rows
|
|
885
884
|
})
|
|
886
885
|
|
|
887
|
-
|
|
886
|
+
// Marker only: a spinner for a working agent, the open-session dot, and the
|
|
887
|
+
// neutral row for idle sessions. Titles stay plain and no status text is
|
|
888
|
+
// rendered.
|
|
889
|
+
const markerOf = (id: string): SidebarMarker => {
|
|
890
|
+
let live: string | undefined
|
|
888
891
|
try {
|
|
889
|
-
|
|
890
|
-
const questions = props.api.state.session.question(id)
|
|
891
|
-
return sidebarActivity(
|
|
892
|
-
id === currentID(),
|
|
893
|
-
props.api.state.session.status(id)?.type,
|
|
894
|
-
(permissions?.length ?? 0) > 0 || (questions?.length ?? 0) > 0,
|
|
895
|
-
waiting().get(id),
|
|
896
|
-
)
|
|
892
|
+
live = props.api.state.session.status(id)?.type
|
|
897
893
|
} catch {}
|
|
898
|
-
return
|
|
894
|
+
return sidebarMarker(id === currentID(), live)
|
|
899
895
|
}
|
|
900
896
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
897
|
+
// The spinner animates only while a listed session is actually running, so
|
|
898
|
+
// an idle list costs no timer and only running rows re-render per frame.
|
|
899
|
+
const [spinFrame, setSpinFrame] = createSignal(0)
|
|
900
|
+
let spinTimer: ReturnType<typeof setInterval> | undefined
|
|
901
|
+
createEffect(() => {
|
|
902
|
+
const spinning = itemRows().some((entry) => markerOf(entry.id) === "running")
|
|
903
|
+
if (spinning && !spinTimer) {
|
|
904
|
+
spinTimer = setInterval(() => setSpinFrame((value) => (value + 1) % SIDEBAR_SPINNER.length), 120)
|
|
905
|
+
} else if (!spinning && spinTimer) {
|
|
906
|
+
clearInterval(spinTimer)
|
|
907
|
+
spinTimer = undefined
|
|
908
|
+
setSpinFrame(0)
|
|
909
|
+
}
|
|
910
|
+
})
|
|
911
|
+
onCleanup(() => {
|
|
912
|
+
if (spinTimer) clearInterval(spinTimer)
|
|
913
|
+
})
|
|
907
914
|
|
|
908
915
|
const cancelDelete = () => {
|
|
909
916
|
if (confirmTimer) clearTimeout(confirmTimer)
|
|
@@ -1027,8 +1034,8 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1027
1034
|
if (!confirmArmed()) requestDelete(entry)
|
|
1028
1035
|
},
|
|
1029
1036
|
},
|
|
1030
|
-
{ key: "
|
|
1031
|
-
{ key: "
|
|
1037
|
+
{ key: "ctrl+s", desc: "Pin session", preventDefault: true, cmd: () => props.onTogglePin("sessions", entry.id) },
|
|
1038
|
+
{ key: "ctrl+d", desc: "Pin directory", preventDefault: true, cmd: () => props.onTogglePin("directories", entry.dir) },
|
|
1032
1039
|
{
|
|
1033
1040
|
key: "/",
|
|
1034
1041
|
desc: "Search sessions",
|
|
@@ -1077,7 +1084,7 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1077
1084
|
},
|
|
1078
1085
|
},
|
|
1079
1086
|
{
|
|
1080
|
-
key: "
|
|
1087
|
+
key: "ctrl+s",
|
|
1081
1088
|
desc: "Pin session",
|
|
1082
1089
|
preventDefault: true,
|
|
1083
1090
|
cmd: () => {
|
|
@@ -1086,7 +1093,7 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1086
1093
|
},
|
|
1087
1094
|
},
|
|
1088
1095
|
{
|
|
1089
|
-
key: "
|
|
1096
|
+
key: "ctrl+d",
|
|
1090
1097
|
desc: "Pin directory",
|
|
1091
1098
|
preventDefault: true,
|
|
1092
1099
|
cmd: () => {
|
|
@@ -1210,7 +1217,7 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1210
1217
|
</text>
|
|
1211
1218
|
</box>
|
|
1212
1219
|
)
|
|
1213
|
-
const
|
|
1220
|
+
const marker = createMemo(() => markerOf(row.entry.id))
|
|
1214
1221
|
return (
|
|
1215
1222
|
<box
|
|
1216
1223
|
flexDirection="row"
|
|
@@ -1225,7 +1232,7 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1225
1232
|
onMouseOver={() => {
|
|
1226
1233
|
setHovered(row.entry.id)
|
|
1227
1234
|
// The keyboard layer wins over the hover layer while nav is
|
|
1228
|
-
// active. Keep its cursor on the hovered row so
|
|
1235
|
+
// active. Keep its cursor on the hovered row so Ctrl-S/Ctrl-D
|
|
1229
1236
|
// cannot silently pin a different (often current) session.
|
|
1230
1237
|
if (navActive()) {
|
|
1231
1238
|
const index = itemRows().findIndex((entry) => entry.id === row.entry.id)
|
|
@@ -1241,30 +1248,24 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1241
1248
|
style={{
|
|
1242
1249
|
fg: isPendingDelete(row.entry.id)
|
|
1243
1250
|
? theme().error
|
|
1244
|
-
:
|
|
1251
|
+
: marker() === "current"
|
|
1245
1252
|
? theme().accent
|
|
1246
|
-
:
|
|
1253
|
+
: marker() === "running"
|
|
1254
|
+
? theme().success
|
|
1255
|
+
: theme().textMuted,
|
|
1247
1256
|
}}
|
|
1248
1257
|
>
|
|
1249
|
-
{
|
|
1258
|
+
{marker() === "current" ? "●" : marker() === "running" ? SIDEBAR_SPINNER[spinFrame()] : isActive(row.entry.id) ? "▸" : "○"}
|
|
1250
1259
|
</span>
|
|
1251
1260
|
</text>
|
|
1252
1261
|
<Highlighted
|
|
1253
1262
|
text={`${props.pins().sessions.includes(row.entry.id) ? "★ " : ""}${truncate(row.entry.title, SIDEBAR_TITLE_WIDTH - (props.pins().sessions.includes(row.entry.id) ? 2 : 0))}`}
|
|
1254
1263
|
query={query()}
|
|
1255
|
-
color={isPendingDelete(row.entry.id) ? theme().error :
|
|
1264
|
+
color={isPendingDelete(row.entry.id) ? theme().error : theme().text}
|
|
1256
1265
|
matchColor={theme().warning}
|
|
1257
1266
|
/>
|
|
1258
1267
|
<text flexShrink={0} style={{ fg: isPendingDelete(row.entry.id) ? theme().error : theme().textMuted }}>
|
|
1259
|
-
{isPendingDelete(row.entry.id) ? (
|
|
1260
|
-
"ctrl+x again"
|
|
1261
|
-
) : activity() !== "idle" ? (
|
|
1262
|
-
<span>
|
|
1263
|
-
{ago(row.entry.updated)} · <span style={{ fg: statusColor(activity()) }}>{activity() === "waiting" ? waitingReason(row.entry.id) : activity()}</span>
|
|
1264
|
-
</span>
|
|
1265
|
-
) : (
|
|
1266
|
-
ago(row.entry.updated)
|
|
1267
|
-
)}
|
|
1268
|
+
{isPendingDelete(row.entry.id) ? "ctrl+x again" : ago(row.entry.updated)}
|
|
1268
1269
|
</text>
|
|
1269
1270
|
</box>
|
|
1270
1271
|
)
|
|
@@ -1279,7 +1280,7 @@ function SidebarSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1279
1280
|
</Show>
|
|
1280
1281
|
<Show when={navActive()}>
|
|
1281
1282
|
<box paddingTop={1}>
|
|
1282
|
-
<text style={{ fg: theme().textMuted }}>↑↓ move · enter open · ctrl+p preview ·
|
|
1283
|
+
<text style={{ fg: theme().textMuted }}>↑↓ move · enter open · ctrl+p preview · ctrl+s/d pin · ctrl+x delete · esc done</text>
|
|
1283
1284
|
</box>
|
|
1284
1285
|
</Show>
|
|
1285
1286
|
</Show>
|
|
@@ -1350,8 +1351,8 @@ function HomeSessions(props: { api: TuiPluginApi } & PinProps) {
|
|
|
1350
1351
|
preventDefault: true,
|
|
1351
1352
|
cmd: () => preview.open(entry),
|
|
1352
1353
|
},
|
|
1353
|
-
{ key: "
|
|
1354
|
-
{ key: "
|
|
1354
|
+
{ key: "ctrl+s", desc: "Pin session", preventDefault: true, cmd: () => props.onTogglePin("sessions", entry.id) },
|
|
1355
|
+
{ key: "ctrl+d", desc: "Pin directory", preventDefault: true, cmd: () => props.onTogglePin("directories", entry.dir) },
|
|
1355
1356
|
],
|
|
1356
1357
|
})
|
|
1357
1358
|
})
|
|
@@ -1806,11 +1807,11 @@ const tui: TuiPlugin = async (api) => {
|
|
|
1806
1807
|
},
|
|
1807
1808
|
{ key: "enter", desc: "Open session", preventDefault: true, cmd: () => choose() },
|
|
1808
1809
|
{ key: "ctrl+p", desc: "Toggle transcript preview", preventDefault: true, cmd: togglePreview },
|
|
1809
|
-
{ key: "
|
|
1810
|
+
{ key: "ctrl+s", desc: "Pin session", preventDefault: true, cmd: () => {
|
|
1810
1811
|
const entry = selectableEntries()[cursor()]
|
|
1811
1812
|
if (entry) onTogglePin("sessions", entry.id)
|
|
1812
1813
|
} },
|
|
1813
|
-
{ key: "
|
|
1814
|
+
{ key: "ctrl+d", desc: "Pin directory", preventDefault: true, cmd: () => {
|
|
1814
1815
|
const entry = selectableEntries()[cursor()]
|
|
1815
1816
|
if (entry) onTogglePin("directories", entry.dir)
|
|
1816
1817
|
} },
|
|
@@ -2025,7 +2026,7 @@ const tui: TuiPlugin = async (api) => {
|
|
|
2025
2026
|
: waitingOnly() && waitingCoverage() === "unavailable"
|
|
2026
2027
|
? "Needs-input data unavailable · turn off the filter"
|
|
2027
2028
|
: pinnedOnly() && pins().sessions.length === 0
|
|
2028
|
-
? "No pinned sessions yet ·
|
|
2029
|
+
? "No pinned sessions yet · ctrl+s pins a session"
|
|
2029
2030
|
: !indexProgress().complete && query().trim()
|
|
2030
2031
|
? "No matches yet · still searching transcripts…"
|
|
2031
2032
|
: scope()
|
|
@@ -2072,7 +2073,7 @@ const tui: TuiPlugin = async (api) => {
|
|
|
2072
2073
|
: "↑↓ move · enter open · ctrl+g project · option+w needs · option+p pinned · ctrl+p preview"}
|
|
2073
2074
|
</text>
|
|
2074
2075
|
<Show when={!pendingDelete()}>
|
|
2075
|
-
<text style={{ fg: api.theme.current.textMuted }}>
|
|
2076
|
+
<text style={{ fg: api.theme.current.textMuted }}>ctrl+s/d pin · ctrl+x delete · ctrl+f fork · esc close</text>
|
|
2076
2077
|
</Show>
|
|
2077
2078
|
</box>
|
|
2078
2079
|
</box>
|