Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
52 changes: 51 additions & 1 deletion core/renderer/lib/termPathLinks.test.ts
Original file line number Diff line number Diff line change
@@ -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)

Expand Down Expand Up @@ -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<void> => {
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)
})
})
16 changes: 15 additions & 1 deletion core/renderer/lib/termPathLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ const anchors = new Map<string, string[]>()
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(/[\\/]+$/, '')
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion core/shared/termVersion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading