mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
start() reassigns _claudeSessionId to `resumeSessionId || id` on every launch, including the path that re-attaches to a mux session that outlived the restart. A pane whose CLI had moved on via /clear therefore came back pointing the response viewer at its pre-/clear transcript, and because Session.lastSubmitAt lived only in memory, the history correlation had nothing to correct it with until the user happened to type again — observed as hours of the eye showing a conversation the pane had long since left. Persist lastSubmitAt in SessionState, restore it in restoreMuxSessions(), and flush it when the viewer adopts (a /clear emits no completion event, which is the trigger that would otherwise have persisted it). Recovered panes now re-derive their live conversation on the viewer's first poll. Restoring a stale anchor is safe: the resolver already refuses a candidate transcript older than the one the pane is currently on, which is the shape of a respawn into a fresh conversation.
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
/**
|
|
* @fileoverview The pane's last-Enter timestamp must survive a Codeman restart.
|
|
*
|
|
* `start()` resets `claudeSessionId` to the launch id even when re-attaching to
|
|
* a mux session whose CLI has since moved on (a `/clear` before the restart), so
|
|
* `lastSubmitAt` is the response viewer's only anchor for re-deriving the live
|
|
* conversation. If it is not persisted, a recovered pane shows the pre-`/clear`
|
|
* transcript until the user happens to type again — hours, in practice.
|
|
*
|
|
* Port: N/A (no server needed)
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { Session } from '../src/session.js';
|
|
|
|
describe('session submit anchor', () => {
|
|
it('records the pane Enter and carries it into persisted state', () => {
|
|
const session = new Session({ workingDir: '/tmp' });
|
|
expect(session.lastSubmitAt).toBe(0);
|
|
expect(session.toState().lastSubmitAt).toBeUndefined();
|
|
|
|
const before = Date.now();
|
|
session.write('hello\r');
|
|
const after = Date.now();
|
|
|
|
expect(session.lastSubmitAt).toBeGreaterThanOrEqual(before);
|
|
expect(session.lastSubmitAt).toBeLessThanOrEqual(after);
|
|
expect(session.toState().lastSubmitAt).toBe(session.lastSubmitAt);
|
|
});
|
|
|
|
it('leaves the anchor unset for keystrokes that never submit', () => {
|
|
const session = new Session({ workingDir: '/tmp' });
|
|
session.write('hello');
|
|
session.write('\x1b[A'); // arrow-up: history recall, not a submit
|
|
|
|
expect(session.lastSubmitAt).toBe(0);
|
|
expect(session.toState().lastSubmitAt).toBeUndefined();
|
|
});
|
|
|
|
it('restores the anchor from persisted state on boot recovery', () => {
|
|
const submitted = new Session({ workingDir: '/tmp' });
|
|
submitted.write('prompt\r');
|
|
const persisted = submitted.toState();
|
|
|
|
const recovered = new Session({ workingDir: '/tmp', lastSubmitAt: persisted.lastSubmitAt });
|
|
|
|
expect(recovered.lastSubmitAt).toBe(submitted.lastSubmitAt);
|
|
expect(recovered.toState().lastSubmitAt).toBe(submitted.lastSubmitAt);
|
|
});
|
|
|
|
it('starts a pane with no persisted anchor at zero rather than NaN', () => {
|
|
const recovered = new Session({ workingDir: '/tmp', lastSubmitAt: undefined });
|
|
expect(recovered.lastSubmitAt).toBe(0);
|
|
});
|
|
});
|