mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-10 16:21:22 +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>
|
</TabsList>
|
||||||
</div>
|
</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") && (
|
{mountedTabs.has("application") && (
|
||||||
<SettingsLazyTab value="application">
|
<SettingsLazyTab value="application">
|
||||||
<LazySettingsApplicationTab
|
<LazySettingsApplicationTab
|
||||||
|
|||||||
@@ -77,13 +77,13 @@ export const ClaudeCodeCard: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{found && (
|
{found && (
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||||
<span className="text-muted-foreground">{t('ai.claude.path')}</span>
|
<span className="text-muted-foreground shrink-0">{t('ai.claude.path')}</span>
|
||||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||||
{pathInfo.version && (
|
{pathInfo.version && (
|
||||||
<>
|
<>
|
||||||
<span className="text-muted-foreground">|</span>
|
<span className="text-muted-foreground shrink-0">|</span>
|
||||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -102,7 +102,7 @@ export const ClaudeCodeCard: React.FC<{
|
|||||||
value={customPath}
|
value={customPath}
|
||||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||||
placeholder={t('ai.claude.customPathPlaceholder')}
|
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()}>
|
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!customPath.trim()}>
|
||||||
<RefreshCw size={14} className="mr-1.5" />
|
<RefreshCw size={14} className="mr-1.5" />
|
||||||
|
|||||||
@@ -117,13 +117,13 @@ export const CodebuddyCard: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{found && (
|
{found && (
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||||
<span className="text-muted-foreground">{t('ai.codebuddy.path')}</span>
|
<span className="text-muted-foreground shrink-0">{t('ai.codebuddy.path')}</span>
|
||||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||||
{pathInfo.version && (
|
{pathInfo.version && (
|
||||||
<>
|
<>
|
||||||
<span className="text-muted-foreground">|</span>
|
<span className="text-muted-foreground shrink-0">|</span>
|
||||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +142,7 @@ export const CodebuddyCard: React.FC<{
|
|||||||
value={customPath}
|
value={customPath}
|
||||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||||
placeholder={t('ai.codebuddy.customPathPlaceholder')}
|
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()}>
|
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!customPath.trim()}>
|
||||||
<RefreshCw size={14} className="mr-1.5" />
|
<RefreshCw size={14} className="mr-1.5" />
|
||||||
|
|||||||
@@ -108,13 +108,13 @@ export const CodexConnectionCard: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{found && (
|
{found && (
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||||
<span className="text-muted-foreground">{t('ai.codex.path')}</span>
|
<span className="text-muted-foreground shrink-0">{t('ai.codex.path')}</span>
|
||||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||||
{pathInfo.version && (
|
{pathInfo.version && (
|
||||||
<>
|
<>
|
||||||
<span className="text-muted-foreground">|</span>
|
<span className="text-muted-foreground shrink-0">|</span>
|
||||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +133,7 @@ export const CodexConnectionCard: React.FC<{
|
|||||||
value={customPath}
|
value={customPath}
|
||||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||||
placeholder={t('ai.codex.customPathPlaceholder')}
|
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()}>
|
<Button variant="outline" size="sm" onClick={onRecheckPath} disabled={!customPath.trim()}>
|
||||||
<RefreshCw size={14} className="mr-1.5" />
|
<RefreshCw size={14} className="mr-1.5" />
|
||||||
|
|||||||
@@ -61,13 +61,13 @@ export const CopilotCliCard: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{found && (
|
{found && (
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs min-w-0">
|
||||||
<span className="text-muted-foreground">{t(`${i18nPrefix}.path`)}</span>
|
<span className="text-muted-foreground shrink-0">{t(`${i18nPrefix}.path`)}</span>
|
||||||
<span className="font-mono text-foreground truncate">{pathInfo.path}</span>
|
<span className="font-mono text-foreground min-w-0 flex-1 truncate" title={pathInfo.path ?? undefined}>{pathInfo.path}</span>
|
||||||
{pathInfo.version && (
|
{pathInfo.version && (
|
||||||
<>
|
<>
|
||||||
<span className="text-muted-foreground">|</span>
|
<span className="text-muted-foreground shrink-0">|</span>
|
||||||
<span className="text-muted-foreground">{pathInfo.version}</span>
|
<span className="text-muted-foreground shrink-0">{pathInfo.version}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +87,7 @@ export const CopilotCliCard: React.FC<{
|
|||||||
value={customPath}
|
value={customPath}
|
||||||
onChange={(e) => onCustomPathChange(e.target.value)}
|
onChange={(e) => onCustomPathChange(e.target.value)}
|
||||||
placeholder={t(`${i18nPrefix}.customPathPlaceholder`)}
|
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()}>
|
<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