Compare commits

...

6 Commits

Author SHA1 Message Date
Shantur Rathore
c7d4f99e48 fix(ui): prevent settings modal overflow on phones 2026-04-09 21:00:17 +01:00
Shantur Rathore
d50c00afb4 revert: remove debouncing and transparent window from zoom fix
Reverted debouncing logic and transparent window mode that were causing issues.
Kept the zoom step reduction from 0.2 to 0.1 for finer control.
2026-04-09 16:23:45 +01:00
Shantur Rathore
0ef57df3bc fix(ui): show token stats and simplify context window calculation
- Track messageInfoVersion in cache signature to rebuild when tokens arrive via SSE
- Read tokens from step-finish part directly (embedded in SSE events)
- Simplify available tokens to show full context window when no explicit input limit
2026-04-08 22:19:10 +01:00
Shantur Rathore
0739ec857c Reapply "fix(ui): support unified diff patch format in session changes viewer"
This reverts commit af6429162f.
2026-04-08 20:57:23 +01:00
Shantur Rathore
b060ab45ff Revert "feat(tauri): add zip bundle target for macOS and Windows"
This reverts commit 197898c01c.
2026-04-08 20:57:23 +01:00
Shantur Rathore
af6429162f Revert "fix(ui): support unified diff patch format in session changes viewer"
This reverts commit 2e9ee2cde6.
2026-04-08 20:57:12 +01:00
8 changed files with 68 additions and 65 deletions

View File

