v0.10.0
Changelog
All notable changes to this project will be documented in this file.
v0.10.0 - 2026-05-13
Features
-
Feat: update version to 0.10.0 and enhance changelog with new features and improvements
-
Feat: localise all UI strings for NVDA translator
Add # TRANSLATORS: comments to all translate() and _() calls across:
- ui/session_state.py — provider labels, status messages, guidance
- service/error_presentation.py — error titles and messages
- service/provider_readiness.py — provider display names
- plugin/controller.py — all script descriptions
- plugin/application.py — use case titles, provider toggle, layer help
- plugin/background.py — model preload messages (f-strings → _())
- plugin/layer_mode.py — layer activation and error messages
- plugin/presenter.py — empty result message
Add missing WebUI t() keys to Python localized_strings:
- response_streaming_subtitle, attachment_load_failed, attachments_added_status
Fix WebUI attachment strings to use static fallback + .replace()
instead of template literals for proper localisation support.
- Feat: improve chat error UX with inline error messages, composer text restoration, and attach-to-current fix
User-facing changes:
- Add to current chat now injects the summary/description as an assistant message
into the currently active conversation (was silently opening the window) - Provider errors during chat are shown as inline error blocks in the transcript
instead of a display-mode overlay that hid the conversation - The user's message is restored in the composer so they can retry without retyping
- Error streaming state is properly cleaned up via chat_stream_abort
- New ErrorBlock content type for styled error rendering in the WebView
Internal changes:
-
StreamProjection.abort(reason) for clean teardown of in-flight streams
-
AttachToCurrentAction carries a token for seed text storage/resolution
-
Error metadata flows through chat_append metadata instead of show_error
-
ErrorPresentation type imported and used for content block construction
-
Full edge-case test coverage for attach-to-current flow
-
Changelog updated for v0.9.3
-
Feat(protocol): add canonical YAML spec and code generator
Replace hand-maintained command/event enums across Python, Rust, and
TypeScript with a single canonical YAML definition
(scripts/protocol.yaml) and a generator script
(scripts/generate_protocol.py).
Generated outputs:
- addon/.../ui/host_protocol_constants.py (Python string constants)
- nvda_ui_host/src/protocol_commands.rs (Rust CommandName/EventName
enums + UiCommand impl with match arms) - nvda_ui_host/webui/.../protocol-commands.ts (TS type unions)
Wire protocol.rs to include! the generated Rust enums (506 -> 373
lines). Wire host_protocol.py to import from generated constants with
standalone importlib fallback for tests.
Token savings: 14 commands + 14 events now defined once instead of
across 3 languages. Adding a command = edit protocol.yaml + regenerate.
- Feat(ui): add AttachToCurrent action for zero-token conversation attachment
Add AttachToCurrentAction that opens chat attached to the current
conversation without requiring a token roundtrip through the
ResultActionStore. This appears alongside OpenChat in result actions,
giving users a lightweight option when they don't need seed text.
- Feat(config): encrypt API keys at rest using Windows DPAPI
Store Gemini and OpenAI API keys encrypted via Windows DPAPI (crypt32.dll
via ctypes) instead of plaintext in config.yaml. Existing keys are
automatically migrated on next save.
-
New utils/crypto.py with lazy ctypes DPAPI bindings
-
YamlConfigStore transparently encrypts on save, decrypts on load
-
CRYPTPROTECT_UI_FORBIDDEN flag prevents UI hangs on NVDA main thread
-
Sensitive keys detected by ApiKey/ApiToken suffix
-
Feat: update documentation and instructions for TypeScript migration, command handling, and Copilot integration
Fixes
- Fix(ollama): stop auto-pulling model when not found locally
Change ensureModelInstalled to raise an error instead of
automatically calling ollama pull when the model is missing.
Users must now run 'ollama pull ' manually.
Improvements
- Docs: add behavioral specs for protocol, stream projection, and presentation intent
Create docs/specs/ as the canonical source of truth for cross-layer
contracts. These specs serve as safety nets for refactoring, onboarding
for coding agents, and code-generation targets.
- protocol-contract.md: all 14 commands, 14 events, envelope rules
- stream-projection.md: StreamProjection lifecycle and normalization
- presentation-intent.md: typed UI intent metadata and builder patterns
- README.md: spec index and maintenance instructions
Refactoring
- Refactor(providers): extract shared HTTP utilities from Ollama and OpenAI clients
Create providers/_http_utils.py with three generic helpers that consolidate
nearly identical patterns duplicated across provider API clients:
- parse_json_response(raw, path, provider) — JSON parsing with descriptive errors
- read_error_body(error) — HTTPError body extraction
- request_json_with_retry(make_request, ...) — retry loop with timeout/backoff
Ollama http.py: -83 lines (delegates _parseJSON, _readErrorBody, _requestJSON
to shared utils; keeps _requestPullStream as Ollama-specific).
OpenAI client.py: -46 lines (delegates _parse_json, _read_error_body,
_request_json to shared utils; keeps SSE streaming as OpenAI-specific).
Gemini client.py is not affected — its _request_json pattern differs
(pre-built Request, SSL context, no HTTPError retry, different error
hierarchy).
Net: ~129 lines removed from provider clients, 112 shared lines added.
- Refactor(plugin): extract ModelCache from UseCasePresenter
Move thread-safe model cache (RLock, {provider -> models} dict, async
refresh with stale-provider guards) into dedicated plugin/model_cache.py.
UseCasePresenter now delegates to ModelCache via _get_cached_models and
_refresh_available_models_async thin wrappers plus an _on_models_cached
callback that syncs UI session state.
Removes threading import and lock boilerplate from presenter.py.
- Refactor(host): split webview.rs into focused sub-modules
Extract three concerns from the monolithic 636-line webview.rs into
dedicated files included via include!():
- webview_state.rs (114 lines): static globals, thread-local controller,
WebViewEvent enum, ready-transition logic, test helpers - webview_delivery.rs (123 lines): message send/flush/queue pipeline,
asset embedding (host.html/css/js) - webview_events.rs (61 lines): JS event parsing and dispatch
webview.rs (358 lines) keeps: public API (resize, focus, post_host_command,
notify_window_visible), init_webview, and the test module.
No functional changes. All 5 webview tests pass.
- Refactor: align legacy EVENT_HOST_CLOSED with canonical close_host spec
Drop the manual EVENT_HOST_CLOSED constant and align all layers
with the spec-driven 'close_host' event name:
-
Rust (window.rs, ipc/state.rs): emit 'close_host' instead of 'host_closed'
-
Python (host_protocol.py): import EVENT_CLOSE_HOST from generated constants
-
Python (host_renderer.py, test_host_protocol.py): EVENT_HOST_CLOSED -> EVENT_CLOSE_HOST
-
TypeScript (bridge.ts): type COMMANDS/CONTROL_COMMANDS with generated CommandName
-
TypeScript (protocol-types.ts): re-export CommandName, EventName, CHAT_COMMANDS
-
Refactor(host): consolidate verbose log messages in app dispatch
Replace multi-line log format strings in handle_command and
handle_raw_message with compact single-line log entries.
- Shorten log prefix from 'Host app ...' to 'app ...'
- Merge separate info-level forwarding and policy log lines into one
- Compress debug-level preview logs to single line
- Combine activation_policy and focus_request logging
Reduces app.rs from 294 to 272 lines and improves readability of
the command dispatch path without changing behavior.
- Refactor(ui): extract session TypedDicts to session_types module
Split TypedDict type definitions from session_state.py (318→290 lines)
into a new session_types.py module (56 lines). Consumers that only need
the type shapes can now import from session_types without loading the
builder logic and localized string tables.
-
Move SessionProviderInfo, SessionProviderOption, SessionProviderStatus,
SessionConversationSummary, UISessionMetadata TypedDicts to new module -
Keep UISessionState dataclass in session_state.py (has to_metadata method)
-
session_state.py imports types from session_types.py
-
Refactor(ui): extract StreamProjection from adapter to dedicated module
Move _StreamedAssistantProjection and _normalize_stream_fragment from
ui/adapter.py (558→427 lines) into a new ui/stream_projection.py module.
- Rename _StreamedAssistantProjection → StreamProjection (public API)
- Rename _normalize_stream_fragment → normalize_stream_fragment
- Add module docstring referencing spec at docs/specs/stream-projection.md
- Adapter now imports StreamProjection instead of defining it inline
This reduces the token footprint for agents reading adapter.py by ~130
lines and gives the streaming projection logic a clear, testable home.
- Refactor(webui): migrate to TypeScript with Svelte 5 clean architecture
Rewrite the WebView UI layer from JavaScript to TypeScript with
a clean architecture that separates concerns and eliminates fragile
reactivity patterns:
-
Extract Transcript class into transcript.svelte.ts with
for inherent Svelte 5 reactivity, removing dependency on external
\ proxy wrapping -
Remove self-referential getter/setter on chat.messages in favor
of direct appState.chat.transcript.messages access (2 fewer
proxy hops in the reactivity chain) -
Split monolithic bridge.js into modular command handlers
(commands/), operations (operations/), and typed protocol
types (protocol-types.ts) -
Add CONTROL_COMMANDS set to bridge.ts so control state
extraction only runs for open_chat, sync_session, and
render_display — content-only streaming commands no longer
trigger wasted updateControlState() calls -
Replace manual bumpChatRenderVersion() with \ from
transcript.count for auto-scroll -
Add JS console.log forwarding to Rust log via webview.rs
for end-to-end debugging visibility -
Extract keyboard shortcuts into dedicated shortcuts.ts module
-
Add tsconfig.json and update vite.config.js for TypeScript
-
Add rust-side web_ui_ready safety net in NavigationCompleted
-
Refactor code style to use double quotes consistently, update state management, and add configuration files for biome
SHA256
ff344279bfd86759fd10e4235743abe1c7a50e52000fca8a20cde063f03a56ee AIAssistant-0.10.0.nvda-addon