From 1295cfabcabaeebeb0d5c8605a9c596d8ad98afe Mon Sep 17 00:00:00 2001 From: Claas Pancratius <54213976+OfflinePing@users.noreply.github.com> Date: Fri, 15 May 2026 15:09:03 +0200 Subject: [PATCH] feat(settings): add Info section with version, runtime, and diagnostics (#413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds a new **Info** section to the Settings screen, giving users quick visibility into their CodeNomad version, runtime environment, and a way to collect diagnostic data for bug reports. Closes #412 ## Changes - **New component:** `info-settings-section.tsx` — renders three cards: - **About** — Server/UI version, runtime type (electron/tauri/web), platform, OS with CPU architecture detection, server URL, workspace root - **Updates** — Placeholder "Check for updates" button, ready to wire into the existing dev release monitor (`serverMeta.update`) - **Diagnostics** — Log scope dropdown (Summary only / Summary + workspace logs), Copy to clipboard, Download .txt — generates a structured diagnostic report - **New styles:** `settings-info.css` — info row layout, select row, toast feedback, update note - **Wiring:** Added `"info"` to `SettingsSectionId` union, registered nav item and routing case in `settings-screen.tsx`, imported CSS in `controls.css` - **i18n:** 20 new keys added to all 7 locales (English canonical, others fall back via existing fallback chain) ## Design decisions - No server changes needed — version/runtime info comes from existing `GET /api/meta` endpoint and client-side `navigator` detection - No sensitive data (API keys, env values) included in diagnostic reports - CPU architecture extracted from `navigator.userAgent` for Linux/Windows/macOS - Log scope dropdown uses existing Kobalte Select pattern for consistency ## Verification - `tsc --noEmit` passes (0 errors) - `vite build` bundles successfully - Tauri Rust backend compiles and starts correctly --- > **Note:** The majority of this implementation was produced through CodeNomad using DeepSeek v4 Pro and has undergone manual review before submission. All design, architecture, and code quality decisions were evaluated by a human reviewer. --------- Co-authored-by: Shantur Rathore --- .../ui/src/components/settings-screen.tsx | 6 +- .../settings/info-settings-section.tsx | 333 ++++++++++++++++++ .../ui/src/lib/i18n/messages/en/settings.ts | 23 ++ .../ui/src/lib/i18n/messages/es/settings.ts | 23 ++ .../ui/src/lib/i18n/messages/fr/settings.ts | 23 ++ .../ui/src/lib/i18n/messages/he/settings.ts | 23 ++ .../ui/src/lib/i18n/messages/ja/settings.ts | 23 ++ .../ui/src/lib/i18n/messages/ru/settings.ts | 23 ++ .../src/lib/i18n/messages/zh-Hans/settings.ts | 23 ++ packages/ui/src/stores/settings-screen.ts | 2 +- .../src/styles/components/settings-info.css | 57 +++ packages/ui/src/styles/controls.css | 1 + 12 files changed, 558 insertions(+), 2 deletions(-) create mode 100644 packages/ui/src/components/settings/info-settings-section.tsx create mode 100644 packages/ui/src/styles/components/settings-info.css diff --git a/packages/ui/src/components/settings-screen.tsx b/packages/ui/src/components/settings-screen.tsx index d65e5653..921855bc 100644 --- a/packages/ui/src/components/settings-screen.tsx +++ b/packages/ui/src/components/settings-screen.tsx @@ -1,5 +1,5 @@ import { Dialog } from "@kobalte/core/dialog" -import { Settings, Bell, MonitorUp, Paintbrush, Terminal, Volume2, Globe, X } from "lucide-solid" +import { Settings, Bell, Info, MonitorUp, Paintbrush, Terminal, Volume2, Globe, X } from "lucide-solid" import { createMemo, For, type Component } from "solid-js" import { useI18n } from "../lib/i18n" import { @@ -10,6 +10,7 @@ import { type SettingsSectionId, } from "../stores/settings-screen" import { AppearanceSettingsSection } from "./settings/appearance-settings-section" +import { InfoSettingsSection } from "./settings/info-settings-section" import { NotificationsSettingsSection } from "./settings/notifications-settings-section" import { OpenCodeSettingsSection } from "./settings/opencode-settings-section" import { RemoteAccessSettingsSection } from "./settings/remote-access-settings-section" @@ -27,6 +28,7 @@ export const SettingsScreen: Component = () => { { id: "speech" as SettingsSectionId, icon: Volume2, label: t("settings.nav.speech") }, { id: "sidecars" as SettingsSectionId, icon: Globe, label: t("settings.nav.sidecars") }, { id: "opencode" as SettingsSectionId, icon: Terminal, label: t("settings.nav.opencode") }, + { id: "info" as SettingsSectionId, icon: Info, label: t("settings.nav.info") }, ] if (canOpenRemoteWindows()) { @@ -48,6 +50,8 @@ export const SettingsScreen: Component = () => { return case "opencode": return + case "info": + return case "appearance": default: return diff --git a/packages/ui/src/components/settings/info-settings-section.tsx b/packages/ui/src/components/settings/info-settings-section.tsx new file mode 100644 index 00000000..cc1f1937 --- /dev/null +++ b/packages/ui/src/components/settings/info-settings-section.tsx @@ -0,0 +1,333 @@ +import { createEffect, createMemo, createResource, createSignal, onCleanup, type Component } from "solid-js" +import { Info } from "lucide-solid" +import { useI18n } from "../../lib/i18n" +import { getServerMeta } from "../../lib/server-meta" +import { runtimeEnv } from "../../lib/runtime-env" +import type { ServerMeta } from "../../../../server/src/api-types" + +interface UserAgentData { + platform?: string + getHighEntropyValues?: (hints: string[]) => Promise> +} + +function getUserAgentData(): UserAgentData | undefined { + return (navigator as any).userAgentData +} + +function detectOs(): string { + if (typeof navigator === "undefined") return "Unknown" + + const uaData = getUserAgentData() + if (uaData?.platform) { + const arch = extractArchFromUA(navigator.userAgent) + return arch ? `${uaData.platform} ${arch}` : uaData.platform + } + + const ua = navigator.userAgent + const p = navigator.platform + if (!p) return "Unknown" + + const maybeArch = extractArchFromUA(ua) + if (maybeArch && !p.includes(maybeArch)) { + return `${p} ${maybeArch}` + } + return p +} + +function extractArchFromUA(ua: string): string | null { + const match = ua.match(/Linux\s+(x86_64|aarch64|armv[0-9]+[a-z]*|i[3-6]86)/i) + ?? ua.match(/Win64;\s*(x64|arm64)/i) + ?? ua.match(/Mac\s*OS\s*X[^)]*?_(x86_64|arm64)/i) + return match ? match[1] : null +} + +async function resolveArchitecture(): Promise { + try { + const uaData = getUserAgentData() + if (!uaData?.getHighEntropyValues) return null + const values = await uaData.getHighEntropyValues(["architecture", "bitness"]) + const parts: string[] = [] + if (values.architecture && !values.architecture.startsWith("x86")) { + parts.push(values.architecture) + } + if (values.bitness && values.bitness !== "64") { + parts.push(`${values.bitness}-bit`) + } + if (!parts.length && values.architecture) { + parts.push(values.architecture) + } + return parts.length > 0 ? parts.join(" ") : null + } catch { + return null + } +} + +function buildDiagnosticReport( + meta: ServerMeta | null, + osDisplay: string, +): string { + const lines: string[] = [] + lines.push("CodeNomad Diagnostic Report") + lines.push("============================") + lines.push(`Generated: ${new Date().toISOString()}`) + lines.push(`Server version: ${meta?.serverVersion ?? "unknown"}`) + lines.push(`UI version: ${meta?.ui?.version ?? "unknown"} (source: ${meta?.ui?.source ?? "unknown"})`) + lines.push(`Runtime: ${runtimeEnv.host}`) + lines.push(`Platform: ${runtimeEnv.platform}`) + lines.push(`Window context: ${runtimeEnv.windowContext}`) + lines.push(`OS: ${osDisplay}`) + lines.push(`Server URL: ${meta?.localUrl ?? "unknown"}`) + lines.push(`Workspace root: ${meta?.workspaceRoot ?? "unknown"}`) + lines.push(`UI source: ${meta?.ui?.source ?? "unknown"}`) + lines.push("") + return lines.join("\n") +} + +async function copyToClipboard(text: string): Promise { + try { + await navigator.clipboard.writeText(text) + return true + } catch { + return false + } +} + +function downloadTextFile(filename: string, text: string) { + const blob = new Blob([text], { type: "text/plain;charset=utf-8" }) + const url = URL.createObjectURL(blob) + const anchor = document.createElement("a") + anchor.href = url + anchor.download = filename + document.body.appendChild(anchor) + anchor.click() + document.body.removeChild(anchor) + URL.revokeObjectURL(url) +} + +function extractReleasePrefix(version: string): string { + return version.replace(/^v/, "").split("-")[0] +} + +function versionNewer(current: string, latest: string): boolean | null { + const c = extractReleasePrefix(current).split(".").map(Number) + const l = extractReleasePrefix(latest).split(".").map(Number) + if (c.some(isNaN) || l.some(isNaN)) return null + if (l[0] > c[0]) return true + if (l[0] < c[0]) return false + if (l[1] > c[1]) return true + if (l[1] < c[1]) return false + if (l[2] > c[2]) return true + return false +} + +export const InfoSettingsSection: Component = () => { + const { t } = useI18n() + const [meta, { mutate }] = createResource(() => getServerMeta()) + const [copyFeedback, setCopyFeedback] = createSignal<"success" | "error" | null>(null) + const [osArch, setOsArch] = createSignal(null) + + createEffect(() => { + resolveArchitecture().then((arch) => { + if (arch) setOsArch(arch) + }) + }) + + const updateInfo = createMemo(() => { + const m = meta() + if (!m?.update) return null + return m.update + }) + + const supportInfo = createMemo(() => meta()?.support ?? null) + + const latestVersion = createMemo(() => { + const update = updateInfo() + if (update?.version) return update.version + return supportInfo()?.latestServerVersion ?? null + }) + + const showDownloadLink = createMemo(() => { + let url: string | null = null + const update = updateInfo() + if (update?.url) url = update.url + else if (supportInfo()?.latestServerUrl) url = supportInfo()!.latestServerUrl ?? null + if (!url) return { url: null, show: false } + if (update?.url) return { url, show: true } + const current = meta()?.serverVersion + const latest = latestVersion() + if (!current || !latest) return { url: null, show: false } + return { url, show: versionNewer(current, latest) !== false } + }) + + let feedbackTimer: ReturnType | undefined + + createEffect(() => { + if (copyFeedback()) { + clearTimeout(feedbackTimer) + feedbackTimer = setTimeout(() => setCopyFeedback(null), 2500) + } + }) + + onCleanup(() => clearTimeout(feedbackTimer)) + + const handleRefresh = async () => { + const fresh = await getServerMeta(true) + mutate(fresh) + } + + const osDisplay = createMemo(() => { + const base = detectOs() + const arch = osArch() + return arch ? `${base} (${arch})` : base + }) + + const handleCopy = async () => { + const report = buildDiagnosticReport(meta() ?? null, osDisplay()) + const ok = await copyToClipboard(report) + if (ok) setCopyFeedback("success") + else setCopyFeedback("error") + } + + const handleDownload = () => { + const report = buildDiagnosticReport(meta() ?? null, osDisplay()) + const ts = new Date().toISOString().replace(/[:.]/g, "-") + downloadTextFile(`codenomad-diagnostics-${ts}.txt`, report) + } + + return ( +
+
+
+
+ +
+

{t("settings.section.info.title")}

+

{t("settings.section.info.subtitle")}

+
+
+
+ +
+
+ {t("settings.info.version.server")} + {meta()?.serverVersion ?? "—"} +
+
+ {t("settings.info.version.ui")} + {meta()?.ui?.version ?? "—"} +
+
+ {t("settings.info.version.uiSource")} + + {meta()?.ui?.source ?? "—"} + +
+
+ {t("settings.info.runtime.type")} + {runtimeEnv.host} +
+
+ {t("settings.info.runtime.platform")} + {runtimeEnv.platform} +
+
+ {t("settings.info.runtime.os")} + {osDisplay()} +
+
+ {t("settings.info.server.url")} + + {meta()?.localUrl ?? "—"} + +
+
+ {t("settings.info.server.root")} + + {meta()?.workspaceRoot ?? "—"} + +
+
+
+ +
+
+
+

{t("settings.info.updates.title")}

+

{t("settings.info.updates.subtitle")}

+
+
+ +
+
+ {t("settings.info.version.server")} + {meta()?.serverVersion ?? "—"} +
+
+ {t("settings.info.updates.latest")} + + {latestVersion() ?? "—"} + +
+
+ +
+ {showDownloadLink().show && ( + + {t("settings.info.updates.download")} + + )} + +
+
+ +
+
+
+

{t("settings.info.diagnostics.title")}

+

{t("settings.info.diagnostics.subtitle")}

+
+
+ +
+ + +
+ + {copyFeedback() === "success" && ( +
+ {t("settings.info.diagnostics.copied")} +
+ )} + {copyFeedback() === "error" && ( + + )} +
+
+ ) +} diff --git a/packages/ui/src/lib/i18n/messages/en/settings.ts b/packages/ui/src/lib/i18n/messages/en/settings.ts index 35f3999c..79c27144 100644 --- a/packages/ui/src/lib/i18n/messages/en/settings.ts +++ b/packages/ui/src/lib/i18n/messages/en/settings.ts @@ -71,6 +71,7 @@ export const settingsMessages = { "settings.nav.remote": "Remote Access", "settings.nav.speech": "Speech", "settings.nav.opencode": "OpenCode", + "settings.nav.info": "Info", "settings.scope.device": "This device", "settings.scope.server": "Server setting", "settings.common.enabled": "Enabled", @@ -239,4 +240,26 @@ export const settingsMessages = { "sidecars.refresh": "Refresh", "sidecars.path": "Path", "sidecars.go": "Go", + + "settings.section.info.title": "About", + "settings.section.info.subtitle": "View version, runtime, and gather diagnostic information.", + "settings.info.version.server": "Server version", + "settings.info.version.ui": "UI version", + "settings.info.version.uiSource": "UI source", + "settings.info.runtime.type": "Runtime", + "settings.info.runtime.platform": "Platform", + "settings.info.runtime.os": "Operating system", + "settings.info.server.url": "Server URL", + "settings.info.server.root": "Workspace root", + "settings.info.updates.title": "Updates", + "settings.info.updates.subtitle": "Check for new releases of CodeNomad.", + "settings.info.updates.latest": "Latest version", + "settings.info.updates.download": "Download update", + "settings.info.updates.refresh": "Refresh", + "settings.info.diagnostics.copyFailed": "Clipboard access denied. Use the download button instead.", + "settings.info.diagnostics.title": "Diagnostics", + "settings.info.diagnostics.subtitle": "Collect system and version info for bug reports.", + "settings.info.diagnostics.copy": "Copy to clipboard", + "settings.info.diagnostics.download": "Download .txt", + "settings.info.diagnostics.copied": "Diagnostic info copied to clipboard.", } as const diff --git a/packages/ui/src/lib/i18n/messages/es/settings.ts b/packages/ui/src/lib/i18n/messages/es/settings.ts index a158ec51..44a6bacd 100644 --- a/packages/ui/src/lib/i18n/messages/es/settings.ts +++ b/packages/ui/src/lib/i18n/messages/es/settings.ts @@ -71,6 +71,7 @@ export const settingsMessages = { "settings.nav.remote": "Remote Access", "settings.nav.speech": "Speech", "settings.nav.opencode": "OpenCode", + "settings.nav.info": "Info", "settings.scope.device": "This device", "settings.scope.server": "Server setting", "settings.common.enabled": "Enabled", @@ -238,4 +239,26 @@ export const settingsMessages = { "sidecars.refresh": "Refresh", "sidecars.path": "Path", "sidecars.go": "Go", + + "settings.section.info.title": "About", + "settings.section.info.subtitle": "View version, runtime, and gather diagnostic information.", + "settings.info.version.server": "Server version", + "settings.info.version.ui": "UI version", + "settings.info.version.uiSource": "UI source", + "settings.info.runtime.type": "Runtime", + "settings.info.runtime.platform": "Platform", + "settings.info.runtime.os": "Operating system", + "settings.info.server.url": "Server URL", + "settings.info.server.root": "Workspace root", + "settings.info.updates.title": "Updates", + "settings.info.updates.subtitle": "Check for new releases of CodeNomad.", + "settings.info.updates.latest": "Latest version", + "settings.info.updates.download": "Download update", + "settings.info.updates.refresh": "Refresh", + "settings.info.diagnostics.copyFailed": "Clipboard access denied. Use the download button instead.", + "settings.info.diagnostics.title": "Diagnostics", + "settings.info.diagnostics.subtitle": "Recopila información del sistema y de la versión para informes de errores.", + "settings.info.diagnostics.copy": "Copy to clipboard", + "settings.info.diagnostics.download": "Download .txt", + "settings.info.diagnostics.copied": "Diagnostic info copied to clipboard.", } as const diff --git a/packages/ui/src/lib/i18n/messages/fr/settings.ts b/packages/ui/src/lib/i18n/messages/fr/settings.ts index 72229287..a462271f 100644 --- a/packages/ui/src/lib/i18n/messages/fr/settings.ts +++ b/packages/ui/src/lib/i18n/messages/fr/settings.ts @@ -71,6 +71,7 @@ export const settingsMessages = { "settings.nav.remote": "Remote Access", "settings.nav.speech": "Speech", "settings.nav.opencode": "OpenCode", + "settings.nav.info": "Info", "settings.scope.device": "This device", "settings.scope.server": "Server setting", "settings.common.enabled": "Enabled", @@ -238,4 +239,26 @@ export const settingsMessages = { "sidecars.refresh": "Refresh", "sidecars.path": "Path", "sidecars.go": "Go", + + "settings.section.info.title": "About", + "settings.section.info.subtitle": "View version, runtime, and gather diagnostic information.", + "settings.info.version.server": "Server version", + "settings.info.version.ui": "UI version", + "settings.info.version.uiSource": "UI source", + "settings.info.runtime.type": "Runtime", + "settings.info.runtime.platform": "Platform", + "settings.info.runtime.os": "Operating system", + "settings.info.server.url": "Server URL", + "settings.info.server.root": "Workspace root", + "settings.info.updates.title": "Updates", + "settings.info.updates.subtitle": "Check for new releases of CodeNomad.", + "settings.info.updates.latest": "Latest version", + "settings.info.updates.download": "Download update", + "settings.info.updates.refresh": "Refresh", + "settings.info.diagnostics.copyFailed": "Clipboard access denied. Use the download button instead.", + "settings.info.diagnostics.title": "Diagnostics", + "settings.info.diagnostics.subtitle": "Collectez les informations système et de version pour les rapports de bug.", + "settings.info.diagnostics.copy": "Copy to clipboard", + "settings.info.diagnostics.download": "Download .txt", + "settings.info.diagnostics.copied": "Diagnostic info copied to clipboard.", } as const diff --git a/packages/ui/src/lib/i18n/messages/he/settings.ts b/packages/ui/src/lib/i18n/messages/he/settings.ts index cc8b5904..b0c0e7f9 100644 --- a/packages/ui/src/lib/i18n/messages/he/settings.ts +++ b/packages/ui/src/lib/i18n/messages/he/settings.ts @@ -70,6 +70,7 @@ export const settingsMessages = { "settings.nav.notifications": "התראות", "settings.nav.remote": "גישה מרוחקת", "settings.nav.opencode": "OpenCode", + "settings.nav.info": "Info", "settings.scope.device": "מכשיר זה", "settings.scope.server": "הגדרת שרת", "settings.common.enabled": "מופעל", @@ -237,4 +238,26 @@ export const settingsMessages = { "sidecars.refresh": "Refresh", "sidecars.path": "Path", "sidecars.go": "Go", + + "settings.section.info.title": "About", + "settings.section.info.subtitle": "View version, runtime, and gather diagnostic information.", + "settings.info.version.server": "Server version", + "settings.info.version.ui": "UI version", + "settings.info.version.uiSource": "UI source", + "settings.info.runtime.type": "Runtime", + "settings.info.runtime.platform": "Platform", + "settings.info.runtime.os": "Operating system", + "settings.info.server.url": "Server URL", + "settings.info.server.root": "Workspace root", + "settings.info.updates.title": "Updates", + "settings.info.updates.subtitle": "Check for new releases of CodeNomad.", + "settings.info.updates.latest": "Latest version", + "settings.info.updates.download": "Download update", + "settings.info.updates.refresh": "Refresh", + "settings.info.diagnostics.copyFailed": "Clipboard access denied. Use the download button instead.", + "settings.info.diagnostics.title": "Diagnostics", + "settings.info.diagnostics.subtitle": "אסוף מידע על המערכת והגרסה לדיווחי באגים.", + "settings.info.diagnostics.copy": "Copy to clipboard", + "settings.info.diagnostics.download": "Download .txt", + "settings.info.diagnostics.copied": "Diagnostic info copied to clipboard.", } as const diff --git a/packages/ui/src/lib/i18n/messages/ja/settings.ts b/packages/ui/src/lib/i18n/messages/ja/settings.ts index f764acbf..c69dd402 100644 --- a/packages/ui/src/lib/i18n/messages/ja/settings.ts +++ b/packages/ui/src/lib/i18n/messages/ja/settings.ts @@ -71,6 +71,7 @@ export const settingsMessages = { "settings.nav.remote": "Remote Access", "settings.nav.speech": "Speech", "settings.nav.opencode": "OpenCode", + "settings.nav.info": "Info", "settings.scope.device": "This device", "settings.scope.server": "Server setting", "settings.common.enabled": "Enabled", @@ -238,4 +239,26 @@ export const settingsMessages = { "sidecars.refresh": "Refresh", "sidecars.path": "Path", "sidecars.go": "Go", + + "settings.section.info.title": "About", + "settings.section.info.subtitle": "View version, runtime, and gather diagnostic information.", + "settings.info.version.server": "Server version", + "settings.info.version.ui": "UI version", + "settings.info.version.uiSource": "UI source", + "settings.info.runtime.type": "Runtime", + "settings.info.runtime.platform": "Platform", + "settings.info.runtime.os": "Operating system", + "settings.info.server.url": "Server URL", + "settings.info.server.root": "Workspace root", + "settings.info.updates.title": "Updates", + "settings.info.updates.subtitle": "Check for new releases of CodeNomad.", + "settings.info.updates.latest": "Latest version", + "settings.info.updates.download": "Download update", + "settings.info.updates.refresh": "Refresh", + "settings.info.diagnostics.copyFailed": "Clipboard access denied. Use the download button instead.", + "settings.info.diagnostics.title": "Diagnostics", + "settings.info.diagnostics.subtitle": "バグ報告用にシステム情報とバージョン情報を収集します。", + "settings.info.diagnostics.copy": "Copy to clipboard", + "settings.info.diagnostics.download": "Download .txt", + "settings.info.diagnostics.copied": "Diagnostic info copied to clipboard.", } as const diff --git a/packages/ui/src/lib/i18n/messages/ru/settings.ts b/packages/ui/src/lib/i18n/messages/ru/settings.ts index 2b9dc2f2..28fd6e23 100644 --- a/packages/ui/src/lib/i18n/messages/ru/settings.ts +++ b/packages/ui/src/lib/i18n/messages/ru/settings.ts @@ -71,6 +71,7 @@ export const settingsMessages = { "settings.nav.remote": "Remote Access", "settings.nav.speech": "Speech", "settings.nav.opencode": "OpenCode", + "settings.nav.info": "Info", "settings.scope.device": "This device", "settings.scope.server": "Server setting", "settings.common.enabled": "Enabled", @@ -238,4 +239,26 @@ export const settingsMessages = { "sidecars.refresh": "Refresh", "sidecars.path": "Path", "sidecars.go": "Go", + + "settings.section.info.title": "About", + "settings.section.info.subtitle": "View version, runtime, and gather diagnostic information.", + "settings.info.version.server": "Server version", + "settings.info.version.ui": "UI version", + "settings.info.version.uiSource": "UI source", + "settings.info.runtime.type": "Runtime", + "settings.info.runtime.platform": "Platform", + "settings.info.runtime.os": "Operating system", + "settings.info.server.url": "Server URL", + "settings.info.server.root": "Workspace root", + "settings.info.updates.title": "Updates", + "settings.info.updates.subtitle": "Check for new releases of CodeNomad.", + "settings.info.updates.latest": "Latest version", + "settings.info.updates.download": "Download update", + "settings.info.updates.refresh": "Refresh", + "settings.info.diagnostics.copyFailed": "Clipboard access denied. Use the download button instead.", + "settings.info.diagnostics.title": "Diagnostics", + "settings.info.diagnostics.subtitle": "Соберите сведения о системе и версии для отчетов об ошибках.", + "settings.info.diagnostics.copy": "Copy to clipboard", + "settings.info.diagnostics.download": "Download .txt", + "settings.info.diagnostics.copied": "Diagnostic info copied to clipboard.", } as const diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/settings.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/settings.ts index beae0a58..613a5ca5 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/settings.ts @@ -71,6 +71,7 @@ export const settingsMessages = { "settings.nav.remote": "Remote Access", "settings.nav.speech": "Speech", "settings.nav.opencode": "OpenCode", + "settings.nav.info": "Info", "settings.scope.device": "This device", "settings.scope.server": "Server setting", "settings.common.enabled": "Enabled", @@ -238,4 +239,26 @@ export const settingsMessages = { "sidecars.refresh": "Refresh", "sidecars.path": "Path", "sidecars.go": "Go", + + "settings.section.info.title": "About", + "settings.section.info.subtitle": "View version, runtime, and gather diagnostic information.", + "settings.info.version.server": "Server version", + "settings.info.version.ui": "UI version", + "settings.info.version.uiSource": "UI source", + "settings.info.runtime.type": "Runtime", + "settings.info.runtime.platform": "Platform", + "settings.info.runtime.os": "Operating system", + "settings.info.server.url": "Server URL", + "settings.info.server.root": "Workspace root", + "settings.info.updates.title": "Updates", + "settings.info.updates.subtitle": "Check for new releases of CodeNomad.", + "settings.info.updates.latest": "Latest version", + "settings.info.updates.download": "Download update", + "settings.info.updates.refresh": "Refresh", + "settings.info.diagnostics.copyFailed": "Clipboard access denied. Use the download button instead.", + "settings.info.diagnostics.title": "Diagnostics", + "settings.info.diagnostics.subtitle": "收集系统和版本信息,用于错误报告。", + "settings.info.diagnostics.copy": "Copy to clipboard", + "settings.info.diagnostics.download": "Download .txt", + "settings.info.diagnostics.copied": "Diagnostic info copied to clipboard.", } as const diff --git a/packages/ui/src/stores/settings-screen.ts b/packages/ui/src/stores/settings-screen.ts index e8ae4bf4..bf12e43f 100644 --- a/packages/ui/src/stores/settings-screen.ts +++ b/packages/ui/src/stores/settings-screen.ts @@ -1,6 +1,6 @@ import { createSignal } from "solid-js" -export type SettingsSectionId = "appearance" | "notifications" | "remote" | "speech" | "opencode" | "sidecars" +export type SettingsSectionId = "appearance" | "notifications" | "remote" | "speech" | "opencode" | "sidecars" | "info" const [settingsOpen, setSettingsOpen] = createSignal(false) const [activeSettingsSection, setActiveSettingsSection] = createSignal("appearance") diff --git a/packages/ui/src/styles/components/settings-info.css b/packages/ui/src/styles/components/settings-info.css new file mode 100644 index 00000000..37cab87b --- /dev/null +++ b/packages/ui/src/styles/components/settings-info.css @@ -0,0 +1,57 @@ +.settings-info-grid { + display: flex; + flex-direction: column; + gap: 0; +} + +.settings-info-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.72rem 0; + border-top: 1px solid color-mix(in oklab, var(--border-base) 78%, transparent); +} + +.settings-info-row:first-child { + border-top: none; + padding-top: 0; +} + +.settings-info-label { + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + color: var(--text-secondary); + flex-shrink: 0; +} + +.settings-info-value { + font-size: var(--font-size-sm); + color: var(--text-primary); + text-align: end; + font-family: var(--font-mono, monospace); + word-break: break-all; +} + +.settings-info-value-muted { + color: var(--text-muted); +} + +.settings-info-actions { + display: flex; + align-items: center; + gap: 0.625rem; + margin-top: 0.5rem; + flex-wrap: wrap; +} + +.settings-info-toast { + margin-top: 0.75rem; + padding: 0.625rem 0.875rem; + border: 1px solid var(--border-base); + background: color-mix(in oklab, var(--accent-primary) 10%, var(--surface-base)); + border-radius: 0; + color: var(--text-primary); + font-size: var(--font-size-sm); +} + diff --git a/packages/ui/src/styles/controls.css b/packages/ui/src/styles/controls.css index e7862d0a..3b42fec4 100644 --- a/packages/ui/src/styles/controls.css +++ b/packages/ui/src/styles/controls.css @@ -9,3 +9,4 @@ @import "./components/remote-access.css"; @import "./components/permission-notification.css"; @import "./components/settings-screen.css"; +@import "./components/settings-info.css";