fix: improve Windows UX and stabilize chat/session behavior across runtimes (#693)

* fix: preserve unsent prompt when adding editor context in VS Code

* fix: append Add to chat selections as markdown blocks with stable spacing

Convert selected assistant content to markdown before appending
Wrap each Add to chat selection in an `md` fenced block
Preserve multiline composer formatting across repeated appends

* fix: normalize persisted Windows paths to prevent identity mismatches

* fix: hide Windows subprocess console popups across server tasks

Hide OpenCode startup and shell command child windows in the web server
Apply windowsHide to cloudflared and skills-catalog git subprocesses
Cover remaining git service exec paths that could surface console windows

* fix: restore chat auto re-pin when reaching bottom

Re-pin now triggers when scrolling back into the bottom zone, not only via the button.
Upward user scroll intent still unpins immediately and is not overridden by re-pin.
Unified bottom/re-pin threshold logic to reduce sensitivity mismatches.

* fix: restore chat scroll release on mobile during streaming

Restores pinned-scroll release on touch scroll up so mobile users can leave auto-follow while streaming.
Improves re-pin behavior near bottom to avoid sticky or inconsistent pin states.
Includes related chat UI and dependency updates in the same change set.

* fix: hide daemon startup probe consoles on Windows

* fix: prevent pinned scroll tug-of-war during streaming

* fix: prefer git.exe to avoid Windows diff popup flashes

* fix: prefer git.exe discovery in Windows git flows

* fix: avoid where probes in Windows git resolution

* fix: avoid update-check subprocess flashes on Windows

* fix: normalize read file path labels

* feat: add OpenChamber defaults and improve theme ports

Add new OpenChamber light and dark themes
Regenerate imported themes with stronger surface mapping
Set OpenChamber themes as the default top options

* fix: stabilize chat pin and unpin behavior during streaming

Restores reliable unpin on upward wheel and touch gestures while auto-follow is active.
Prevents immediate re-pin while the user is actively scrolling upward near the bottom.
Keeps smooth follow-to-bottom behavior while reducing scroll tug-of-war.

* fix: suppress Windows command popups in VSCode runtime processes

Hide spawned git and server process windows in VS Code runtime
Extend hidden-window handling to server port cleanup and reveal commands
Keep behavior unchanged on non-Windows platforms
This commit is contained in:
Bohdan Triapitsyn
2026-03-17 13:18:54 +02:00
committed by GitHub
parent a07c068b66
commit 3123de5f43
49 changed files with 8473 additions and 1183 deletions
@@ -206,7 +206,7 @@ const normalizePathValue = (value: string): string => {
if (!trimmed) {
return '';
}
return trimmed.replace(/\\/g, '/');
return trimmed.replace(/\\/g, '/').replace(/\/{2,}/g, '/');
};
const trimTrailingSlashes = (value: string): string => {
@@ -240,6 +240,45 @@ const getRelativePathFromDirectory = (filePath: string, currentDirectory: string
return normalizedPath;
};
const renderReadFilePath = (displayPath: string) => {
const lastSlash = displayPath.lastIndexOf('/');
if (lastSlash === -1) {
return (
<span
className="min-w-0 flex-1 truncate whitespace-nowrap typography-meta leading-5"
style={{ color: 'var(--tools-title)' }}
title={displayPath}
>
{displayPath}
</span>
);
}
const dir = displayPath.slice(0, lastSlash);
const name = displayPath.slice(lastSlash + 1);
const hasAbsoluteRoot = dir.startsWith('/');
const displayDir = hasAbsoluteRoot ? dir.slice(1) : dir;
return (
<span className="min-w-0 inline-flex max-w-full flex-1 items-baseline overflow-hidden typography-meta leading-5" title={displayPath}>
{hasAbsoluteRoot ? <span className="flex-shrink-0" style={{ color: 'var(--tools-description)' }}>/</span> : null}
<span
className="min-w-0 shrink truncate whitespace-nowrap"
style={{
color: 'var(--tools-description)',
direction: 'rtl',
textAlign: 'left',
}}
>
{displayDir}
</span>
<span className="flex-shrink-0" style={{ color: 'var(--tools-description)' }}>/</span>
<span className="flex-shrink-0" style={{ color: 'var(--tools-title)' }}>{name}</span>
</span>
);
};
const resolveAbsolutePath = (currentDirectory: string, filePath: string): string => {
const normalizedPath = normalizePathValue(filePath);
if (!normalizedPath) {
@@ -502,17 +541,7 @@ export const StaticToolRow: React.FC<{
title={entry.offset ? `${entry.displayPath}:${entry.offset}` : entry.displayPath}
>
{showToolFileIcons ? <FileTypeIcon filePath={entry.path} className="h-3.5 w-3.5" /> : null}
<span
className="min-w-0 flex-1 truncate whitespace-nowrap typography-meta leading-5"
style={{
color: 'var(--tools-description)',
direction: 'rtl',
textAlign: 'left',
}}
title={entry.displayPath}
>
{entry.displayPath}
</span>
{renderReadFilePath(entry.displayPath)}
</button>
))
: null}
@@ -520,7 +549,7 @@ export const StaticToolRow: React.FC<{
? descriptions.map((desc, index) => (
<span key={`${desc}-${index}`} className="inline-flex min-w-0 flex-1">
<Text
variant={animateTailText ? 'generate-effect' : undefined}
variant={animateTailText ? 'generate-effect' : 'static'}
className="min-w-0 flex-1 truncate whitespace-nowrap typography-meta leading-5"
style={{ color: 'var(--tools-description)' }}
title={desc}
@@ -550,7 +579,7 @@ export const StaticToolRow: React.FC<{
: null}
{!isReadGroup && !isSearchGroup && !isFetchGroup && descriptions.length > 0 ? (
<Text
variant={animateTailText ? 'generate-effect' : undefined}
variant={animateTailText ? 'generate-effect' : 'static'}
className="min-w-0 flex-1 truncate whitespace-nowrap typography-meta leading-5"
style={{ color: 'var(--tools-description)' }}
>
@@ -103,7 +103,7 @@ const getMultiFileDescription = (
<span key={entry.path} className="inline-flex min-w-0 max-w-full items-center gap-1 typography-meta leading-5" style={{ color: 'var(--tools-description)' }}>
{showFileIcons ? <FileTypeIcon filePath={entry.path} className="h-3.5 w-3.5" /> : null}
<Text
variant={animate ? 'generate-effect' : undefined}
variant={animate ? 'generate-effect' : 'static'}
className="min-w-0 max-w-full truncate typography-meta leading-5"
style={{ color: 'var(--tools-description)' }}
title={entry.path}
@@ -302,14 +302,36 @@ const getPrimaryDiffFromMetadata = (
return undefined;
};
const getRelativePath = (absolutePath: string, currentDirectory: string): string => {
if (absolutePath.startsWith(currentDirectory)) {
const relativePath = absolutePath.substring(currentDirectory.length);
const normalizeDisplayPath = (value: string): string => {
const trimmed = value.trim().replace(/\\/g, '/').replace(/\/{2,}/g, '/');
if (!trimmed || trimmed === '/') {
return trimmed;
}
return trimmed.replace(/\/+$/, '');
};
return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath;
const getRelativePath = (absolutePath: string, currentDirectory: string): string => {
const normalizedAbsolutePath = normalizeDisplayPath(absolutePath);
const normalizedCurrentDirectory = normalizeDisplayPath(currentDirectory);
if (!normalizedAbsolutePath) {
return '';
}
return absolutePath;
if (!normalizedCurrentDirectory) {
return normalizedAbsolutePath;
}
if (normalizedAbsolutePath === normalizedCurrentDirectory) {
return '.';
}
const prefix = `${normalizedCurrentDirectory}/`;
if (normalizedAbsolutePath.startsWith(prefix)) {
return normalizedAbsolutePath.slice(prefix.length);
}
return normalizedAbsolutePath;
};
const usePierreThemeConfig = () => {
@@ -390,6 +412,13 @@ const getToolDescriptionPath = (part: ToolPartType, state: ToolStateUnion, curre
}
}
if (part.tool === 'read' && input) {
const filePath = input?.filePath || input?.file_path || input?.path || metadata?.filePath || metadata?.file_path || metadata?.path;
if (typeof filePath === 'string') {
return getRelativePath(filePath, currentDirectory);
}
}
if (['write', 'create', 'file_write'].includes(part.tool) && input) {
const filePath = input?.filePath || input?.file_path || input?.path;
if (typeof filePath === 'string') {
@@ -853,7 +882,7 @@ const TaskToolSummary: React.FC<{
</span>
) : (
<Text
variant={animateTailText ? 'generate-effect' : undefined}
variant={animateTailText ? 'generate-effect' : 'static'}
className={cn(
'typography-meta flex-1 min-w-0 text-muted-foreground/70',
isMobile ? 'whitespace-normal break-words' : 'truncate'
@@ -964,11 +993,14 @@ const renderPathLikeGitChanges = (path: string, grow = true) => {
const dir = path.slice(0, lastSlash);
const name = path.slice(lastSlash + 1);
const hasAbsoluteRoot = dir.startsWith('/');
const displayDir = hasAbsoluteRoot ? dir.slice(1) : dir;
return (
<span className={cn('min-w-0 flex items-baseline overflow-hidden typography-ui-label', grow && 'flex-1')} title={path}>
{hasAbsoluteRoot ? <span className="flex-shrink-0 text-muted-foreground">/</span> : null}
<span className="min-w-0 truncate text-muted-foreground" style={{ direction: 'rtl', textAlign: 'left' }}>
{dir}
{displayDir}
</span>
<span className="flex-shrink-0">
<span className="text-muted-foreground">/</span>
@@ -998,20 +1030,23 @@ const renderAnimatedPathWithIcon = (path: string, _animate = true, grow = true,
const dir = path.slice(0, lastSlash);
const name = path.slice(lastSlash + 1);
const hasAbsoluteRoot = dir.startsWith('/');
const displayDir = hasAbsoluteRoot ? dir.slice(1) : dir;
return (
<span className={cn('min-w-0 inline-flex items-center gap-1 overflow-hidden', grow && 'flex-1')} title={path}>
{showFileIcons ? <FileTypeIcon filePath={path} className="h-3.5 w-3.5 flex-shrink-0" /> : null}
<span className={cn('min-w-0 inline-flex items-baseline overflow-hidden typography-meta', grow && 'flex-1')}>
<span className={cn('min-w-0 inline-flex max-w-full items-baseline overflow-hidden typography-meta', grow && 'flex-1')}>
{hasAbsoluteRoot ? <span className="flex-shrink-0" style={{ color: 'var(--tools-description)' }}>/</span> : null}
<span
className="min-w-0 flex-1 truncate whitespace-nowrap"
className="min-w-0 shrink truncate whitespace-nowrap"
style={{
color: 'var(--tools-description)',
direction: 'rtl',
textAlign: 'left',
}}
>
{dir}
{displayDir}
</span>
<span className="flex-shrink-0" style={{ color: 'var(--tools-description)' }}>/</span>
<span className="flex-shrink-0" style={{ color: 'var(--tools-title)' }}>
@@ -1933,7 +1968,7 @@ const ToolPart: React.FC<ToolPartProps> = ({
renderAnimatedPathWithIcon(descriptionPath, animateTailText, false, showToolFileIcons)
) : (
<Text
variant={animateTailText ? 'generate-effect' : undefined}
variant={animateTailText ? 'generate-effect' : 'static'}
className="min-w-0 truncate typography-meta"
style={{ color: 'var(--tools-description)' }}
title={description}