mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-29 18:11:40 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
274f043681 |
No files matched your search
@@ -523,7 +523,8 @@ const SettingsPageContent: React.FC<{ settings: SettingsState; appLock?: AppLock
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 h-full flex flex-col min-h-0 bg-muted/10">
|
||||
{/* min-w-0: row flex item must shrink so long Agent paths truncate inside the pane. */}
|
||||
<div className="flex-1 h-full flex flex-col min-h-0 min-w-0 bg-muted/10">
|
||||
{mountedTabs.has("application") && (
|
||||
<SettingsLazyTab value="application">
|
||||
<LazySettingsApplicationTab
|
||||
|
||||
@@ -77,13 +77,13 @@ export const ClaudeCodeCard: React.FC<{
|
||||
</div>
|
||||
|
||||
{found && (
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="text-muted-foreground">{t('ai.claude.path')}</span>
|
||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
||||
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||
<span className="text-muted-foreground shrink-0">{t('ai.claude.path')}</span>
|
||||
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||
{pathInfo.version && (
|
||||
<>
|
||||
<span className="text-muted-foreground">|</span>
|
||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
||||
<span className="text-muted-foreground shrink-0">|</span>
|
||||
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -102,7 +102,7 @@ export const ClaudeCodeCard: React.FC<{
|
||||
value={customPath}
|
||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||
placeholder={t('ai.claude.customPathPlaceholder')}
|
||||
className="flex-1 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
className="flex-1 min-w-0 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!customPath.trim()}>
|
||||
<RefreshCw size={14} className="mr-1.5" />
|
||||
|
||||
@@ -117,13 +117,13 @@ export const CodebuddyCard: React.FC<{
|
||||
</div>
|
||||
|
||||
{found && (
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="text-muted-foreground">{t('ai.codebuddy.path')}</span>
|
||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
||||
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||
<span className="text-muted-foreground shrink-0">{t('ai.codebuddy.path')}</span>
|
||||
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||
{pathInfo.version && (
|
||||
<>
|
||||
<span className="text-muted-foreground">|</span>
|
||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
||||
<span className="text-muted-foreground shrink-0">|</span>
|
||||
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -142,7 +142,7 @@ export const CodebuddyCard: React.FC<{
|
||||
value={customPath}
|
||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||
placeholder={t('ai.codebuddy.customPathPlaceholder')}
|
||||
className="flex-1 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
className="flex-1 min-w-0 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!customPath.trim()}>
|
||||
<RefreshCw size={14} className="mr-1.5" />
|
||||
|
||||
@@ -108,13 +108,13 @@ export const CodexConnectionCard: React.FC<{
|
||||
</div>
|
||||
|
||||
{found && (
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="text-muted-foreground">{t('ai.codex.path')}</span>
|
||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
||||
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||
<span className="text-muted-foreground shrink-0">{t('ai.codex.path')}</span>
|
||||
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||
{pathInfo.version && (
|
||||
<>
|
||||
<span className="text-muted-foreground">|</span>
|
||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
||||
<span className="text-muted-foreground shrink-0">|</span>
|
||||
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -133,7 +133,7 @@ export const CodexConnectionCard: React.FC<{
|
||||
value={customPath}
|
||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||
placeholder={t('ai.codex.customPathPlaceholder')}
|
||||
className="flex-1 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
className="flex-1 min-w-0 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!customPath.trim()}>
|
||||
<RefreshCw size={14} className="mr-1.5" />
|
||||
|
||||
@@ -61,13 +61,13 @@ export const CopilotCliCard: React.FC<{
|
||||
</div>
|
||||
|
||||
{found && (
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="text-muted-foreground">{t(`${i18nPrefix}.path`)}</span>
|
||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
||||
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||
<span className="text-muted-foreground shrink-0">{t(`${i18nPrefix}.path`)}</span>
|
||||
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||
{pathInfo.version && (
|
||||
<>
|
||||
<span className="text-muted-foreground">|</span>
|
||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
||||
<span className="text-muted-foreground shrink-0">|</span>
|
||||
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@ export const CopilotCliCard: React.FC<{
|
||||
value={customPath}
|
||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||
placeholder={t(`${i18nPrefix}.customPathPlaceholder`)}
|
||||
className="flex-1 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
className="flex-1 min-w-0 h-8 rounded-md border border-input bg-background px-3 text-sm font-mono placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
/>
|
||||
)}
|
||||
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!allowEmptyCheck && !customPath.trim()}>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { ClaudeCodeCard } from "./ClaudeCodeCard";
|
||||
import { CodebuddyCard } from "./CodebuddyCard";
|
||||
import { CodexConnectionCard } from "./CodexConnectionCard";
|
||||
import { CopilotCliCard } from "./CopilotCliCard";
|
||||
|
||||
const LONG_PATH = "C:\\Users\\70541\\AppData\\Local\\Microsoft\\WinGet\\Packages\\Anthropic.ClaudeCode_Microsoft.Winget.Source_8wekyb3d8bbwe\\claude.exe";
|
||||
const VERSION = "9.9.9-test";
|
||||
|
||||
function classList(tag: string): Set<string> {
|
||||
const match = tag.match(/class="([^"]*)"/);
|
||||
assert.ok(match, `missing class on ${tag}`);
|
||||
return new Set(match[1].split(/\s+/));
|
||||
}
|
||||
|
||||
function assertClasses(tag: string, expected: string[]) {
|
||||
const have = classList(tag);
|
||||
for (const name of expected) {
|
||||
assert.ok(have.has(name), `expected ${name} on ${tag}`);
|
||||
}
|
||||
}
|
||||
|
||||
function openingTagContaining(markup: string, text: string): string {
|
||||
const idx = markup.indexOf(`>${text}<`);
|
||||
assert.ok(idx >= 0, `missing text node ${text}`);
|
||||
const start = markup.lastIndexOf("<", idx);
|
||||
return markup.slice(start, idx + 1);
|
||||
}
|
||||
|
||||
function pathRowClass(markup: string): string {
|
||||
const idx = markup.indexOf(LONG_PATH);
|
||||
assert.ok(idx >= 0, "missing path text");
|
||||
const matches = [...markup.slice(0, idx).matchAll(/<div\b[^>]*class="([^"]*)"/g)];
|
||||
assert.ok(matches.length > 0, "missing path row");
|
||||
return matches[matches.length - 1][1];
|
||||
}
|
||||
|
||||
function assertLongPathTruncates(markup: string) {
|
||||
const pathTag = openingTagContaining(markup, LONG_PATH);
|
||||
assertClasses(pathTag, ["truncate", "min-w-0", "flex-1"]);
|
||||
assert.match(pathTag, new RegExp(`title="${LONG_PATH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`));
|
||||
assert.ok(pathRowClass(markup).split(/\s+/).includes("min-w-0"), "path row missing min-w-0");
|
||||
|
||||
const versionTag = openingTagContaining(markup, VERSION);
|
||||
assertClasses(versionTag, ["shrink-0"]);
|
||||
|
||||
const input = markup.match(/<input\b[^>]*>/);
|
||||
assert.ok(input, "missing custom path input");
|
||||
assertClasses(input[0], ["flex-1", "min-w-0"]);
|
||||
assert.match(input[0], new RegExp(`value="${LONG_PATH.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`));
|
||||
}
|
||||
|
||||
test("Claude Code card truncates a long CLI path inside the card", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<ClaudeCodeCard
|
||||
pathInfo={{ path: LONG_PATH, version: VERSION, available: true }}
|
||||
isResolvingPath={false}
|
||||
customPath={LONG_PATH}
|
||||
onCustomPathChange={() => {}}
|
||||
onRecheckPath={() => {}}
|
||||
onResetPath={() => {}}
|
||||
configDir=""
|
||||
onConfigDirChange={() => {}}
|
||||
settingsPath=""
|
||||
onSettingsPathChange={() => {}}
|
||||
envText=""
|
||||
onEnvTextChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
assertLongPathTruncates(markup);
|
||||
});
|
||||
|
||||
test("Codex card truncates a long CLI path inside the card", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<CodexConnectionCard
|
||||
pathInfo={{ path: LONG_PATH, version: VERSION, available: true }}
|
||||
isResolvingPath={false}
|
||||
customPath={LONG_PATH}
|
||||
onCustomPathChange={() => {}}
|
||||
onRecheckPath={() => {}}
|
||||
onResetPath={() => {}}
|
||||
integration={null}
|
||||
loginSession={null}
|
||||
isLoading={false}
|
||||
error={null}
|
||||
onRefresh={() => {}}
|
||||
onConnect={() => {}}
|
||||
onCancel={() => {}}
|
||||
onOpenUrl={() => {}}
|
||||
onLogout={() => {}}
|
||||
appServerRuntime="sdk"
|
||||
appServerStatus={null}
|
||||
onAppServerRuntimeChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
assertLongPathTruncates(markup);
|
||||
});
|
||||
|
||||
test("Codebuddy card truncates a long CLI path inside the card", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<CodebuddyCard
|
||||
pathInfo={{ path: LONG_PATH, version: VERSION, available: true }}
|
||||
isResolvingPath={false}
|
||||
customPath={LONG_PATH}
|
||||
onCustomPathChange={() => {}}
|
||||
onRecheckPath={() => {}}
|
||||
onResetPath={() => {}}
|
||||
internetEnv=""
|
||||
onInternetEnvChange={() => {}}
|
||||
envText=""
|
||||
onEnvTextChange={() => {}}
|
||||
/>,
|
||||
);
|
||||
assertLongPathTruncates(markup);
|
||||
});
|
||||
|
||||
test("Copilot CLI card truncates a long CLI path inside the card", () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<CopilotCliCard
|
||||
pathInfo={{ path: LONG_PATH, version: VERSION, available: true }}
|
||||
isResolvingPath={false}
|
||||
customPath={LONG_PATH}
|
||||
onCustomPathChange={() => {}}
|
||||
onRecheckPath={() => {}}
|
||||
onResetPath={() => {}}
|
||||
/>,
|
||||
);
|
||||
assertLongPathTruncates(markup);
|
||||
});
|
||||
Reference in new issue
Block a user