backstep 0.1.6 → 0.1.8
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.
|
@@ -132,6 +132,8 @@ test('with nothing to undo the band still holds the key, and a press says there
|
|
|
132
132
|
|
|
133
133
|
const ui = await $.ui.mount({ ...BAND, surface: 'terminal' })
|
|
134
134
|
expect((await ui.find({ type: 'Text', text: /nothing to undo yet/ }))).toBeTruthy()
|
|
135
|
+
// The mark: the front triangle in the terminal's own color, the one behind in the logo's pink.
|
|
136
|
+
expect((await ui.find({ type: 'Text', text: '◀', props: { color: '#ee7bb6' } }))).toBeTruthy()
|
|
135
137
|
await ui.press({ key: 'undo' })
|
|
136
138
|
expect(toasts[0]).toContain('Nothing to undo yet')
|
|
137
139
|
await ui.unmount()
|
package/mod/hooks/register.tsx
CHANGED
|
@@ -13,6 +13,12 @@ const turns = atom({ plugin: 'backstep', key: 'turns' } as const, [] as Turn[])
|
|
|
13
13
|
const rewind = atom({ plugin: 'backstep', key: 'rewind' } as const, null as Rewind | null)
|
|
14
14
|
const note = atom({ plugin: 'backstep', key: 'note' } as const, null as string | null)
|
|
15
15
|
const armed = atom({ plugin: 'backstep', key: 'armed' } as const, null as number | null)
|
|
16
|
+
// The keys bound to undo, read as a session starts and when the person changes
|
|
17
|
+
// them. Kept in state, not a variable, so the band redraws with the new key.
|
|
18
|
+
const keys = atom({ plugin: 'backstep', key: 'keys' } as const, ['ctrl+u'] as string[])
|
|
19
|
+
// Backstep's mark, as on the logo: the front rewind triangle in the terminal's
|
|
20
|
+
// own black or white, the one behind it in the logo's pink.
|
|
21
|
+
const PINK = '#ee7bb6'
|
|
16
22
|
// How long the "press again to undo" question waits before it lets go.
|
|
17
23
|
const CONFIRM_MS = 5000
|
|
18
24
|
|
|
@@ -186,8 +192,9 @@ async function undoLastTurn($: EngineInterface) {
|
|
|
186
192
|
|
|
187
193
|
if (done) {
|
|
188
194
|
const list = items.filter((i) => i.ok).map((i) => `- ${i.label}`).join('\n')
|
|
195
|
+
const key = await keysLabel($)
|
|
189
196
|
await update($, note, () =>
|
|
190
|
-
`[Backstep] The user pressed undo (${
|
|
197
|
+
`[Backstep] The user pressed undo (${key}) on your previous turn. These changes were reverted:\n${list}\n` +
|
|
191
198
|
(lost ? `${plural(lost, 'action')} from that turn couldn't be undone (they reached outside this machine with no way back, or the project was too big to snapshot).\n` : '') +
|
|
192
199
|
`Treat them as not having happened, re-read files before editing them, and don't redo them unless asked.`,
|
|
193
200
|
)
|
|
@@ -275,10 +282,8 @@ type ShellPlan = { kind: 'read' | 'write' | 'outbound'; reason?: string; id?: st
|
|
|
275
282
|
|
|
276
283
|
// Set from the plugin's options each time the module registers.
|
|
277
284
|
let cli = 'backstep'
|
|
278
|
-
// The keys bound to undo, read as a session starts and when the person changes them.
|
|
279
|
-
let undoKeys = ['ctrl+u']
|
|
280
285
|
const keyLabel = (k: string) => (k.startsWith('cmd+') ? `⌘${k.slice(4).toUpperCase()}` : k)
|
|
281
|
-
const keysLabel = () =>
|
|
286
|
+
const keysLabel = async ($: EngineInterface) => (await read($, keys)).map(keyLabel).join(' / ')
|
|
282
287
|
|
|
283
288
|
function backstep(args: string[]): string[] {
|
|
284
289
|
return cli === 'backstep' ? ['backstep', ...args] : ['node', '--disable-warning=ExperimentalWarning', cli, ...args]
|
|
@@ -441,10 +446,12 @@ async function changeKey($: EngineInterface, key: string): Promise<string> {
|
|
|
441
446
|
r = { ok: false, detail: ran.stderr.trim() || 'it failed' }
|
|
442
447
|
}
|
|
443
448
|
if (!r.ok || !r.key) return `Couldn't change the undo key: ${r.detail ?? 'it failed'}`
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
449
|
+
const now = r.keys?.length ? r.keys : [r.key]
|
|
450
|
+
await update($, keys, () => now)
|
|
451
|
+
const label = await keysLabel($)
|
|
452
|
+
$.ui.toast(`Backstep: undo is now ${label}.`)
|
|
453
|
+
const was = r.previous && !now.includes(r.previous) ? ` (it was ${keyLabel(r.previous)})` : ''
|
|
454
|
+
return `Undo is now ${label}${was}. It works right away.`
|
|
448
455
|
}
|
|
449
456
|
|
|
450
457
|
const CONNECT = 'mcp__backstep__connect'
|
|
@@ -499,15 +506,15 @@ export const register: Register = (on, options) => {
|
|
|
499
506
|
await $.tool.register(CONNECT_TOOL)
|
|
500
507
|
await $.tool.register(KEY_TOOL)
|
|
501
508
|
const r = await next(e)
|
|
502
|
-
const
|
|
509
|
+
const boundOut = await $.process.run(backstep(['key', '--json'])).catch(() => null)
|
|
503
510
|
try {
|
|
504
|
-
const
|
|
505
|
-
if (
|
|
511
|
+
const bound = (JSON.parse(boundOut?.stdout || '{}') as { keys?: string[] }).keys
|
|
512
|
+
if (bound?.length) await update($, keys, () => bound)
|
|
506
513
|
} catch {
|
|
507
514
|
// keep the default
|
|
508
515
|
}
|
|
509
516
|
// Said first thing, so nobody has to wonder whether it's running.
|
|
510
|
-
$.ui.log(`✓ Backstep connected · press ${keysLabel()} to undo Claude's last turn`)
|
|
517
|
+
$.ui.log(`✓ Backstep connected · press ${await keysLabel($)} to undo Claude's last turn`)
|
|
511
518
|
// In the background: a session never waits on either.
|
|
512
519
|
void wrapNewServers($, e.cwd)
|
|
513
520
|
// One question at a time: Supabase first, then any update.
|
|
@@ -592,15 +599,17 @@ export const register: Register = (on, options) => {
|
|
|
592
599
|
if (r?.phase === 'running') {
|
|
593
600
|
return (
|
|
594
601
|
<Box>
|
|
595
|
-
<Text
|
|
596
|
-
<Text color=
|
|
602
|
+
<Text bold>◀</Text>
|
|
603
|
+
<Text color={PINK} bold>◀</Text>
|
|
604
|
+
<Text bold> Rewinding </Text>
|
|
605
|
+
<Text>{bar(r.frame)}</Text>
|
|
597
606
|
<Text dimColor> undoing the last turn…</Text>
|
|
598
607
|
</Box>
|
|
599
608
|
)
|
|
600
609
|
}
|
|
601
610
|
|
|
602
611
|
if (r) {
|
|
603
|
-
const tone = r.phase === 'failed' ? 'error' : r.phase === 'empty' ? 'warning' :
|
|
612
|
+
const tone = r.phase === 'failed' ? 'error' : r.phase === 'empty' ? 'warning' : undefined
|
|
604
613
|
const glyph = r.phase === 'failed' ? '✗' : r.phase === 'empty' ? '!' : '✓'
|
|
605
614
|
const visible = r.items.slice(0, Math.min(r.shown, 6))
|
|
606
615
|
const more = r.items.length - 6
|
|
@@ -612,7 +621,7 @@ export const register: Register = (on, options) => {
|
|
|
612
621
|
{visible.map((i, n) => (
|
|
613
622
|
<Text key={`i${n}`} color={i.ok ? undefined : 'error'} dimColor={i.ok}>
|
|
614
623
|
{' '}
|
|
615
|
-
{i.ok ? '
|
|
624
|
+
{i.ok ? '◀ ' : '✗ '}
|
|
616
625
|
{i.label}
|
|
617
626
|
</Text>
|
|
618
627
|
))}
|
|
@@ -622,13 +631,16 @@ export const register: Register = (on, options) => {
|
|
|
622
631
|
}
|
|
623
632
|
|
|
624
633
|
const target = (await read($, turns)).findLast(hasWork)
|
|
634
|
+
const label = (await read($, keys)).map(keyLabel).join(' / ')
|
|
625
635
|
if (target && (await read($, armed)) !== null) {
|
|
626
636
|
return (
|
|
627
637
|
<Box>
|
|
628
|
-
<Text
|
|
638
|
+
<Text bold>◀</Text>
|
|
639
|
+
<Text color={PINK} bold>◀</Text>
|
|
640
|
+
<Text color="warning" bold> Undo Claude's last turn? </Text>
|
|
629
641
|
<Text dimColor>{summary(target)} </Text>
|
|
630
642
|
<Button key="undo" action={UNDO_ACTION} plain onPress={() => void pressUndo($)}>
|
|
631
|
-
{`${
|
|
643
|
+
{`${label} again to undo`}
|
|
632
644
|
</Button>
|
|
633
645
|
<Text dimColor> · </Text>
|
|
634
646
|
<Button key="cancel" plain dimColor onPress={() => void update($, armed, () => null)}>
|
|
@@ -643,10 +655,12 @@ export const register: Register = (on, options) => {
|
|
|
643
655
|
// with nothing to undo, a press says so rather than doing nothing.
|
|
644
656
|
return (
|
|
645
657
|
<Box>
|
|
646
|
-
<Text
|
|
658
|
+
<Text bold>◀</Text>
|
|
659
|
+
<Text color={PINK} bold>◀</Text>
|
|
660
|
+
<Text> </Text>
|
|
647
661
|
<Text dimColor>{target ? `Last turn: ${summary(target)} ` : 'Backstep is on · nothing to undo yet '}</Text>
|
|
648
662
|
<Button key="undo" action={UNDO_ACTION} plain dimColor onPress={() => void pressUndo($)}>
|
|
649
|
-
{`${
|
|
663
|
+
{`${label} to undo`}
|
|
650
664
|
</Button>
|
|
651
665
|
</Box>
|
|
652
666
|
)
|
package/mod/types/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type Rewind = {
|
|
|
26
26
|
declare module 'claude-code' {
|
|
27
27
|
interface PluginState {
|
|
28
28
|
// armed: when the undo key was first pressed; a second press within a few seconds undoes.
|
|
29
|
-
|
|
29
|
+
// keys: the keys bound to undo, kept here so the band redraws when they change.
|
|
30
|
+
backstep: { turns: Turn[]; rewind: Rewind | null; note: string | null; armed: number | null; keys: string[] }
|
|
30
31
|
}
|
|
31
32
|
}
|