Release notes

FEATURE

Windows interface language auto-detection

Windows users now get the user interface in their familiar system language right out of the box, without having to tweak any settings manually. Previously, if you didn't explicitly set environment variables (hidden configurations stuffed into the system before a program runs), the language detection only knew how to ask macOS (NSLocale) for the language and always awkwardly fell back to English on Windows. It now maps GetUserDefaultUILanguage() through locale.windows_locale, so whether your Windows UI is in zh-TW, zh-CN, ja, or ko, the app matches it instantly. Environment variables still take precedence if you want to override it. It's like walking into a global chain store's local branch—clerks used to greet everyone in English unless you wore a name tag requesting otherwise, but now they simply look at where they are and speak your native language the moment you walk in.

BUGFIX

The Windows tray finally starts perfectly

Windows users can now successfully open the app from the system tray (the row of small icons in the bottom right of the Windows screen) without it silently crashing before it even appears. Previously, starting the tray on Windows inadvertently tried to import the macOS-exclusive objc module, raising a ModuleNotFoundError; since the app's fallback (a backup path that automatically switches to another available mode when a feature fails) only knew how to handle a missing wintray module, it just quietly aborted without showing any window. Furthermore, when building the panel-switching menu, pystray would reject the action due to an extra defaulted positional parameter (a variable passed by position), raising a ValueError; and when exposing the controller to the JS bridge (the communication link between the web frontend and Python backend), pywebview serialized every public attribute, which recursed through the WinForms window graph until hitting the limit. Now, the objc module is strictly loaded only when needed (lazy import), the fallback gracefully degrades to a text interface (TUI) while printing the exact missing module. The menu parameter bug is fixed by making the panel-id binding keyword-only, and the recursion loop is resolved by keeping the controller in a private attribute. It's like trying to start a car whose engine used to flood and die silently if a single unrelated part was missing; now the ignition purrs to life perfectly, and if something does break, it at least lights up a specific warning on the dashboard.

FEATURE

Windows project paths no longer display errors

Project names and folder paths on Windows are no longer bizarrely misread by the app. Because Windows paths can be a bit messy, if your current working directory (cwd) was recorded using POSIX-style cwds (paths using forward slashes like C:/... typically seen on Mac or Linux) or if it used a dash-encoded name (a format that replaces slashes with hyphens to safely create filenames), the system stubbornly tried to split them strictly by Windows backslashes. This resulted in it keeping the entire long path string as the "project name" or completely failing to decode paths because a bare backslash lacks a drive letter. Now, it smartly normalizes all separators on Windows before splitting, and correctly anchors decoded paths to drive roots (like C:\), restoring your actual project folder flawlessly. It's like a mail carrier who used to get utterly confused and fail to deliver your mail if your address was written with commas instead of standard line breaks; now they automatically clean up your address format in their head and drop the package at the correct house every time.

Original changelog

Original changelogWindows interface language auto-detection
**Windows auto-detects the interface language**: with no `USAGE_LANG`/`TT_LANG`/`LANG` set, language detection only knew how to ask macOS (`NSLocale`) and always fell back to English on Windows. It now maps `GetUserDefaultUILanguage()` through `locale.windows_locale`, so a zh-TW / zh-CN / ja / ko Windows UI gets the matching interface out of the box. Environment variables still take precedence.
Original changelogThe Windows tray finally starts perfectly
**The Windows tray now actually starts**: importing the tray pulled in the `panels` package, whose `__init__` eagerly imported the PyObjC-backed `panels.web_panel`; on Windows this raised `ModuleNotFoundError: No module named 'objc'`, and because the TUI fallback in `main.py` only recognized a missing `wintray` module, the windowed build exited silently with nothing on screen. The panel registry now imports `web_panel` lazily inside `all_panels()` (macOS behavior unchanged), and the fallback degrades to the TUI whenever any module in the tray's import chain is missing, printing the missing module's name.
**The tray menu and JS bridge no longer break at startup**: pystray rejects menu actions whose lambda carries an extra defaulted positional parameter, so building the panel-switch submenu raised `ValueError` before the icon ever appeared; and pywebview serializes every public attribute of the `js_api` object into the JS bridge, so exposing the tray controller on it recursed through the WinForms window graph until the recursion limit. The panel-id binding is now keyword-only, and the bridge holds the controller in a private attribute.
Original changelogWindows project paths no longer display errors
**Project names derive correctly from POSIX-style cwds on Windows**: `usage_session_resume._project_from_cwd` and `adapters.claude.project_from_cwd` split paths on `os.sep` only, so a transcript cwd recorded with forward slashes (or a `C:/...`-style path) kept the whole path as the "project" on Windows. Both now normalize separators on Windows before splitting; POSIX behavior is unchanged, since backslashes are legal in POSIX filenames.
**Encoded project-path decoding now works on Windows**: `project_from_encoded_path` reconstructed a project's real directory from its dash-encoded name by searching from the filesystem root, but a bare Windows `\` has no drive letter and never matches a real path, so decoding always fell back to the raw encoded string. It now anchors at the drive root (e.g. `C:\`) when the encoded name starts with one; POSIX behavior is unchanged.