mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
fix(story-mode): keyboard focus with the file preview, and what round 3 found missing
From review round 3:
- Keyboard: a file opened from a panel or drawer takes focus on the
preview's close button, Tab cycles inside the preview while it is open,
and Escape gives focus back to what opened it. Before, Tab walked out of
the overlay to the session tabs behind it. A panel rebuilt under focus (a
new turn, a fold into a loop) hands focus to its replacement.
- A commit at the end of a test one-liner is no longer lost: commits and
pushes are counted whatever intent wins, and the check says "The tests
pass, and Claude saves the work." (plus "1 commit" in the Director's cut).
- Files written by `printf/echo ... > f`, and files Claude Code says a
command changed ("This command modified 1 file you've previously read"),
count as changed files.
- A compaction says what it did ("automatic · 46.9k → 3.6k tokens · 24 s");
an automatic one reads "Claude's memory is full", and its panel opens.
- ToolSearch is looking around, not a terminal panel; EnterPlanMode reads
"Claude switches to plan mode"; the plan drawer shows the plan you
approved ("You approved the plan.").
- A finished newest page is checked every 15 s (a 304 when unchanged): a
local command like /compact raises no hook.
- Boards draw the real number of dots (5 tests, 5 dots); captions leave
here-document markers out.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
dae6a52daa
commit
757e14e991
5 files changed
+266
-30
No files matched your search
@@ -57,7 +57,7 @@ function boot(initial: unknown) {
|
||||
`<!doctype html><body>
|
||||
<button class="btn-story-header btn-story-header--hidden"></button>
|
||||
<button id="sessionOptionsStoryBtn" hidden></button>
|
||||
<div class="file-preview-overlay" id="filePreviewOverlay"></div>
|
||||
<div class="file-preview-overlay" id="filePreviewOverlay"><button id="pvOther">Edit</button><button title="Close">×</button></div>
|
||||
<div class="folder-grid-overlay" id="folderGridOverlay"></div>
|
||||
</body>`,
|
||||
{ url: 'http://localhost/', runScripts: 'outside-only', pretendToBeVisual: true }
|
||||
@@ -563,7 +563,12 @@ describe('story-mode.js overlay', () => {
|
||||
boot(
|
||||
storyOf([
|
||||
prompt('look'),
|
||||
...bash('b1', 'ls /work/demo-shop && ls -R /work/demo-shop/clone', 'List the clone', 'clone/src:\nkebab.py\n\nnotes.md'),
|
||||
...bash(
|
||||
'b1',
|
||||
'ls /work/demo-shop && ls -R /work/demo-shop/clone',
|
||||
'List the clone',
|
||||
'clone/src:\nkebab.py\n\nnotes.md'
|
||||
),
|
||||
end(1000),
|
||||
])
|
||||
);
|
||||
@@ -579,6 +584,30 @@ describe('story-mode.js overlay', () => {
|
||||
expect(app.openFilePreview).toHaveBeenLastCalledWith('/work/demo-shop/notes.md', SID);
|
||||
});
|
||||
|
||||
it('a file opened from Story Mode takes keyboard focus, keeps Tab inside, and gives focus back on Escape', async () => {
|
||||
boot(storyOf(demoTurn()));
|
||||
const overlay = () => $('#filePreviewOverlay')!;
|
||||
app.openFilePreview = vi.fn(() => overlay().classList.add('visible'));
|
||||
app.openStoryMode(SID);
|
||||
await settle();
|
||||
sizeAndDraw();
|
||||
$('.story-panel[data-kind="loop"] .story-hit')!.click();
|
||||
const open = $$('.story-drawer-body .story-btn-s').find((b) => b.textContent === 'Open file')!;
|
||||
open.focus();
|
||||
open.click();
|
||||
await new Promise((r) => win.requestAnimationFrame(() => r(null)));
|
||||
const close = $('#filePreviewOverlay button[title="Close"]')!;
|
||||
expect(win.document.activeElement).toBe(close);
|
||||
for (const n of $$('#filePreviewOverlay button'))
|
||||
Object.defineProperty(n, 'offsetParent', { get: () => win.document.body });
|
||||
press('Tab');
|
||||
expect(overlay().contains(win.document.activeElement)).toBe(true);
|
||||
press('Escape');
|
||||
expect(app.closeFilePreview).toHaveBeenCalled();
|
||||
expect(win.document.activeElement).toBe(open);
|
||||
expect($('#storyOverlay')!.hidden).toBe(false);
|
||||
});
|
||||
|
||||
it('turning the setting off hides the button and closes an open story', async () => {
|
||||
boot(storyOf(demoTurn()));
|
||||
app.openStoryMode(SID);
|
||||
|
||||
+49
-1
@@ -740,7 +740,55 @@ describe('buildStoryPage', () => {
|
||||
expect(ev.headline).toBe('node --test');
|
||||
expect(page.filesChanged).toBe(1);
|
||||
const check = page.panels.find((p) => p.kind === 'check')!;
|
||||
expect(check.caption.director).toMatch(/^node --test → ok · 3 passed · writes 1 file$/);
|
||||
expect(check.caption.director).toBe('node --test → ok · 3 passed · writes 1 file · 1 commit');
|
||||
// The commit at the end of the test one-liner is not lost: the check says it saved.
|
||||
expect(check.caption.story).toBe('The tests pass, and Claude saves the work.');
|
||||
});
|
||||
|
||||
it('one-line printf/echo writes and the files Claude Code says a command changed are edits', () => {
|
||||
expect(
|
||||
heredocWrites('printf \'name = "x"\\nversion = 1\\n\' > pyproject.toml && ls', '/w').map((d) => [d.file, d.added])
|
||||
).toEqual([['/w/pyproject.toml', 2]]);
|
||||
expect(heredocWrites('echo hi > /tmp/x.txt; echo "a" >> notes.md', '/w').map((d) => d.file)).toEqual([
|
||||
'/w/notes.md',
|
||||
]);
|
||||
const lines = [
|
||||
prompt('rewrite'),
|
||||
...bash(
|
||||
'b1',
|
||||
"python3 - <<'EOF'\nopen('t.py','w').write('x')\nEOF",
|
||||
'Rewrite the test file',
|
||||
"done\n[This command modified 1 file you've previously read: test_a.py. Call Read before editing.]"
|
||||
),
|
||||
end(1000),
|
||||
];
|
||||
(lines[2] as { cwd?: string }).cwd = '/w';
|
||||
const page = pageOf(lines);
|
||||
expect(page.events[1].edits?.map((d) => d.file)).toEqual(['/w/test_a.py']);
|
||||
expect(page.filesChanged).toBe(1);
|
||||
});
|
||||
|
||||
it('a compaction says what it did; ToolSearch is looking; EnterPlanMode says plan mode', () => {
|
||||
const page = pageOf([
|
||||
prompt('go'),
|
||||
tool('t1', 'ToolSearch', { query: 'select:EnterPlanMode,ExitPlanMode', max_results: 2 }),
|
||||
result('t1', 'ok'),
|
||||
tool('p1', 'EnterPlanMode', {}),
|
||||
result('p1', 'Entered plan mode.'),
|
||||
{
|
||||
type: 'system',
|
||||
subtype: 'compact_boundary',
|
||||
timestamp: '2026-10-18T09:45:00.000Z',
|
||||
compactMetadata: { trigger: 'auto', preTokens: 46928, postTokens: 3623, durationMs: 24014 },
|
||||
},
|
||||
end(1000),
|
||||
]);
|
||||
expect(page.panels.map((p) => p.kind)).toEqual(['mission', 'investigate', 'plan', 'compact', 'complete']);
|
||||
expect(page.panels[2].caption.story).toBe('Claude switches to plan mode to think it through.');
|
||||
expect(page.panels[3].caption).toEqual({
|
||||
story: "Claude's memory is full: it packs up its notes.",
|
||||
director: 'automatic · 46.9k → 3.6k tokens · 24 s',
|
||||
});
|
||||
});
|
||||
|
||||
it('heredocWrites skips scratch, stdin bodies and relative paths after a cd', () => {
|
||||
|
||||
Reference in new issue
Block a user