From e20e3ea022c3422d3a0cbe1a08a939a8eb1b7ca0 Mon Sep 17 00:00:00 2001 From: Naved Rangwala Date: Thu, 23 Apr 2026 14:04:37 -0700 Subject: [PATCH 1/9] fix emoji/wide-character rendering with @xterm/addon-unicode-graphemes xterm.js defaults to Unicode 6.1 width tables which treat emoji as single-width, causing all subsequent characters on the line to shift. The unicode-graphemes addon uses UAX #29 grapheme clustering (Unicode 15) for correct cell-width calculation. Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 7 +++++++ package.json | 1 + public/index.html | 1 + public/terminal-manager.js | 2 ++ 4 files changed, 11 insertions(+) diff --git a/package-lock.json b/package-lock.json index 59847ea..71fd349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@xterm/addon-fit": "^0.11.0", "@xterm/addon-search": "^0.16.0", + "@xterm/addon-unicode-graphemes": "^0.4.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0", @@ -2453,6 +2454,12 @@ "integrity": "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==", "license": "MIT" }, + "node_modules/@xterm/addon-unicode-graphemes": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-unicode-graphemes/-/addon-unicode-graphemes-0.4.0.tgz", + "integrity": "sha512-9+/CqwbKcnlkJU4d3wIgO+wjsL8f6vyz+UwUWLu6nADQz8Gr8ONqGCJfdDjIdI+yYZLABQqQy47FzEM6AWELjw==", + "license": "MIT" + }, "node_modules/@xterm/addon-web-links": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz", diff --git a/package.json b/package.json index ecc248a..db13513 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "dependencies": { "@xterm/addon-fit": "^0.11.0", "@xterm/addon-search": "^0.16.0", + "@xterm/addon-unicode-graphemes": "^0.4.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0", diff --git a/public/index.html b/public/index.html index bcbc2f6..8484857 100644 --- a/public/index.html +++ b/public/index.html @@ -101,6 +101,7 @@ + diff --git a/public/terminal-manager.js b/public/terminal-manager.js index 5738184..d533500 100644 --- a/public/terminal-manager.js +++ b/public/terminal-manager.js @@ -202,6 +202,8 @@ function createTerminalEntry(session) { })); const searchAddon = new SearchAddon.SearchAddon(); terminal.loadAddon(searchAddon); + terminal.loadAddon(new UnicodeGraphemesAddon.UnicodeGraphemesAddon()); + terminal.unicode.activeVersion = '15'; terminal.open(container); container.style.backgroundColor = TERMINAL_THEME.background; From eec14619f3bd5784ba8d23dfc57f0bca52c2d976 Mon Sep 17 00:00:00 2001 From: Ali Basiri Date: Sun, 26 Apr 2026 15:01:58 -0700 Subject: [PATCH 2/9] v0.0.26: WebGL terminal renderer and emoji rendering fix Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71fd349..106b568 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "switchboard", - "version": "0.0.25", + "version": "0.0.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "switchboard", - "version": "0.0.25", + "version": "0.0.26", "hasInstallScript": true, "dependencies": { "@xterm/addon-fit": "^0.11.0", diff --git a/package.json b/package.json index db13513..be4e08e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "switchboard", - "version": "0.0.25", + "version": "0.0.26", "private": true, "description": "A desktop app for browsing, searching, and managing CLI coding sessions", "author": "Doctly ", From 7cc3816a7e889df2cff421ea00f370ba4ef03e62 Mon Sep 17 00:00:00 2001 From: Ali Basiri Date: Mon, 27 Apr 2026 09:27:18 -0700 Subject: [PATCH 3/9] v0.0.27: WebGL terminal renderer, emoji fix, WebGL fallback logging Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 106b568..c39871e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "switchboard", - "version": "0.0.26", + "version": "0.0.27", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "switchboard", - "version": "0.0.26", + "version": "0.0.27", "hasInstallScript": true, "dependencies": { "@xterm/addon-fit": "^0.11.0", diff --git a/package.json b/package.json index be4e08e..20045ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "switchboard", - "version": "0.0.26", + "version": "0.0.27", "private": true, "description": "A desktop app for browsing, searching, and managing CLI coding sessions", "author": "Doctly ", From f4bb48ddc7b141169ee8556cce62c541062bbd1a Mon Sep 17 00:00:00 2001 From: Ali Basiri Date: Mon, 27 Apr 2026 09:50:31 -0700 Subject: [PATCH 4/9] ci: split build and publish into separate jobs to fix multi-platform race All 4 matrix jobs were running electron-builder with --publish always in parallel, racing to find-or-create the draft release for the tag. Each non-winner created its own duplicate draft, scattering mac/win assets (latest-mac.yml, .dmg, .zip, .exe) across drafts that no one publishes. Result on v0.0.27: the "Latest" release shipped with only Linux assets, and electron-updater 404s on latest-mac.yml. Now matrix jobs always run --publish never and upload artifacts (now including latest*.yml and .blockmap files needed by electron-updater). A single publish job, gated on tag push, downloads all artifacts and creates one draft release with gh release create. One publisher, one release, no race. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build.yml | 40 +++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f916f01..70dc697 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,11 +55,9 @@ jobs: - name: Bundle CodeMirror run: npm run bundle:codemirror - - name: Build (release) - if: startsWith(github.ref, 'refs/tags/v') - run: npx electron-builder --${{ matrix.platform }} --publish always + - name: Build + run: npx electron-builder --${{ matrix.platform }} --publish never env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} CSC_LINK: ${{ matrix.platform == 'mac' && secrets.CSC_LINK || '' }} CSC_KEY_PASSWORD: ${{ matrix.platform == 'mac' && secrets.CSC_KEY_PASSWORD || '' }} CSC_IDENTITY_AUTO_DISCOVERY: ${{ matrix.platform == 'mac' }} @@ -67,21 +65,41 @@ jobs: APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} - - name: Build (artifacts only) - if: "!startsWith(github.ref, 'refs/tags/v')" - run: npx electron-builder --${{ matrix.platform }} --publish never - env: - CSC_IDENTITY_AUTO_DISCOVERY: false - - name: Upload artifacts - if: "!startsWith(github.ref, 'refs/tags/v')" uses: actions/upload-artifact@v4 with: name: dist-${{ matrix.platform }}${{ matrix.arch && format('-{0}', matrix.arch) || '' }} path: | dist/*.dmg + dist/*.dmg.blockmap dist/*.zip + dist/*.zip.blockmap dist/*.exe + dist/*.exe.blockmap dist/*.AppImage dist/*.deb + dist/latest*.yml if-no-files-found: ignore + + publish: + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Create GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${GITHUB_REF_NAME}" \ + --draft \ + --title "${GITHUB_REF_NAME#v}" \ + --notes "" \ + dist/* From d7d4af5b4f2caa6222a7aae2374bd3ccce33a3d2 Mon Sep 17 00:00:00 2001 From: Ali Basiri Date: Mon, 27 Apr 2026 10:06:24 -0700 Subject: [PATCH 5/9] v0.0.28: republish of 0.0.27 with all mac/win/linux assets (CI fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.0.27's release was missing the macOS and Windows artifacts due to a race in the publish pipeline — all 4 matrix jobs ran electron-builder with --publish always in parallel, each creating its own duplicate draft release. The "Latest" release that won the race only contained the Linux assets, breaking electron-updater for mac/win clients (404 on latest-mac.yml). No app-code changes from 0.0.27. The pipeline is now split: the matrix builds with --publish never and a single follow-up job creates one draft release with all artifacts. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c39871e..d79f444 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "switchboard", - "version": "0.0.27", + "version": "0.0.28", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "switchboard", - "version": "0.0.27", + "version": "0.0.28", "hasInstallScript": true, "dependencies": { "@xterm/addon-fit": "^0.11.0", diff --git a/package.json b/package.json index 20045ac..218593d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "switchboard", - "version": "0.0.27", + "version": "0.0.28", "private": true, "description": "A desktop app for browsing, searching, and managing CLI coding sessions", "author": "Doctly ", From 23303dc3f1e5877ad894b4f5dcd3640ea96427b1 Mon Sep 17 00:00:00 2001 From: Naved Rangwala Date: Mon, 4 May 2026 07:49:45 -0700 Subject: [PATCH 6/9] disable cursor blink to reduce idle terminal CPU usage (#36) 5 idle terminals with cursorBlink: true cause ~28% CPU from constant WebGL repaint cycles. With blink disabled, idle terminals drop to near-zero. Co-authored-by: Claude Opus 4.6 (1M context) --- public/terminal-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/terminal-manager.js b/public/terminal-manager.js index d533500..6863b22 100644 --- a/public/terminal-manager.js +++ b/public/terminal-manager.js @@ -175,7 +175,7 @@ function createTerminalEntry(session) { fontSize: 12, fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', Menlo, monospace", theme: TERMINAL_THEME, - cursorBlink: true, + cursorBlink: false, scrollback: 10000, convertEol: true, allowProposedApi: true, From 560e4c230b5490fea12369590cdf23eb879fec1a Mon Sep 17 00:00:00 2001 From: Ali B Date: Mon, 4 May 2026 12:06:56 -0700 Subject: [PATCH 7/9] fix: match Claude CLI's project-folder encoding to prevent duplicate sidebar entries (#41) Switchboard's folder-name regex only replaced `/` and `_`, while the Claude CLI replaces every non-alphanumeric character (and applies a 200-char cap with a hash suffix on overflow). For project paths containing spaces, dots, backslashes, colons, etc., the two sides produced different folder names under ~/.claude/projects/, surfacing as undismissable duplicate projects in the sidebar. - Add encode-project-path.js mirroring the CLI's algorithm exactly (reverse-engineered from claude 2.1.126: /[^a-zA-Z0-9]/g, 200-char cap, (h<<5)-h+c|0 hash suffix on overflow). Deterministic and pure. - Mirror the encoder in public/utils.js for the renderer. - Route all 10 call sites through encodeProjectPath(). - Fix session-cache.js phantom-project bug: only insert a project entry after the archive filter passes, so folders whose sessions are all archived don't appear as empty undismissable projects in the sidebar. Fixes #38. Supersedes #37 (also covers Windows drive paths). Co-authored-by: Claude Opus 4.7 (1M context) --- encode-project-path.js | 14 ++++++++++++++ main.js | 7 ++++--- public/app.js | 4 ++-- public/dialogs.js | 4 ++-- public/utils.js | 12 ++++++++++++ schedule-ipc.js | 5 +++-- session-cache.js | 14 +++++++++----- 7 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 encode-project-path.js diff --git a/encode-project-path.js b/encode-project-path.js new file mode 100644 index 0000000..5f1ce44 --- /dev/null +++ b/encode-project-path.js @@ -0,0 +1,14 @@ +// Mirror Claude CLI's project-folder naming so Switchboard-created folders +// match the ones the CLI writes for the same project path. +// Reverse-engineered from claude CLI 2.1.126. +function encodeProjectPath(projectPath) { + const sanitized = projectPath.replace(/[^a-zA-Z0-9]/g, '-'); + if (sanitized.length <= 200) return sanitized; + let h = 0; + for (let i = 0; i < projectPath.length; i++) { + h = (h << 5) - h + projectPath.charCodeAt(i) | 0; + } + return sanitized.slice(0, 200) + '-' + Math.abs(h).toString(36); +} + +module.exports = { encodeProjectPath }; diff --git a/main.js b/main.js index a24a1b2..7799be8 100644 --- a/main.js +++ b/main.js @@ -28,6 +28,7 @@ const cleanPtyEnv = Object.fromEntries( // Shell profiles → shell-profiles.js const { discoverShellProfiles, getShellProfiles, resolveShell, isWindows, isWslShell, windowsToWslPath, shellArgs } = require('./shell-profiles'); const { startScheduler } = require('./schedule-runner'); +const { encodeProjectPath } = require('./encode-project-path'); @@ -293,7 +294,7 @@ ipcMain.handle('add-project', (_event, projectPath) => { } // Create the corresponding folder in ~/.claude/projects/ so it persists - const folder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(projectPath); const folderPath = path.join(PROJECTS_DIR, folder); if (!fs.existsSync(folderPath)) { fs.mkdirSync(folderPath, { recursive: true }); @@ -329,7 +330,7 @@ ipcMain.handle('remove-project', (_event, projectPath) => { setSetting('global', global); // Clean up DB cache and search index for this folder - const folder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(projectPath); deleteCachedFolder(folder); deleteSearchFolder(folder); deleteSetting('project:' + projectPath); @@ -981,7 +982,7 @@ ipcMain.handle('open-terminal', async (_event, sessionId, projectPath, isNew, se if (!isPlainTerminal) { // Snapshot existing .jsonl files before spawning (for new session + fork/plan detection) - projectFolder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + projectFolder = encodeProjectPath(projectPath); const claudeProjectDir = path.join(PROJECTS_DIR, projectFolder); if (fs.existsSync(claudeProjectDir)) { try { diff --git a/public/app.js b/public/app.js index 9875965..7631359 100644 --- a/public/app.js +++ b/public/app.js @@ -670,7 +670,7 @@ async function loadProjects({ resort = false } = {}) { const activeTerminals = await window.api.getActiveTerminals(); for (const { sessionId, projectPath } of activeTerminals) { if (pendingSessions.has(sessionId)) continue; // already tracked - const folder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(projectPath); // Find the session object already injected by the backend let session; for (const proj of cachedAllProjects) { @@ -709,7 +709,7 @@ async function launchNewSession(project, sessionOptions) { }; // Track as pending (no .jsonl yet) - const folder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(projectPath); pendingSessions.set(sessionId, { session, projectPath, folder }); // Inject into cached project data so it appears in sidebar immediately diff --git a/public/dialogs.js b/public/dialogs.js index dab5b16..836ce2d 100644 --- a/public/dialogs.js +++ b/public/dialogs.js @@ -49,7 +49,7 @@ async function launchScheduleCreator(project) { }; // Inject into sidebar - const folder = project.projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(project.projectPath); pendingSessions.set(result.sessionId, { session, projectPath: project.projectPath, folder }); sessionMap.set(result.sessionId, session); for (const projList of [cachedProjects, cachedAllProjects]) { @@ -141,7 +141,7 @@ async function launchTerminalSession(project) { }; // Track as pending - const folder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(projectPath); pendingSessions.set(sessionId, { session, projectPath, folder }); // Inject into cached project data diff --git a/public/utils.js b/public/utils.js index 8702928..e76e641 100644 --- a/public/utils.js +++ b/public/utils.js @@ -1,5 +1,17 @@ // --- Utility functions (shared across renderer modules) --- +// Mirror Claude CLI's project-folder naming. Must stay in sync with +// encode-project-path.js (main process). Reverse-engineered from claude CLI 2.1.126. +function encodeProjectPath(projectPath) { + const sanitized = projectPath.replace(/[^a-zA-Z0-9]/g, '-'); + if (sanitized.length <= 200) return sanitized; + let h = 0; + for (let i = 0; i < projectPath.length; i++) { + h = (h << 5) - h + projectPath.charCodeAt(i) | 0; + } + return sanitized.slice(0, 200) + '-' + Math.abs(h).toString(36); +} + function cleanDisplayName(name) { if (!name) return name; const prefix = 'Implement the following plan:'; diff --git a/schedule-ipc.js b/schedule-ipc.js index 7d83e66..cd3d858 100644 --- a/schedule-ipc.js +++ b/schedule-ipc.js @@ -4,6 +4,7 @@ const path = require('path'); const fs = require('fs'); const os = require('os'); const crypto = require('crypto'); +const { encodeProjectPath } = require('./encode-project-path'); const CLAUDE_DIR = path.join(os.homedir(), '.claude'); const PROJECTS_DIR = path.join(CLAUDE_DIR, 'projects'); @@ -144,7 +145,7 @@ function init(log, runCommand) { const sessionId = crypto.randomUUID(); const msgId = crypto.randomUUID(); const timestamp = new Date().toISOString(); - const folder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(projectPath); const claudeProjectDir = path.join(PROJECTS_DIR, folder); fs.mkdirSync(claudeProjectDir, { recursive: true }); @@ -191,7 +192,7 @@ function init(log, runCommand) { const dotClaudeDir = path.dirname(commandsDir); const projectPath = path.dirname(dotClaudeDir); - const folder = projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(projectPath); const schedule = { file: path.basename(filePath), filePath, projectPath, folder, diff --git a/session-cache.js b/session-cache.js index 4a306de..342eaff 100644 --- a/session-cache.js +++ b/session-cache.js @@ -4,6 +4,7 @@ const { Worker } = require('worker_threads'); const { getFolderIndexMtimeMs } = require('./folder-index-state'); const { deriveProjectPath } = require('./derive-project-path'); const { readSessionFile } = require('./read-session-file'); +const { encodeProjectPath } = require('./encode-project-path'); /** * Session cache module. @@ -170,13 +171,13 @@ function buildProjectsFromCache(showArchived) { const global = getSetting('global') || {}; const hiddenProjects = new Set(global.hiddenProjects || []); - // Group by folder (worktree sessions appear as separate projects) + // Group by folder (worktree sessions appear as separate projects). + // Only insert a project entry once we have a session that survives the + // archive filter — otherwise folders whose sessions are all archived would + // appear in the sidebar as undismissable phantom entries. const projectMap = new Map(); for (const row of cachedRows) { if (hiddenProjects.has(row.projectPath)) continue; - if (!projectMap.has(row.folder)) { - projectMap.set(row.folder, { folder: row.folder, projectPath: row.projectPath, sessions: [] }); - } const meta = metaMap.get(row.sessionId); const s = { sessionId: row.sessionId, @@ -192,6 +193,9 @@ function buildProjectsFromCache(showArchived) { archived: meta?.archived || 0, }; if (!showArchived && s.archived) continue; + if (!projectMap.has(row.folder)) { + projectMap.set(row.folder, { folder: row.folder, projectPath: row.projectPath, sessions: [] }); + } projectMap.get(row.folder).sessions.push(s); } @@ -212,7 +216,7 @@ function buildProjectsFromCache(showArchived) { // Inject active plain terminal sessions so they participate in sorting for (const [sessionId, session] of activeSessions) { if (session.exited || !session.isPlainTerminal) continue; - const folder = session.projectPath.replace(/[/_]/g, '-').replace(/^-/, '-'); + const folder = encodeProjectPath(session.projectPath); if (hiddenProjects.has(session.projectPath)) continue; if (!projectMap.has(folder)) { projectMap.set(folder, { folder, projectPath: session.projectPath, sessions: [] }); From ae611808a277b1a612dd8c4bb5e4dbddd487bfa1 Mon Sep 17 00:00:00 2001 From: Ali Basiri Date: Tue, 5 May 2026 09:32:31 -0700 Subject: [PATCH 8/9] v0.0.29: fix duplicate sidebar projects from path encoding mismatch; disable cursor blink to cut idle CPU --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d79f444..651a46c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "switchboard", - "version": "0.0.28", + "version": "0.0.29", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "switchboard", - "version": "0.0.28", + "version": "0.0.29", "hasInstallScript": true, "dependencies": { "@xterm/addon-fit": "^0.11.0", diff --git a/package.json b/package.json index 218593d..907ec5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "switchboard", - "version": "0.0.28", + "version": "0.0.29", "private": true, "description": "A desktop app for browsing, searching, and managing CLI coding sessions", "author": "Doctly ", From b88b19f3d97609fd63a0b46e81a4d33910ac5109 Mon Sep 17 00:00:00 2001 From: Ali B Date: Tue, 5 May 2026 10:51:50 -0700 Subject: [PATCH 9/9] fix: pick up ai-title entries so session names display (#42) Claude Code writes auto-generated titles as type:"ai-title"/aiTitle entries, but readSessionFile only parsed type:"custom-title". Sessions with an AI title but no user-set title fell back to the raw first prompt. User-set customTitle still takes precedence. Fixes #33 Co-authored-by: Claude Opus 4.7 (1M context) --- read-session-file.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/read-session-file.js b/read-session-file.js index 26c2e8b..1123289 100644 --- a/read-session-file.js +++ b/read-session-file.js @@ -13,12 +13,16 @@ function readSessionFile(filePath, folder, projectPath) { let textContent = ''; let slug = null; let customTitle = null; + let aiTitle = null; for (const line of lines) { const entry = JSON.parse(line); if (entry.slug && !slug) slug = entry.slug; if (entry.type === 'custom-title' && entry.customTitle) { customTitle = entry.customTitle; } + if (entry.type === 'ai-title' && entry.aiTitle) { + aiTitle = entry.aiTitle; + } if (entry.type === 'user' || entry.type === 'assistant' || (entry.type === 'message' && (entry.role === 'user' || entry.role === 'assistant'))) { messageCount++; @@ -45,7 +49,7 @@ function readSessionFile(filePath, folder, projectPath) { summary, firstPrompt: summary, created: stat.birthtime.toISOString(), modified: stat.mtime.toISOString(), - messageCount, textContent, slug, customTitle, + messageCount, textContent, slug, customTitle: customTitle || aiTitle, }; } catch { return null;