mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-15 02:49:33 +01:00
feat(story-mode): draw a Claude session's turns as comic pages, live
Story Mode is an opt-in header button (showStoryButton, per device, default off, kept off phones) that opens the active Claude Code session as comic pages: one page per prompt, panels for reading code, planning, helpers, edits, tests (POW/KRAK), questions and saving, each with a plain story caption and a Director's cut caption naming the tools. Files open in the existing file preview, the code page opens the exact diff, a test board opens the output, and a running turn redraws as Claude works. Server: GET /api/sessions/:id/story (experimental) reads a bounded tail of the session's own transcript. A pure core (src/story-mode.ts) reduces lines, joins tool calls with their results, splits turns at prompts and turn_duration, and groups events into at most 12 panels; Bash calls are sorted by what the command does. The reader (src/story-reader.ts) caches reduced entries and reads only appended bytes while the file grows. Frontend: story-mode.js (canvas frame engine, live refresh from transcript and hook SSE events plus a poll while a turn runs, details drawer, the wire) and story-mode.css, with two self-hosted OFL comic fonts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
0ab71d61d1
commit
2653db10b5
27 files changed
+5746
-6
No files matched your search
@@ -22,7 +22,7 @@
|
||||
* recoverable pane into the collision this module exists to prevent.
|
||||
*
|
||||
* @dependencies none
|
||||
* @consumedby session (relaunch resume pinning)
|
||||
* @consumedby session (relaunch resume pinning), web/routes/story-routes (Story Mode transcript lookup)
|
||||
*
|
||||
* @module utils/claude-transcript
|
||||
*/
|
||||
@@ -47,6 +47,31 @@ export function claudeProjectsDir(configDir?: string): string {
|
||||
return join(configDir || fromEnv || join(homedir(), '.claude'), 'projects');
|
||||
}
|
||||
|
||||
/**
|
||||
* Path of the transcript for `conversationId` under any project directory, or null. Unlike
|
||||
* {@link claudeTranscriptExists} it answers with the file (Story Mode reads it), so the id is
|
||||
* checked against a strict charset before it is joined into a path, and only a regular file counts.
|
||||
*/
|
||||
export async function findClaudeTranscriptPath(conversationId: string, configDir?: string): Promise<string | null> {
|
||||
if (!/^[a-zA-Z0-9._-]+$/.test(conversationId) || conversationId.startsWith('.')) return null;
|
||||
const projectsDir = claudeProjectsDir(configDir);
|
||||
let projectDirs: string[];
|
||||
try {
|
||||
projectDirs = await readdir(projectsDir);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
for (const projectDir of projectDirs) {
|
||||
const candidate = join(projectsDir, projectDir, `${conversationId}.jsonl`);
|
||||
try {
|
||||
if ((await stat(candidate)).isFile()) return candidate;
|
||||
} catch {
|
||||
// Not in this project directory; keep looking.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when a transcript for `conversationId` exists under any project
|
||||
* directory. Returns false for a missing projects dir or an unreadable one,
|
||||
|
||||
Reference in new issue
Block a user