From 0b5f5c00d20004292eab2953d3b0fb2fa0b43578 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Thu, 15 Oct 2026 11:36:41 +0200 Subject: [PATCH] fix(paths): the tab's own folder is never a link found from a shown folder (core 0.40.7) Owner, 2026-10-15: "the status line in claude code which shows which folder im working in has become a link i dont want that". Claude Code's status line prints the tab's folder as Github-loftspace/Stutter-Diagnostics; since #221 a shown folder above it (Documents\Claude) resolved that to the folder the shell is in. A folder found from a shown folder that is the shell's own folder, or one above it, is now skipped, as the prompt already is. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017XYVRPzvhLpPCr75uB6uaA --- core/package.json | 2 +- core/renderer/lib/termPathLinks.test.ts | 52 ++++++++++++++++++++++++- core/renderer/lib/termPathLinks.ts | 16 +++++++- core/shared/termVersion.ts | 2 +- package-lock.json | 4 +- package.json | 2 +- 6 files changed, 71 insertions(+), 7 deletions(-) diff --git a/core/package.json b/core/package.json index 7e57384..e35a428 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "prism-term-core", - "version": "0.40.6", + "version": "0.40.7", "description": "What Prism Terminal and Prism share: the terminal (pty, shells, agent detection and indicator, themes, links, the panel, dictation) and the update chip with its window. TypeScript source, compiled by the host.", "license": "MIT", "private": true, diff --git a/core/renderer/lib/termPathLinks.test.ts b/core/renderer/lib/termPathLinks.test.ts index 15e8759..12b153a 100644 --- a/core/renderer/lib/termPathLinks.test.ts +++ b/core/renderer/lib/termPathLinks.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { configureTermCore, resetTermCore, type TermApi, type TermHostConfig } from '../host' -import { findPath, linkRanges, onPathsFound, parentDir, pathCandidates, resetPathCache, takeAsked } from './termPathLinks' +import { findPath, holdsCwd, linkRanges, onPathsFound, parentDir, pathCandidates, resetPathCache, takeAsked } from './termPathLinks' const paths = (text: string): string[] => pathCandidates(text).map((p) => p.path) @@ -186,3 +186,53 @@ describe('a relative path is tried in the folders the tab showed', () => { expect(bases).toEqual([REPO, `${MISC}\\dto\\b\\out`]) }) }) + +// Owner, 2026-10-15: Claude Code's status line prints the tab's folder as +// `Github-loftspace/Stutter-Diagnostics`, which a shown folder above it found. +describe('the folder the tab is in is never a link found from a shown folder', () => { + const ROOT = 'C:\\c' + const CWD = 'C:\\c\\Github-loftspace\\Stutter' + const dirs = new Set([ROOT, `${ROOT}\\Github-loftspace`, CWD, `${ROOT}\\Github`]) + const files = new Set([`${CWD}\\src\\a.ts`]) + const join = (base: string, t: string): string => (/^[A-Za-z]:/.test(t) ? t : `${base}\\${t.replace(/\//g, '\\')}`) + const ask = vi.fn(async (cwd: string, texts: string[]) => + texts.map((t) => { + const abs = join(cwd, t) + return dirs.has(abs) ? { kind: 'dir' as const, abs } : files.has(abs) ? { kind: 'file' as const, abs } : null + }) + ) + const settle = async (): Promise => { + for (let i = 0; i < 4; i += 1) await vi.advanceTimersByTimeAsync(50) + } + beforeEach(() => { + vi.useFakeTimers() + configureTermCore({ api: { termPathKinds: ask } as unknown as TermApi } as TermHostConfig) + }) + afterEach(() => { + resetPathCache() + resetTermCore() + vi.useRealTimers() + }) + + it('holdsCwd: the folder itself and those above it, either slash, any case', () => { + expect(holdsCwd('C:\\c\\Github-loftspace\\Stutter', CWD)).toBe(true) + expect(holdsCwd('c:/C/github-loftspace', CWD)).toBe(true) + expect(holdsCwd('C:\\c\\Github', CWD)).toBe(false) + expect(holdsCwd('C:\\c\\Github-loftspace\\Stutter\\src', CWD)).toBe(false) + }) + + it('the status line stays plain', async () => { + const full = `see ${CWD}\\src\\a.ts` + linkRanges(full, CWD, 'A') + await settle() + linkRanges(full, CWD, 'A') + await settle() + const status = 'Github-loftspace/Stutter (Sonnet 5.5)' + for (let i = 0; i < 3; i += 1) { + linkRanges(status, CWD, 'A') + await settle() + } + expect(linkRanges(status, CWD, 'A')).toEqual([]) + expect(findPath('A', CWD, 'Github-loftspace/Stutter')).toBe(null) + }) +}) diff --git a/core/renderer/lib/termPathLinks.ts b/core/renderer/lib/termPathLinks.ts index 8f6dd2c..a4a7ec3 100644 --- a/core/renderer/lib/termPathLinks.ts +++ b/core/renderer/lib/termPathLinks.ts @@ -69,6 +69,20 @@ const anchors = new Map() const isRelative = (p: string): boolean => !/^(?:[A-Za-z]:|[\\/]|~)/.test(p) const hasSeparator = (p: string): boolean => /[\\/]/.test(p) +/** + * Whether `dir` is `cwd` or a folder above it. Such a folder found from a shown + * folder is the one the shell is already in, said another way: Claude Code's + * status line prints the tab's folder as `Github-loftspace/Stutter-Diagnostics` + * on every redraw, and it lit up as a link (owner, 2026-10-15: "has become a + * link i dont want that"). The prompt is left alone for the same reason. + */ +export function holdsCwd(dir: string, cwd: string): boolean { + const norm = (p: string): string => p.replace(/\//g, '\\').replace(/\\+$/, '').toLowerCase() + const d = norm(dir) + const c = norm(cwd) + return c === d || c.startsWith(d + '\\') +} + /** The folder above `dir`, or null at a drive's root. */ export function parentDir(dir: string): string | null { const d = dir.replace(/[\\/]+$/, '') @@ -130,7 +144,7 @@ export function findPath( let unknown = false for (const base of anchorBases(owner, cwd, path)) { const b = knownPath(base, path) - if (b) return { hit: b, base } + if (b && !(b.kind === 'dir' && holdsCwd(b.abs, cwd))) return { hit: b, base } if (b === undefined) { unknown = true need(base) diff --git a/core/shared/termVersion.ts b/core/shared/termVersion.ts index 73c3f5f..eda96d7 100644 --- a/core/shared/termVersion.ts +++ b/core/shared/termVersion.ts @@ -7,5 +7,5 @@ * so a core release that bumps `core/package.json` (or xterm) without this * fails `npm test` (core/README.md, "Releasing the core"). */ -export const TERM_CORE_VERSION = '0.40.6' +export const TERM_CORE_VERSION = '0.40.7' export const XTERM_VERSION = '6.0.0' diff --git a/package-lock.json b/package-lock.json index 1eb040b..c73fd7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prism-terminal", - "version": "0.52.1", + "version": "0.52.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prism-terminal", - "version": "0.52.1", + "version": "0.52.2", "license": "MIT", "dependencies": { "@xterm/addon-fit": "^0.11.0", diff --git a/package.json b/package.json index baa7066..7c36745 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "prism-terminal", "productName": "Prism Terminal", - "version": "0.52.1", + "version": "0.52.2", "description": "A tabbed Windows terminal for AI CLIs.", "main": "./out/main/index.js", "author": "Max",