Compare commits

..

No commits in common. "f19dc7c26cf3b813707c94ab5f6596a31f896d5d" and "3a051c8012119cc1abd931d5587995b052ae6170" have entirely different histories.

13 changed files with 90 additions and 810 deletions

View File

@ -1,492 +0,0 @@
# Dev Cheat Sheet
Concise command reference for day-to-day work across Git, Python, Rust, Node, Tauri, and .NET.
Use this as a practical command sheet, not as a full tutorial.
## Table of Contents 📑
- [Setup](#setup)
- [Git](#git)
- [PAQ-Next (Advanced Compression)](#paq-next-advanced-compression)
- [Python](#python)
- [Node and npm](#node-and-npm)
- [Rust](#rust)
- [Tauri v2](#tauri-v2)
- [Dotnet and CSharp](#dotnet-and-csharp)
- [Common Output Folder Pattern](#common-output-folder-pattern)
- [Docker](#docker)
- [Useful Cleanup Commands](#useful-cleanup-commands)
- [Recommended Habits](#recommended-habits)
- [Workflows and SDT DevTool](#workflows-and-sdt-devtool)
## Setup
### Setup w64devkit for C++ (PAQ-Next)
```powershell
$env:PATH = "f:\w64devkit\w64devkit\bin;" + $env:PATH
```
Adds GCC/G++ and Unix tools to the current PowerShell session.
### Probe toolchain availability
```powershell
python scripts/diag.py probe --tool dotnet --json
python scripts/diag.py probe --tool python --json
python scripts/diag.py probe --tool node --json
python scripts/diag.py probe --tool npm --json
python scripts/diag.py probe --tool cargo --json
python scripts/diag.py probe --tool tauri --json
python scripts/diag.py probe --tool git --json
```
Checks if required tools are in the current path.
### Shell bootstrap (doctor)
```powershell
python scripts/dev_shell.py doctor
```
Analyzes the environment and reports missing components or configuration errors.
[Back to Top](#dev-cheat-sheet)
## Git
### Check where you are
```powershell
git status --short --branch
git remote -v
git branch -vv
# See which files are ignored by git
git clean -nd
```
### Stashing
```powershell
git stash push -m "Description"
git stash pop
git stash drop # Discard
git stash clear # Wipe all stashes
```
### Remotes & Pushing - THE FLAGS
```powershell
# Safer 'force push' (fails if remote has moved)
git push --force-with-lease
# Push all branches and all tags at once
git push --all
git push --tags
# Delete a remote branch
git push origin --delete branch-name
# Set upstream for current branch (first push)
git push -u origin branch-name
```
### History & Analysis
```powershell
# Graphical log in terminal
git log --graph --oneline --decorate --all
# Search commit messages
git log --grep="bug fix"
# Find which commit introduced a string
git log -S "secret_key"
# See changes per line (last change for every line)
git blame file_path.py
```
### Git Repository Cleanup
```powershell
# Fast cleanup of untracked files
git clean -fd
# Prune stale remote tracking branches
git fetch origin --prune
```
 
[🔼 Back to Top](#dev-cheat-sheet)
## PAQ-Next (Advanced Compression)
### Compile PAQ-Next (C++)
```powershell
# Add toolchain first (if not in path)
$env:PATH = "f:\w64devkit\w64devkit\bin;" + $env:PATH
# Build from root of src-paq-next/
g++ -O3 -mavx2 -Iinclude cli/main.cpp src/archiver.cpp -o paq_next_cli.exe
```
Compiles with AVX2 optimizations and include-path support.
### Run PAQ-Next Compression
```powershell
# Compress a folder
.\paq_next_cli.exe c <source_folder>
# Compress a folder to a specific file
.\paq_next_cli.exe c <source_folder> <output_name>.paq
# Extract an archive
.\paq_next_cli.exe x <archive>.paq <destination_folder>
```
[Back to Top](#dev-cheat-sheet)
## Python
### Package Management - THE FLAGS
```powershell
# Install from requirements with upgrades
pip install -r requirements.txt --upgrade
# Install without saving to cache (Great for CI/Low disk)
pip install -r requirements.txt --no-cache-dir
# Target a specific directory
pip install -t ./lib <package>
# Search local packages for a string
pip list | Select-String "crypto"
# See everything about a package
pip show <package-name>
```
### Quality & Performance
```powershell
# Parallel testing
pip install pytest-xdist
pytest -n auto # Use all CPU cores
# Benchmark a script
python -m cProfile -s time script.py
```
&nbsp;
[🔼 Back to Top](#dev-cheat-sheet)
## Node and npm
### Dependency Management - THE FLAGS
```powershell
# Force install (when versions conflict)
npm install --force
# Ignore peer dependency errors (Common in React)
npm install --legacy-peer-deps
# Production only (Skip devDependencies)
npm install --production
# Deep security audit and auto-fix
npm audit fix --force
```
### Scripts & Performance
```powershell
# Build with specific log level
npm run build --loglevel silent
# Pass custom flags directly to a script
npm run dev -- --host 0.0.0.0 --port 3000
```
&nbsp;
[🔼 Back to Top](#dev-cheat-sheet)
## Rust
### Essential Workflow - THE FLAGS
```powershell
# Fast check (Skip code generation)
cargo check --all-targets --all-features
# Build with specific features enabled
cargo build --features "feature-a,feature-b"
# Build without default features
cargo build --no-default-features
# Cross-compilation (Requires target in toolchain)
cargo build --target x86_64-apple-darwin
cargo build --target aarch64-unknown-linux-gnu
```
### Diagnostics & Performance
```powershell
# See exactly why a build is slow
cargo build --timings
# Run specific test by name/substring
cargo test test_function_name
# Open local docs for all dependencies
cargo doc --open --no-deps
```
&nbsp;
[🔼 Back to Top](#dev-cheat-sheet)
## Tauri v2
### Initialize Mobile (REQUIRED for first run)
```powershell
# This creates the android/ios project folders in src-tauri/gen
npm run tauri android init
npm run tauri ios init
```
### Desktop Builds - THE FLAGS
```powershell
# Fast build (Binary only)
npm run tauri build -- --no-bundle
# Explicit config file (Great for multi-env)
npm run tauri build -- --config path/to/config.json
# Verbose output for debugging packaging
npm run tauri build -- --verbose
```
### Mobile Builds
```powershell
# Debug mode on Android device (or Emulator)
npm run tauri android dev
# Production APK/AAB generation
npm run tauri android build -- --target aarch64
```
### Troubleshooting
```powershell
# System diagnostic report
npm run tauri info
```
&nbsp;
[🔼 Back to Top](#dev-cheat-sheet)
### Frontend-only build
```powershell
npm run build
```
Builds the web frontend assets (the `dist/` folder) without launching or packaging the desktop app.
## Dotnet and CSharp
### Restore & Build - THE FLAGS
```powershell
# Restore from specific NuGet source
dotnet restore --source https://api.nuget.org/v3/index.json
# Build without restoring (Great for build script repetition)
dotnet build --no-restore
# Build with minimal output (Keep it clean)
dotnet build --verbosity quiet # or minimal, normal, detailed, diagnostic
```
### Run & Watch
```powershell
# Run with custom environment variables
$env:ASPNETCORE_ENVIRONMENT="Development"; dotnet run
# Watch with specific project filter
dotnet watch --project Project.csproj
```
### Advanced Publishing
```powershell
# Single-File for Linux (Self-Contained)
dotnet publish -c Release -r linux-x64 --self-contained true `
-p:PublishSingleFile=true `
-p:IncludeNativeLibrariesForSelfExtract=true `
-o ./output/linux-x64-single
# Ready-To-Run (R2R) - Fast startup at the cost of size
dotnet publish -c Release -o ./output -p:PublishReadyToRun=true
```
### Tools
```powershell
# Install a dotnet tool globally
dotnet tool install -g <tool-name>
```
&nbsp;
[🔼 Back to Top](#dev-cheat-sheet)
## Common Output Folder Pattern
Use explicit output folders so results are easy to find and easy to clean up.
```powershell
./output/
./output/win-x64/
./output/win-x64-single/
./output/linux-x64/
./output/linux-x64-single/
```
[Back to Top](#dev-cheat-sheet)
## Docker
### Build & Run
```powershell
docker build -t app-name .
docker run -p 8080:80 app-name
```
### Docker System Cleanup
```powershell
docker system prune -a # Wipe everything unused
docker container prune
docker image prune
```
[Back to Top](#dev-cheat-sheet)
## Useful Cleanup Commands
### Remove Node artifacts
```powershell
Remove-Item -Recurse -Force .\node_modules
Remove-Item -Recurse -Force .\dist
```
### Remove Rust / Tauri artifacts
```powershell
Remove-Item -Recurse -Force .\src-tauri\target
```
### Remove .NET artifacts (Deep clean)
```powershell
Get-ChildItem -Include bin,obj,output -Recurse | Remove-Item -Recurse -Force
```
### Remove Python artifacts
```powershell
Remove-Item -Recurse -Force .\.venv
Get-ChildItem -Include __pycache__ -Recurse | Remove-Item -Recurse -Force
```
[Back to Top](#dev-cheat-sheet)
## Recommended Habits
Before any risky Git action:
```powershell
git status --short --branch
git branch backup/pre-risk
git tag pre-risk-YYYY-MM-DD
git fetch origin --prune
```
Before publishing builds:
```powershell
npm run build
cargo test
dotnet test
```
Adjust to the stack used by the current project.
[Back to Top](#dev-cheat-sheet)
## Workflows and SDT DevTool
### Build / Run SDT (DevTool)
```powershell
# Quick build
python scripts/dotnet-min.py build
# Standard run
dotnet run --project DevTool.csproj
```
### Route & Workflow Verification
```powershell
# Static check
python scripts/verify-workflow-routes.py --project-root .
# Headless execution test
python scripts/verify-workflow-routes.py --project-root . --workflow build --workflow tauri --execute --env-profile dev
```
### Build & Sync Output
```powershell
# Publish web assets + app
python scripts/publish-app.py --target web
# Publish specific projects
python scripts/publish-sidecar.py --project path/to/sidecar.csproj
python scripts/publish-webgateway.py --project path/to/gateway.csproj
# Sync all results to central output/
python scripts/sync-output.py
```
### Database Migration Gate
```powershell
python scripts/migration-gate.py
```
### NuGet / Node Cleanup
```powershell
# NuGet cache export
python scripts/nuget-export-cache.py --output-zip cache-export.zip
# Clean node_modules safely
python scripts/npm-clean.py --working-dir .
```
[Back to Top](#dev-cheat-sheet)

View File

@ -1,6 +1,6 @@
# Legacy Hardware Validation
This report exists to close the remaining release-candidate validation work against the target profile: older desktop hardware with limited RAM where Thisper must remain responsive and low-overhead.
This report exists to close the remaining Phase 2 validation work against the target profile: older desktop hardware with limited RAM where Thisper must remain responsive and low-overhead.
## Validation Checklist
@ -13,46 +13,40 @@ This report exists to close the remaining release-candidate validation work agai
### Repeated Cross-App Rewrite
- Scenario: Use `Ctrl + Alt + R` repeatedly across plain-text targets such as Notepad, browser text boxes, and lightweight game chat surfaces.
- Scenario: Use `Ctrl + Alt + R` repeatedly across plain-text targets such as Notepad and browser text boxes.
- Acceptance target: No destructive replacement on provider failure, no clipboard corruption after failures, stable repeated use without restart.
- Current evidence: Informal real-world usage is positive across multiple programs and even some games. One known host-app incompatibility exists where the T3 Chat app steals focus on the hotkey path.
- Status: `partially validated`
- Current evidence: Informal real-world usage is positive, but no structured pass is recorded yet.
- Status: `remaining`
### Long-Form Desktop Rewrite
- Scenario: Rewrite a multi-paragraph block through the main window and inspect diff/output toggles.
- Acceptance target: UI remains responsive during rewrite and the diff view reflects only actual edits.
- Current evidence: Main UI rewrite has been used successfully in normal writing flow, but no structured timing pass is logged yet.
- Status: `partially validated`
- Current evidence: Main UI build path is working and has been used successfully, but no formal timing pass is logged yet.
- Status: `remaining`
### Successive Rewrites Without Restart
- Scenario: Perform multiple rewrites in sequence from both the main UI and the global shortcut flow.
- Acceptance target: No cumulative instability, no stale model state, no stuck loading state, and no credential-loss regression after restart.
- Current evidence: Runtime observability and secure key persistence are in place. Informal usage is positive, but the pass itself is not fully recorded yet.
- Status: `partially validated`
- Acceptance target: No cumulative instability, no stale model state, no stuck loading state.
- Current evidence: Runtime observability is now present to support this pass, but the pass itself is still pending.
- Status: `remaining`
### Resource Footprint
- Scenario: Observe memory use and responsiveness on constrained hardware during idle, active rewrite, and tray-hidden states.
- Acceptance target: No runaway memory growth and acceptable perceived latency for short text.
- Current evidence: Development footprint is dominated by build artifacts in `target/`, so the meaningful missing data is runtime memory and release-build behavior on older hardware.
- Current evidence: Build/runtime artifacts are large in development, but that is mostly `target/`; release footprint still needs explicit validation.
- Status: `remaining`
## Known Compatibility Notes
- `T3 Chat`: hotkey path can defocus the text box because the host app reacts to the shortcut first
- rich editors and secure fields: still best-effort and may not behave like plain-text targets
## What To Record During The Pass
- idle memory use
- active rewrite latency for short and medium inputs
- whether the tray/hotkey path remains responsive after long idle time
- whether clipboard restoration remains correct after both success and failure
- whether secure credential persistence survives multiple app restarts
- any target app classes that consistently fail or behave inconsistently
## Completion Rule
The desktop release candidate is fully validated when each scenario above has an explicit observed result recorded here, including failures or caveats.
Phase 2 desktop validation is complete when each scenario above has an explicit observed result recorded here, including failures or caveats.

View File

@ -2,29 +2,12 @@
Thisper is a typing-first communication translator. It rewrites raw text into clearer text while staying as close as possible to the original meaning, voice, uncertainty, and tone.
## Release Candidate Status
Thisper is currently a `desktop typed-workflow release candidate`.
What is considered in-scope and ready for release-candidate use:
- desktop text-to-text rewrite workflow
- cross-app selected-text rewrite via `Ctrl + Alt + R`
- tray/background operation
- native secure API key persistence
- protected URL preservation
What still remains before a broader general release:
- structured legacy hardware validation
- final commercial license allowlist decision
## Current Desktop Scope
- Desktop text-to-text rewrite UI with diff review
- Rewrite modes: `Preserve Voice`, `Clean`, `Readable`, `Formal`, `Concise`
- Gemini-backed cloud rewrite provider with typed model selection
- Secure Gemini API key storage through native platform protection
- Secure Gemini API key storage through the system credential store
- Global cross-app rewrite shortcut: `Ctrl + Alt + R`
- System tray support with background operation
- Non-destructive clipboard replacement flow: selected text is only replaced after the full rewrite succeeds
@ -67,7 +50,6 @@ If no API key is configured, Thisper brings the main window forward instead of a
- Secure fields and some application surfaces may block selection, copy, paste, or synthetic key events.
- Clipboard restoration currently preserves prior plain-text clipboard contents. Non-text clipboard formats are not yet restored.
- If a rewrite attempts to drop a protected URL, Thisper now fails that rewrite instead of silently removing the link.
- Some host apps can intercept the hotkey before Thisper sees it. The known example so far is the T3 Chat app defocusing its own text box.
- Model fidelity still depends on prompt behavior. High-value detail protection is planned, but not part of the current Phase 2 completion scope.
## Setup
@ -80,10 +62,7 @@ If no API key is configured, Thisper brings the main window forward instead of a
### Credentials
Use the in-app Settings dialog to save the Gemini API key using native platform protection.
- Windows: DPAPI-encrypted local app storage tied to your Windows user account
- Other supported desktop platforms: native credential store backend
Use the in-app Settings dialog to save the Gemini API key into your system credential store.
For development, Thisper also accepts:
@ -125,8 +104,6 @@ Current status:
See:
- `RELEASE_CANDIDATE.md` for the current desktop release-candidate lock
- `THISPER_STATUS.md` for current completion status
- `THISPER_IMPLEMENTATION_PLAN.md` for the release-candidate scope
- `LEGACY_HARDWARE_VALIDATION.md` for the Phase 2 validation checklist and report
- `PHASE3_SPEECH_PLAN.md` for the queued next-phase speech scope

View File

@ -1,36 +0,0 @@
# Thisper Release Candidate
Current designation: `RC1 candidate`
This document locks the current desktop typed workflow into a release-candidate state.
## Included In RC1
- desktop text-to-text rewrite UI
- diff review
- explicit rewrite modes
- cross-app selected-text rewrite via `Ctrl + Alt + R`
- tray/background operation
- native secure API key persistence
- protected URL preservation
- local-only runtime observability
## Known Issues Accepted In RC1
- some host apps may intercept the hotkey before Thisper can act
- rich editors and secure fields remain best-effort
- non-text clipboard formats are not restored yet
- broader protected-term handling is not implemented yet
## Exit Criteria Before General Release
- legacy hardware validation report completed
- commercial license allowlist decision finalized
- Rust license audit policy aligned with that decision
## Primary Reference Docs
- `README.md`
- `THISPER_STATUS.md`
- `THISPER_IMPLEMENTATION_PLAN.md`
- `LEGACY_HARDWARE_VALIDATION.md`

View File

@ -1,41 +1,18 @@
# Thisper Implementation Plan
This plan reflects the current repo state and the work needed to move the desktop typed workflow from release candidate to general release.
This plan reflects the current repo state and the remaining work needed to close desktop Phase 2 before speech begins.
## Current State
## Completed Baseline
- Desktop text-to-text MVP is complete.
- The provider boundary exists and Gemini is integrated.
- Native secure credential storage is implemented.
- Secure credential storage is implemented.
- Global shortcut and clipboard rewrite are implemented.
- Streaming rewrite is implemented.
- Tray and background lifecycle are implemented.
- Runtime observability is implemented locally in-memory.
- Protected URL preservation is implemented.
## Release Candidate Scope
The current release candidate covers the desktop typed workflow only.
### Included
- main rewrite UI
- explicit rewrite modes
- diff review
- copy output
- secure settings for the Gemini API key
- `Ctrl + Alt + R` selected-text rewrite
- tray/background operation
- native secure credential handling
- protected URL preservation
### Known Limits Accepted For The Release Candidate
- rich editors and some browser surfaces are still best-effort
- non-text clipboard formats are not restored yet
- host-app hotkey conflicts can still occur in specific apps
- broader high-value term protection is not implemented yet
## Remaining To Close Before General Release
## Remaining To Close Phase 2
### 1. Validation
@ -48,10 +25,34 @@ The current release candidate covers the desktop typed workflow only.
- Decide the commercial allowlist policy.
- Update Rust audit configuration to match that policy.
## Current Desktop Contract
### Main UI
- typed input and output panes
- explicit rewrite modes
- diff review
- copy output
- secure settings for the Gemini API key
### Cross-App Utility
- `Ctrl + Alt + R` triggers selected-text rewrite
- default global mode is `Preserve Voice`
- selected text is only replaced after a complete rewrite succeeds
- app can remain hidden in the tray while the hotkey stays active
### Tray Lifecycle
- closing the main window does not quit by default
- first close shows a tray hint
- later closes hide to tray
- tray menu supports show, rewrite, and quit
## Queued Next
After the desktop release candidate is validated:
After Phase 2 is closed:
1. start the speech phase from `PHASE3_SPEECH_PLAN.md`
2. expand trust protections beyond URLs into protected terms and factual tokens
2. queue trust refinements such as protected terms and high-value detail preservation
3. revisit optimization work only after validation data exists

View File

@ -2,32 +2,28 @@
This is the single source of truth for current delivery status.
Current release state: `desktop typed-workflow release candidate`
Phase 2 feature scope is implemented. The remaining work is validation and release-policy closure, not missing core desktop functionality.
## Completed
### Desktop MVP Foundation
- Status: `completed`
- Rationale: The desktop app, main rewrite UI, mode selector, copy flow, and diff review are implemented and in active use.
- Rationale: The desktop app, main rewrite UI, mode selector, copy flow, and diff review are all implemented and working.
- Acceptance condition: User can paste text, rewrite it, inspect diff, and copy output without leaving the app.
- Repo pointer: `src/`, `index.html`, `src/styles.css`, `src-tauri/src/lib.rs`
### Provider Abstraction And Gemini Integration
- Status: `completed`
- Rationale: Rewrite logic is abstracted behind a provider trait and Gemini is implemented as the current backend.
- Rationale: Rewrite logic is abstracted behind a provider trait and Gemini is implemented as the current backend, including streaming.
- Acceptance condition: Main UI and cross-app rewrite both run through the provider boundary instead of hard-coded prompt logic in the UI.
- Repo pointer: `src-tauri/src/translator.rs`, `src-tauri/src/gemini.rs`, `src-tauri/src/lib.rs`
### Native Secure Credential Storage
### Secure Credential Storage
- Status: `completed`
- Rationale: API key storage now uses native platform protection instead of a misleading generic claim. On Windows this is DPAPI-encrypted app-local storage; other supported desktop platforms continue to use the native credential backend.
- Acceptance condition: Saving a key through Settings persists it securely across app restarts and the key is never written in plaintext.
- Repo pointer: `src-tauri/src/credentials.rs`, `src-tauri/src/lib.rs`, `README.md`
- Rationale: The Gemini API key is stored in the system credential store rather than a plaintext app file.
- Acceptance condition: Saving a key through Settings persists it securely across app restarts.
- Repo pointer: `src-tauri/src/lib.rs`, `README.md`
### Global Shortcut And Clipboard Rewrite
@ -39,24 +35,17 @@ Phase 2 feature scope is implemented. The remaining work is validation and relea
### Tray And Background Operation
- Status: `completed`
- Rationale: Thisper supports tray-based lifecycle management and continued hotkey operation while the window is hidden.
- Rationale: Thisper now supports tray-based lifecycle management and continued hotkey operation while the window is hidden.
- Acceptance condition: Closing the window hides to tray after the first hint, tray actions restore or quit correctly, and the hotkey remains active while hidden.
- Repo pointer: `src-tauri/src/lib.rs`, `src/main.ts`, `index.html`, `src/styles.css`
### Runtime Observability
- Status: `completed`
- Rationale: In-memory runtime metrics track rewrite attempts, success/failure counts, last error, last model, and last rewrite duration without storing raw user text.
- Acceptance condition: Frontend can query and display runtime status locally.
- Rationale: In-memory runtime metrics now track rewrite attempts, success/failure counts, last error, last model, and last rewrite duration.
- Acceptance condition: Frontend can query and display runtime status without storing raw user text.
- Repo pointer: `src-tauri/src/lib.rs`, `src/main.ts`
### Protected URL Preservation
- Status: `completed`
- Rationale: Links are now treated as protected content and may not be silently removed during rewrites.
- Acceptance condition: If a rewrite tries to drop a protected URL, it fails instead of returning altered text.
- Repo pointer: `src-tauri/src/gemini.rs`, `README.md`
### License Audit Automation
- Status: `completed`
@ -64,13 +53,13 @@ Phase 2 feature scope is implemented. The remaining work is validation and relea
- Acceptance condition: `licenses:npm`, `licenses:rust`, and `licenses:all` are available and documented.
- Repo pointer: `package.json`, `src-tauri/deny.toml`, `README.md`
## Remaining Before General Release
## Remaining For Phase 2 Completion
### Legacy Hardware Validation Pass
- Status: `remaining`
- Rationale: The implementation is in place, but the acceptance report still depends on a structured pass against the target hardware profile.
- Acceptance condition: The validation checklist is executed and the report is updated with explicit observed results.
- Acceptance condition: The validation checklist is executed and the report is updated with actual observed results.
- Repo pointer: `LEGACY_HARDWARE_VALIDATION.md`
### Commercial License Allowlist Decision
@ -80,19 +69,19 @@ Phase 2 feature scope is implemented. The remaining work is validation and relea
- Acceptance condition: A written allowlist decision is made and the Rust audit configuration is aligned with that decision.
- Repo pointer: `src-tauri/deny.toml`, `README.md`
## Queued After Release Candidate
## Queued After Phase 2
### Speech Input Phase
- Status: `queued`
- Rationale: Speech is the next planned phase after the current desktop release candidate is validated.
- Acceptance condition: Release-candidate validation is closed and the speech scope starts from the queued plan.
- Rationale: Speech is the next planned phase after desktop Phase 2 is closed, but it is not part of current completion criteria.
- Acceptance condition: Phase 2 is marked complete and the speech scope starts from the queued plan.
- Repo pointer: `PHASE3_SPEECH_PLAN.md`
### High-Value Detail Protection Expansion
### High-Value Detail Protection
- Status: `queued`
- Rationale: URLs are protected now, but broader protected terms, factual token preservation, and drift flags are still future trust refinements.
- Rationale: Protected terms, factual token preservation, and drift flags are important trust refinements, but they are not blocking current Phase 2 completion.
- Acceptance condition: Add protected-term rules, token-preservation checks, and a stricter retry path without breaking current flows.
- Repo pointer: `communication_translator_project_plan.md`
@ -101,20 +90,20 @@ Phase 2 feature scope is implemented. The remaining work is validation and relea
### Mobile Platforms
- Status: `deferred`
- Rationale: Android and iOS are not part of the current desktop release candidate.
- Rationale: Android and iOS are not part of current Phase 2 desktop completion.
- Acceptance condition: Revisit after speech planning and desktop validation.
- Repo pointer: `PHASE3_SPEECH_PLAN.md`
### Local Models And Multi-Provider Routing
- Status: `deferred`
- Rationale: The provider boundary exists, but local execution and multi-provider fallback are not required for the current desktop release candidate.
- Acceptance condition: Re-enter scope only after the current desktop workflow is fully validated.
- Rationale: The provider boundary exists, but local execution and multi-provider fallback are not required to close the current desktop scope.
- Acceptance condition: Re-enter scope only after current desktop workflow is fully validated.
- Repo pointer: `src-tauri/src/translator.rs`
### Major UI Redesign
- Status: `deferred`
- Rationale: Current priority is reliability, trust, and release readiness, not visual redesign.
- Acceptance condition: Only revisit after release-candidate validation and trust refinements.
- Rationale: Current priority is lifecycle reliability and trust, not visual redesign.
- Acceptance condition: Only revisit after Phase 2 acceptance and trust refinements.
- Repo pointer: `src/`, `index.html`, `src/styles.css`

View File

@ -1,14 +1,5 @@
# Communication Translator Project Plan
This document is the long-term product vision and design philosophy.
For current implementation state and release readiness, use:
- `README.md`
- `RELEASE_CANDIDATE.md`
- `THISPER_STATUS.md`
- `THISPER_IMPLEMENTATION_PLAN.md`
## Working Name
Use a placeholder name until the product identity becomes obvious through use.

View File

@ -49,7 +49,7 @@
<div class="field">
<label for="api-key-input">Gemini API Key</label>
<input type="password" id="api-key-input" placeholder="Paste your API key here...">
<p class="hint">Stored securely using native platform protection. On Windows, Thisper uses DPAPI-encrypted app data. The key is never written in plaintext.</p>
<p class="hint">Stored in your system credential store. Thisper never writes the key to a plaintext file.</p>
</div>
<div class="modal-actions">
<button id="save-settings-btn" class="primary-btn">Save Settings</button>

12
src-tauri/Cargo.lock generated
View File

@ -4529,7 +4529,6 @@ dependencies = [
"tauri-plugin-clipboard-manager",
"tauri-plugin-global-shortcut",
"tauri-plugin-single-instance",
"windows-dpapi",
]
[[package]]
@ -5488,17 +5487,6 @@ dependencies = [
"windows-strings 0.5.1",
]
[[package]]
name = "windows-dpapi"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2981752d6f11bdcab4db52be8ad5c0e6a6d4d6d566764b3058cc1ee473e6479e"
dependencies = [
"anyhow",
"log",
"winapi",
]
[[package]]
name = "windows-future"
version = "0.2.1"

View File

@ -32,9 +32,6 @@ futures-util = "0.3.32"
keyring = "3.6.3"
regex = "1"
[target.'cfg(target_os = "windows")'.dependencies]
windows-dpapi = "0.2.0"
[profile.release]
panic = "abort"
opt-level = "s"

View File

@ -1,134 +0,0 @@
use tauri::AppHandle;
const KEYRING_SERVICE: &str = "thisper";
const KEYRING_ACCOUNT: &str = "gemini_api_key";
pub fn load_api_key(app: &AppHandle) -> Result<Option<String>, String> {
backend::load_api_key(app)
}
pub fn save_api_key(app: &AppHandle, key: &str) -> Result<(), String> {
backend::save_api_key(app, key)
}
pub fn storage_description() -> &'static str {
backend::storage_description()
}
#[cfg(target_os = "windows")]
mod backend {
use super::{KEYRING_ACCOUNT, KEYRING_SERVICE};
use std::fs;
use std::path::PathBuf;
use tauri::{AppHandle, Manager};
use windows_dpapi::{Scope, decrypt_data, encrypt_data};
const API_KEY_FILE: &str = "gemini_api_key.dpapi";
fn encrypted_file_path(app: &AppHandle) -> Result<PathBuf, String> {
let dir = app
.path()
.app_local_data_dir()
.map_err(|err| format!("Failed to resolve local app data directory: {err}"))?;
Ok(dir.join("credentials").join(API_KEY_FILE))
}
fn load_legacy_keyring_value() -> Result<Option<String>, String> {
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
.map_err(|err| format!("Failed to access legacy credential store: {err}"))?;
match entry.get_password() {
Ok(password) if !password.trim().is_empty() => Ok(Some(password)),
Ok(_) => Ok(None),
Err(keyring::Error::NoEntry) => Ok(None),
Err(err) => Err(format!("Failed to read legacy credential store: {err}")),
}
}
fn delete_legacy_keyring_value() {
if let Ok(entry) = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT) {
let _ = entry.delete_credential();
}
}
pub fn load_api_key(app: &AppHandle) -> Result<Option<String>, String> {
let path = encrypted_file_path(app)?;
if path.exists() {
let encrypted = fs::read(&path)
.map_err(|err| format!("Failed to read encrypted API key: {err}"))?;
if encrypted.is_empty() {
return Ok(None);
}
let decrypted = decrypt_data(&encrypted, Scope::User, None)
.map_err(|err| format!("Failed to decrypt API key: {err}"))?;
let key = String::from_utf8(decrypted)
.map_err(|err| format!("Failed to decode decrypted API key: {err}"))?;
if key.trim().is_empty() {
return Ok(None);
}
return Ok(Some(key));
}
if let Some(legacy_key) = load_legacy_keyring_value()? {
save_api_key(app, &legacy_key)?;
delete_legacy_keyring_value();
return Ok(Some(legacy_key));
}
Ok(None)
}
pub fn save_api_key(app: &AppHandle, key: &str) -> Result<(), String> {
let path = encrypted_file_path(app)?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|err| format!("Failed to create credential directory: {err}"))?;
}
let encrypted = encrypt_data(key.as_bytes(), Scope::User, None)
.map_err(|err| format!("Failed to encrypt API key with Windows DPAPI: {err}"))?;
fs::write(&path, encrypted)
.map_err(|err| format!("Failed to write encrypted API key: {err}"))?;
Ok(())
}
pub fn storage_description() -> &'static str {
"Stored securely using Windows DPAPI in Thisper's local app data directory. The key is encrypted for your Windows user account and is never written in plaintext."
}
}
#[cfg(not(target_os = "windows"))]
mod backend {
use super::{KEYRING_ACCOUNT, KEYRING_SERVICE};
use tauri::AppHandle;
pub fn load_api_key(_app: &AppHandle) -> Result<Option<String>, String> {
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
.map_err(|err| format!("Failed to access native credential storage: {err}"))?;
match entry.get_password() {
Ok(password) if !password.trim().is_empty() => Ok(Some(password)),
Ok(_) => Ok(None),
Err(keyring::Error::NoEntry) => Ok(None),
Err(err) => Err(format!("Failed to read native credential storage: {err}")),
}
}
pub fn save_api_key(_app: &AppHandle, key: &str) -> Result<(), String> {
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
.map_err(|err| format!("Failed to access native credential storage: {err}"))?;
entry
.set_password(key)
.map_err(|err| format!("Failed to save API key securely: {err}"))?;
Ok(())
}
pub fn storage_description() -> &'static str {
"Stored securely using the native platform credential store. Thisper never writes the key in plaintext."
}
}

View File

@ -1,4 +1,3 @@
pub mod credentials;
pub mod gemini;
pub mod translator;
@ -18,6 +17,8 @@ use tauri::{AppHandle, Emitter, Manager, State, WebviewWindow, WindowEvent};
use tauri_plugin_clipboard_manager::ClipboardExt;
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut};
const KEYRING_SERVICE: &str = "thisper";
const KEYRING_ACCOUNT: &str = "gemini_api_key";
const MAIN_WINDOW_LABEL: &str = "main";
const TRAY_ID: &str = "main-tray";
const MENU_SHOW_ID: &str = "show";
@ -68,6 +69,17 @@ struct RuntimeState {
last_metrics: Option<RewriteMetrics>,
}
fn load_saved_api_key() -> Result<Option<String>, String> {
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
.map_err(|err| format!("Failed to access credential store: {err}"))?;
match entry.get_password() {
Ok(password) if !password.trim().is_empty() => Ok(Some(password)),
Ok(_) => Ok(None),
Err(keyring::Error::NoEntry) => Ok(None),
Err(err) => Err(format!("Failed to read credential store: {err}")),
}
}
fn api_key_configured(state: &AppState) -> bool {
let key = state.api_key.lock().unwrap();
!key.trim().is_empty() || std::env::var("GEMINI_API_KEY").is_ok()
@ -320,7 +332,11 @@ async fn save_api_key(
app: AppHandle,
state: State<'_, AppState>,
) -> Result<(), String> {
credentials::save_api_key(&app, &key)?;
let entry = keyring::Entry::new(KEYRING_SERVICE, KEYRING_ACCOUNT)
.map_err(|err| format!("Failed to access credential store: {err}"))?;
entry
.set_password(&key)
.map_err(|err| format!("Failed to save API key securely: {err}"))?;
{
let mut key_state = state.api_key.lock().unwrap();
@ -407,7 +423,11 @@ async fn run_clipboard_rewrite(app: AppHandle) -> Result<(), String> {
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let initial_key = std::env::var("GEMINI_API_KEY").unwrap_or_default();
let initial_key = load_saved_api_key()
.ok()
.flatten()
.or_else(|| std::env::var("GEMINI_API_KEY").ok())
.unwrap_or_default();
let provider =
Box::new(gemini::GeminiProvider::new().expect("Gemini provider initialization failed"))
@ -454,21 +474,6 @@ pub fn run() {
attach_window_lifecycle(&app.handle()).map_err(std::io::Error::other)?;
app.global_shortcut().register(shortcut_clone.clone())?;
match credentials::load_api_key(&app.handle()) {
Ok(Some(saved_key)) => {
let state: State<'_, AppState> = app.state();
let mut api_key = state.api_key.lock().unwrap();
*api_key = saved_key;
}
Ok(None) => {}
Err(err) => {
eprintln!("Credential storage load error: {err}");
let state: State<'_, AppState> = app.state();
let mut runtime = state.runtime.lock().unwrap();
runtime.last_error = Some(err);
}
}
{
let state: State<'_, AppState> = app.state();
let mut runtime = state.runtime.lock().unwrap();

View File

@ -161,7 +161,7 @@ saveSettingsBtn.addEventListener("click", async () => {
settingsModal.classList.add("hidden");
apiKeyInput.value = "";
await refreshRuntimeStatus();
alert("API key saved using native platform protection.");
alert("API key saved to your system credential store.");
} catch (err) {
alert("Error saving API key: " + err);
} finally {