@@ -378,7 +378,7 @@ jobs:
- name: Build macOS bundle (Tauri)
working-directory: packages/tauri-app
run: npm exec -- tauri build --bundles app,zip
run: npm exec -- tauri build
- name: Package Tauri artifacts (macOS)
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
@@ -388,9 +388,7 @@ jobs:
ARTIFACT_DIR="packages/tauri-app/release-tauri"
rm -rf "$ARTIFACT_DIR"
mkdir -p "$ARTIFACT_DIR"
if [ -f "$BUNDLE_ROOT/macos/CodeNomad.app.zip" ]; then
mv "$BUNDLE_ROOT/macos/CodeNomad.app.zip" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-x64.zip"
elif [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then
if [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-x64.zip"
fi
@@ -464,7 +462,7 @@ jobs:
- name: Build macOS bundle (Tauri, arm64)
working-directory: packages/tauri-app
run: npm exec -- tauri build --bundles app,zip
run: npm exec -- tauri build
- name: Package Tauri artifacts (macOS arm64)
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
@@ -474,9 +472,7 @@ jobs:
ARTIFACT_DIR="packages/tauri-app/release-tauri"
rm -rf "$ARTIFACT_DIR"
mkdir -p "$ARTIFACT_DIR"
if [ -f "$BUNDLE_ROOT/macos/CodeNomad.app.zip" ]; then
mv "$BUNDLE_ROOT/macos/CodeNomad.app.zip" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-arm64.zip"
elif [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then
if [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then
ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-arm64.zip"
fi
@@ -553,7 +549,7 @@ jobs:
- name: Build Windows bundle (Tauri)
shell: bash
working-directory: packages/tauri-app
run: npm exec -- tauri build --bundles nsis,zip
run: npm exec -- tauri build
- name: Package Tauri artifacts (Windows)
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}
@@ -563,19 +559,10 @@ jobs:
$artifactDir = "packages/tauri-app/release-tauri"
if (Test-Path $artifactDir) { Remove-Item $artifactDir -Recurse -Force }
New-Item -ItemType Directory -Path $artifactDir | Out-Null
# Use Tauri-generated zip if available
$tauriZip = Get-ChildItem -Path "$bundleRoot/nsis" -Filter "*.zip" -File | Select-Object -First 1
if ($null -ne $tauriZip) {
$exe = Get-ChildItem -Path $bundleRoot -Recurse -File -Filter *.exe | Select-Object -First 1
if ($null -ne $exe) {
$dest = Join-Path $artifactDir ("CodeNomad-Tauri-$env:VERSION-windows-x64.zip")
Move-Item $tauriZip.FullName $dest -Force
} else {
# Fallback: manually zip the exe
$exe = Get-ChildItem -Path $bundleRoot -Recurse -File -Filter *.exe | Select-Object -First 1
if ($null -ne $exe) {
$dest = Join-Path $artifactDir ("CodeNomad-Tauri-$env:VERSION-windows-x64.zip")
Compress-Archive -Path $exe.Directory.FullName -DestinationPath $dest -Force
}
Compress-Archive -Path $exe.Directory.FullName -DestinationPath $dest -Force
}
- name: Upload Actions artifacts (Tauri Windows)
@@ -661,7 +648,7 @@ jobs:
- name: Build Linux bundle (Tauri)
working-directory: packages/tauri-app
run: npm exec -- tauri build --bundles appimage,deb,rpm
run: npm exec -- tauri build
- name: Package Tauri artifacts (Linux)
if: ${{ inputs.upload || inputs.upload_actions_artifacts }}

View File

@@ -11,14 +11,7 @@
"sync:version": "node ./scripts/sync-tauri-version.js",
"prebuild": "node ./scripts/prebuild.js",
"bundle:server": "npm run prebuild",
"build": "tauri build",
"build:mac": "tauri build --target universal-apple-darwin --bundles app,zip",
"build:mac-arm": "tauri build --target aarch64-apple-darwin --bundles app,zip",
"build:mac-intel": "tauri build --target x86_64-apple-darwin --bundles app,zip",
"build:mac-zip": "tauri build --target universal-apple-darwin --bundles zip",
"build:win": "tauri build --bundles nsis,zip",
"build:win-zip": "tauri build --bundles zip",
"build:linux": "tauri build --bundles appimage,deb,rpm"
"build": "tauri build"
},
"devDependencies": {
"@tauri-apps/cli": "^2.9.4"

View File

@@ -9,7 +9,7 @@ use serde_json::json;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
use tauri::webview::Webview;
@@ -32,12 +32,10 @@ use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
static LAST_ZOOM_TIME: Mutex<Option<Instant>> = Mutex::new(None);
const DEFAULT_ZOOM_LEVEL: f64 = 1.0;
const ZOOM_STEP: f64 = 0.1;
const MIN_ZOOM_LEVEL: f64 = 0.2;
const MAX_ZOOM_LEVEL: f64 = 5.0;
const ZOOM_DEBOUNCE_MS: u64 = 50;
#[cfg(windows)]
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
@@ -259,15 +257,6 @@ fn clamp_zoom_level(value: f64) -> f64 {
}
fn set_main_window_zoom(app_handle: &AppHandle, next_zoom: f64) {
if let Ok(mut last_zoom_time) = LAST_ZOOM_TIME.lock() {
if let Some(last_time) = *last_zoom_time {
if last_time.elapsed().as_millis() < ZOOM_DEBOUNCE_MS as u128 {
return;
}
}
*last_zoom_time = Some(Instant::now());
}
if let Some(window) = app_handle.get_webview_window("main") {
let normalized = clamp_zoom_level(next_zoom);
if window.set_zoom(normalized).is_ok() {

View File

@@ -23,7 +23,6 @@
"resizable": true,
"fullscreen": false,
"decorations": true,
"transparent": true,
"theme": "Dark",
"backgroundColor": "#1a1a1a",
"zoomHotkeysEnabled": true
@@ -53,11 +52,10 @@
],
"targets": [
"app",
"zip",
"appimage",
"deb",
"rpm",
"nsis"
]
}
}
}

View File

@@ -629,13 +629,12 @@ export default function MessageBlock(props: MessageBlockProps) {
const lastAssistantIdx = props.lastAssistantIndex()
const isQueued = current.role === "user" && (lastAssistantIdx === -1 || index > lastAssistantIdx)
// Intentionally untracked: messageInfoVersion updates should not trigger
// a full message block rebuild; record revision is the invalidation key.
const info = untrack(messageInfo)
const messageInfoVersion = props.store().state.messageInfoVersion[current.id] ?? 0
const cacheSignature = [
current.id,
current.revision,
messageInfoVersion,
isQueued ? 1 : 0,
props.showThinking() ? 1 : 0,
props.thinkingDefaultExpanded() ? 1 : 0,
@@ -647,6 +646,9 @@ export default function MessageBlock(props: MessageBlockProps) {
return cachedBlock.block
}
// Only capture info after cache check fails - ensures fresh data on version bump
const info = untrack(messageInfo)
const { orderedParts } = buildRecordDisplayData(props.instanceId, current)
const items: MessageBlockItem[] = []
const blockContentKeys: string[] = []
@@ -1108,17 +1110,23 @@ function StepCard(props: StepCardProps) {
return null
}
const info = props.messageInfo
if (!info || info.role !== "assistant" || !info.tokens) {
const part = props.part as any
// step-finish parts have tokens embedded; also check messageInfo
const partTokens = part?.tokens
const infoTokens = info && info.role === "assistant" ? info.tokens : undefined
const tokens = partTokens ?? infoTokens
if (!tokens) {
return null
}
const tokens = info.tokens
return {
input: tokens.input ?? 0,
output: tokens.output ?? 0,
reasoning: tokens.reasoning ?? 0,
cacheRead: tokens.cache?.read ?? 0,
cacheWrite: tokens.cache?.write ?? 0,
cost: info.cost ?? 0,
cost: (part?.cost ?? (info && info.role === "assistant" ? info.cost : 0)) ?? 0,
}
}

View File

@@ -334,7 +334,7 @@ const Field: Component<{
<div class="settings-toggle-title">{props.label}</div>
<div class="settings-toggle-caption">{props.caption}</div>
</div>
<div class="flex items-center gap-2 min-w-[18rem] max-w-[24rem] w-full">
<div class="flex items-center gap-2 w-full min-w-0 sm:min-w-[18rem] sm:max-w-[24rem]">
{props.icon}
<input
type={props.type ?? "text"}
@@ -361,7 +361,7 @@ const SelectField: Component<{
<div class="settings-toggle-title">{props.label}</div>
<div class="settings-toggle-caption">{props.caption}</div>
</div>
<div class="min-w-[18rem] max-w-[24rem] w-full">
<div class="w-full min-w-0 sm:min-w-[18rem] sm:max-w-[24rem]">
<select value={props.value} onInput={(event) => props.onInput(event.currentTarget.value)} class="selector-input w-full">
<For each={props.options}>{(option) => <option value={option.value}>{option.label}</option>}</For>
</select>

View File

@@ -116,18 +116,11 @@ export function updateSessionInfo(instanceId: string, sessionId: string): void {
// Prefer explicit input limits when provided by the API.
// This is used by the UI "Avail" chip.
contextAvailableTokens = modelInputLimit
}
if (!contextAvailableFromPrevious && contextAvailableTokens === null) {
if (contextWindow > 0) {
if (latestHasContextUsage && actualUsageTokens > 0) {
contextAvailableTokens = Math.max(contextWindow - (actualUsageTokens + outputBudget), 0)
} else {
contextAvailableTokens = contextWindow
}
} else {
contextAvailableTokens = null
}
} else if (contextWindow > 0) {
// When no explicit input limit, show full context window capacity.
contextAvailableTokens = contextWindow
} else {
contextAvailableTokens = null
}
setSessionInfoByInstance((prev) => {

View File

@@ -526,14 +526,49 @@
@media (max-width: 640px) {
.settings-screen-frame {
padding: 0;
overflow: hidden;
}
.modal-surface.settings-screen-shell {
width: 100%;
max-width: 100%;
height: 100%;
max-height: none;
min-height: 100%;
border-radius: 0;
overflow-x: hidden;
}
.modal-surface.settings-screen-shell .settings-screen-nav,
.modal-surface.settings-screen-shell .settings-screen-nav-list,
.modal-surface.settings-screen-shell .settings-screen-content,
.modal-surface.settings-screen-shell .settings-screen-scroll,
.modal-surface.settings-screen-shell .settings-section-stack,
.modal-surface.settings-screen-shell .settings-stack,
.modal-surface.settings-screen-shell .settings-card,
.modal-surface.settings-screen-shell .settings-card-content,
.modal-surface.settings-screen-shell .settings-toggle-row,
.modal-surface.settings-screen-shell .settings-toggle-row > * {
min-width: 0;
}
.modal-surface.settings-screen-shell .selector-trigger,
.modal-surface.settings-screen-shell .selector-input,
.modal-surface.settings-screen-shell .selector-button {
min-width: 0;
max-width: 100%;
}
.modal-surface.settings-screen-shell .settings-toggle-caption,
.modal-surface.settings-screen-shell .settings-inline-note,
.modal-surface.settings-screen-shell .remote-address-url,
.modal-surface.settings-screen-shell code {
overflow-wrap: anywhere;
word-break: break-word;
}
.modal-surface.settings-screen-shell .whitespace-nowrap {
white-space: normal;
}
.settings-screen-content-header,