wip: snapshot das alterações não commitadas (2026-10-20)
Origem: D:/Documents/Github/transcript Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 2
This commit is contained in:
1 parent
92c214254e
commit
06aaeeaf79
123 files changed
+20537
-982
No files matched your search
@@ -0,0 +1,35 @@
|
||||
# Multi-agent development pipeline
|
||||
|
||||
Goal: ship UI changes with high design quality, code quality, and the least code that works.
|
||||
|
||||
## Phases
|
||||
|
||||
1. **Scope framer — `product-framer`**
|
||||
- Convert the request into a short fix ledger: issue, acceptance criteria, files, out-of-scope.
|
||||
- Output: checklist only; no code.
|
||||
|
||||
2. **Design quality — `ui-art-director` + `ux-critic`**
|
||||
- Validate hierarchy, density, typography, motion, and user flow.
|
||||
- Cut changes that do not improve the user's experience.
|
||||
|
||||
3. **Minimal code preflight — `code-reviewer`**
|
||||
- Find existing patterns and the shortest safe implementation path.
|
||||
- Reject new dependencies and speculative abstractions.
|
||||
|
||||
4. **Implementation — `frontend-craftsperson`**
|
||||
- Implement one focused batch at a time.
|
||||
- Add the smallest tests that prove behavior.
|
||||
- Reuse tokens and existing DOM helpers.
|
||||
|
||||
5. **Final 3-agent review**
|
||||
- `visual-qa`: visual regressions and missed UX criteria.
|
||||
- `accessibility-reviewer`: keyboard, focus, labels, motion, semantics.
|
||||
- `code-reviewer`: correctness, maintainability, unnecessary code.
|
||||
|
||||
6. **Fix loop**
|
||||
- Send only blocking review findings back to `frontend-craftsperson`.
|
||||
- Rerun the same 3-agent final review once.
|
||||
|
||||
## Default rule
|
||||
|
||||
If two solutions work, choose the one with fewer files, fewer branches, and no dependency changes.
|
||||
@@ -0,0 +1,11 @@
|
||||
# MCP Obsidian
|
||||
|
||||
O projeto usa o servidor MCP HTTP `obsidian` no endpoint local:
|
||||
|
||||
```text
|
||||
https://127.0.0.1:27124/mcp/
|
||||
```
|
||||
|
||||
A configuração local fica em `.mcp.json`, que é ignorado pelo Git. Antes de iniciar o OpenClaude neste projeto, defina a variável de ambiente `OBSIDIAN_MCP_TOKEN` com o token do Obsidian.
|
||||
|
||||
Nunca commite o token nem o arquivo `.mcp.json`.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Minimalist Front Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make the Transcript desktop UI quieter, more minimal, and better organized without adding dependencies.
|
||||
|
||||
**Architecture:** Keep the current vanilla TypeScript DOM structure. Use the shortest safe path: CSS cleanup/overrides, small copy changes, and one keyboard behavior fix. No component split in this pass.
|
||||
|
||||
**Tech Stack:** Tauri, TypeScript, Vite, Vitest, CSS.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Regression tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/app.test.ts`
|
||||
|
||||
- [ ] Add tests for Escape closing settings and minimalist CSS baseline.
|
||||
- [ ] Run `npm test -- --run src/app.test.ts --reporter=verbose`; expect new tests to fail before implementation.
|
||||
|
||||
### Task 2: Behavior and copy polish
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/app.ts`
|
||||
|
||||
- [ ] Add Escape handling to close the settings modal.
|
||||
- [ ] Rename technical settings labels to calmer user-facing labels.
|
||||
|
||||
### Task 3: Minimal visual pass
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/styles.css`
|
||||
|
||||
- [ ] Fix the invalid `height: 100dvh` declaration.
|
||||
- [ ] Hide ambient decoration.
|
||||
- [ ] Flatten shell, topbar, buttons, editor, settings, and dictation overlay.
|
||||
- [ ] Keep reduced motion and existing accessibility styles.
|
||||
|
||||
### Task 4: Verification
|
||||
|
||||
**Files:**
|
||||
- Read-only verification.
|
||||
|
||||
- [ ] Run `npm test -- --run`.
|
||||
- [ ] Run `npm run build`.
|
||||
- [ ] Run `cargo check --manifest-path src-tauri/Cargo.toml` through VS Build Tools environment.
|
||||
@@ -0,0 +1,209 @@
|
||||
# Audio History, API Fallback, and Streaming Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Retain the last 10 recordings, export them through a native save dialog, fall back from API to local Whisper PT-BR, and insert streaming API text progressively.
|
||||
|
||||
**Architecture:** Add a small `audio_history` module that owns safe filename validation, metadata, retention, and export source resolution. Extend the existing OpenAI-compatible client to detect SSE/NDJSON by response `Content-Type`; the existing UI command pipeline owns partial rendering and global-hotkey insertion so the client remains transport-focused.
|
||||
|
||||
**Tech Stack:** Rust, Tauri 2, `tauri-plugin-dialog`, reqwest blocking client, TypeScript, Vitest, Cargo tests.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Persist a bounded, safe audio history
|
||||
|
||||
**Files:**
|
||||
- Create: `src-tauri/src/audio_history.rs`
|
||||
- Modify: `src-tauri/src/lib.rs`
|
||||
- Modify: `src-tauri/src/app_state.rs`
|
||||
- Modify: `src-tauri/src/ui_commands.rs`
|
||||
- Test: `src-tauri/src/audio_history.rs`
|
||||
|
||||
- [ ] **Step 1: Write failing history tests**
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn retain_keeps_the_ten_newest_wavs() { /* create 11 timestamped WAV files; assert 10 remain */ }
|
||||
|
||||
#[test]
|
||||
fn audio_id_cannot_escape_recordings_dir() {
|
||||
assert!(resolve_audio_path(Path::new("recordings"), "../secret.wav").is_err());
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the focused test**
|
||||
|
||||
Run: `cargo test --manifest-path src-tauri/Cargo.toml audio_history::tests --lib`
|
||||
Expected: compilation failure because `audio_history` does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement a minimal history module**
|
||||
|
||||
```rust
|
||||
pub const AUDIO_HISTORY_LIMIT: usize = 10;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AudioHistoryItem { pub id: String, pub size_bytes: u64, pub modified_at_ms: u64 }
|
||||
|
||||
pub fn retain_audio_history(dir: &Path) -> AppResult<Vec<AudioHistoryItem>> { /* list WAVs, sort newest-first, remove beyond 10 */ }
|
||||
pub fn resolve_audio_path(dir: &Path, id: &str) -> AppResult<PathBuf> { /* filename-only + canonicalized containment */ }
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Call retention after both normal and hotkey transcription flows**
|
||||
|
||||
Replace `cleanup_audio_if_needed` calls with `retain_audio_history(&state.recordings_dir)` so captured WAVs are never deleted simply because `keep_audio` is false. Add `audio_history` to `AppSnapshot` from `ManagedAppState::snapshot`.
|
||||
|
||||
- [ ] **Step 5: Run the focused test**
|
||||
|
||||
Run: `cargo test --manifest-path src-tauri/Cargo.toml audio_history::tests --lib`
|
||||
Expected: PASS.
|
||||
|
||||
### Task 2: Export recordings safely through a native dialog
|
||||
|
||||
**Files:**
|
||||
- Modify: `src-tauri/Cargo.toml`
|
||||
- Modify: `src-tauri/src/lib.rs`
|
||||
- Modify: `src-tauri/src/ui_commands.rs`
|
||||
- Test: `src-tauri/src/audio_history.rs`
|
||||
|
||||
- [ ] **Step 1: Add a failing source-resolution test**
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn resolve_audio_path_accepts_a_listed_wav() { /* assert a known WAV resolves inside recordings */ }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the documented Tauri dialog plugin**
|
||||
|
||||
```toml
|
||||
tauri-plugin-dialog = "2"
|
||||
```
|
||||
|
||||
```rust
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
```
|
||||
|
||||
Use `DialogExt::dialog().file().add_filter("Áudio WAV", &["wav"]).blocking_save_file()` and `std::fs::copy`; return `false` when the user cancels.
|
||||
|
||||
- [ ] **Step 3: Register an explicit command**
|
||||
|
||||
```rust
|
||||
#[tauri::command]
|
||||
pub(crate) fn download_audio(id: String, app: tauri::AppHandle, state: State<'_, ManagedAppState>) -> Result<bool, String>
|
||||
```
|
||||
|
||||
Resolve `id` through `audio_history::resolve_audio_path`; never accept a caller-provided file path.
|
||||
|
||||
- [ ] **Step 4: Run Rust tests**
|
||||
|
||||
Run: `cargo test --manifest-path src-tauri/Cargo.toml --lib`
|
||||
Expected: PASS.
|
||||
|
||||
### Task 3: API-to-Whisper PT-BR fallback
|
||||
|
||||
**Files:**
|
||||
- Modify: `src-tauri/src/ui_commands.rs`
|
||||
- Test: `src-tauri/src/ui_commands.rs`
|
||||
|
||||
- [ ] **Step 1: Write a failing pure routing test**
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn api_failure_without_partial_uses_local_fallback() {
|
||||
assert!(should_fallback_to_whisper(TranscriptionMode::ApiOnly, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_stream_does_not_fallback_and_duplicate_text() {
|
||||
assert!(!should_fallback_to_whisper(TranscriptionMode::ApiOnly, true));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Implement the smallest routing rule**
|
||||
|
||||
```rust
|
||||
fn should_fallback_to_whisper(mode: TranscriptionMode, partial_received: bool) -> bool {
|
||||
matches!(mode, TranscriptionMode::ApiOnly) && !partial_received
|
||||
}
|
||||
```
|
||||
|
||||
In `run_configured_transcription`, use a cloned settings value with `transcription.language = Some("pt".into())` to invoke existing Whisper after API failure. Preserve local-first’s current local-then-API behavior.
|
||||
|
||||
- [ ] **Step 3: Run focused tests**
|
||||
|
||||
Run: `cargo test --manifest-path src-tauri/Cargo.toml ui_commands::tests --lib`
|
||||
Expected: PASS.
|
||||
|
||||
### Task 4: Stream response handling and immediate insertion
|
||||
|
||||
**Files:**
|
||||
- Modify: `src-tauri/src/transcription/openai.rs`
|
||||
- Modify: `src-tauri/src/ui_commands.rs`
|
||||
- Test: `src-tauri/src/transcription/openai.rs`
|
||||
|
||||
- [ ] **Step 1: Write failing parser tests**
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn streaming_content_type_is_detected() { /* text/event-stream and x-ndjson true */ }
|
||||
|
||||
#[test]
|
||||
fn cumulative_stream_text_becomes_only_the_new_suffix() {
|
||||
assert_eq!(stream_delta("olá", "olá mundo"), " mundo");
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Implement response-stream transport**
|
||||
|
||||
Set `Accept: text/event-stream, application/x-ndjson, application/json`. For SSE/NDJSON headers, read lines from the blocking response, ignore `[DONE]`, extract `delta`, `text`, `transcript`, `result`, `output`, `data`, and OpenAI `choices[0].delta.content`, then invoke an optional partial callback. Keep JSON/plain-text behavior unchanged.
|
||||
|
||||
- [ ] **Step 3: Wire partial callbacks into UI commands**
|
||||
|
||||
- Main-window transcriptions append partials to `TranscriptDraft.text` and emit snapshots.
|
||||
- Global-hotkey transcriptions hide the non-focusable island on first partial and use existing `FallbackInjector` with `ClipboardPaste` for each new suffix.
|
||||
- Only perform the final full-text insertion when no partial was inserted.
|
||||
- Suppress API-to-Whisper fallback after a partial to prevent duplicate external text.
|
||||
|
||||
- [ ] **Step 4: Run focused tests**
|
||||
|
||||
Run: `cargo test --manifest-path src-tauri/Cargo.toml transcription::openai::tests ui_commands::tests --lib`
|
||||
Expected: PASS.
|
||||
|
||||
### Task 5: Minimal audio history UI
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/app.ts`
|
||||
- Modify: `src/app.test.ts`
|
||||
- Modify: `src/styles.css`
|
||||
|
||||
- [ ] **Step 1: Write failing frontend tests**
|
||||
|
||||
```ts
|
||||
it("shows the latest audio and requests its download", async () => {
|
||||
// Fake snapshot has audioHistory[0]; click the button; expect download_audio with its id.
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Implement a compact details list**
|
||||
|
||||
Add `audioHistory` to frontend `AppSnapshot`. Render one collapsed `<details>` section below diagnostics: “Áudios recentes”, a primary “Baixar último áudio” action, and up to 10 safe metadata rows. Show a useful empty state. Call native `download_audio` by item ID.
|
||||
|
||||
- [ ] **Step 3: Add restrained CSS**
|
||||
|
||||
Use existing tokens and compact buttons. Do not introduce a new modal, gradients, or a card grid. Preserve visible focus styles and reduced-motion behavior.
|
||||
|
||||
- [ ] **Step 4: Run focused tests**
|
||||
|
||||
Run: `npm test -- --run src/app.test.ts --reporter=verbose`
|
||||
Expected: PASS.
|
||||
|
||||
### Task 6: Full verification and release artifact
|
||||
|
||||
**Files:**
|
||||
- Modify only release metadata if a new artifact version is required.
|
||||
|
||||
- [ ] Run `cargo fmt --manifest-path src-tauri/Cargo.toml -- --check`.
|
||||
- [ ] Run `cargo test --manifest-path src-tauri/Cargo.toml --lib`.
|
||||
- [ ] Run `npm test -- --run && npm run build`.
|
||||
- [ ] Run `cargo check --manifest-path src-tauri/Cargo.toml` inside the VS Build Tools environment.
|
||||
- [ ] Run `npm run tauri:build` inside the VS Build Tools environment and verify installer timestamps.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Áudio persistente, fallback local e streaming — Design
|
||||
|
||||
## Objetivo
|
||||
|
||||
Guardar os 10 áudios mais recentes enviados à transcrição, permitir salvar qualquer um deles em um local escolhido pelo usuário, usar Whisper local em PT-BR quando a API falhar e inserir texto progressivamente quando a API responder em streaming.
|
||||
|
||||
## Escopo e decisões
|
||||
|
||||
### Histórico de áudio
|
||||
|
||||
- Cada gravação finalizada permanece no diretório de dados do aplicativo como WAV.
|
||||
- O histórico é limitado a 10 arquivos, ordenados por data de modificação decrescente. Ao finalizar uma gravação, arquivos mais antigos são removidos.
|
||||
- Cada item expõe somente `id` (nome de arquivo validado), nome, tamanho e data. Caminhos absolutos nunca são enviados ao frontend.
|
||||
- O frontend mostra um `<details>` compacto de “Áudios recentes”, com “Baixar último áudio” e ações de salvar por item.
|
||||
- O salvamento usa o diálogo nativo de arquivo e copia o WAV para a localização escolhida. Cancelar o diálogo não é erro.
|
||||
|
||||
### Fallback API → Whisper
|
||||
|
||||
- O modo de servidor tenta a API configurada primeiro.
|
||||
- Qualquer falha antes de texto parcial aciona Whisper embarcado com idioma fixo `pt`.
|
||||
- Se uma resposta streaming já tiver emitido texto parcial, não há fallback automático: repetir o texto poderia duplicar o que já foi inserido no campo ativo. O erro é mostrado para revisão.
|
||||
- O modo local continua usando Whisper primeiro e API como fallback, preservando o comportamento existente.
|
||||
|
||||
### Streaming de resposta
|
||||
|
||||
- O cliente envia `Accept: text/event-stream, application/x-ndjson, application/json`.
|
||||
- A API é detectada como streaming exclusivamente pelo `Content-Type` da resposta: `text/event-stream` ou `application/x-ndjson`.
|
||||
- Eventos SSE/NDJSON aceitam `delta`, `text`, `transcript`, `result`, `output`, `data` e `choices[0].delta.content`; payloads cumulativos são convertidos em somente o sufixo novo.
|
||||
- Na janela principal, cada fragmento atualiza o editor de transcrição.
|
||||
- No atalho global, o primeiro fragmento esconde a barra não focável e tenta colar imediatamente no campo externo ativo; a transcrição final não é colada uma segunda vez.
|
||||
- Respostas JSON normais mantêm o fluxo atual: texto completo somente após concluir.
|
||||
|
||||
## Segurança e limites
|
||||
|
||||
- IDs de áudio são nomes simples de WAV; separadores de diretório, extensões diferentes e caminhos fora do diretório de gravações são rejeitados.
|
||||
- O backend nunca recebe um destino de arquivo escolhido pelo frontend; o diálogo nativo escolhe o destino e o backend executa a cópia local.
|
||||
- Áudio é dado potencialmente sensível. A retenção fixa em 10 arquivos é deliberada e visível na UI; não há telemetria nem upload adicional.
|
||||
|
||||
## Testes
|
||||
|
||||
- Histórico: limita a 10, remove o mais antigo, rejeita IDs inseguros.
|
||||
- Fallback: API-only usa Whisper PT-BR quando não houve parcial; streaming parcial não dispara fallback.
|
||||
- Streaming: detecta Content-Type, extrai deltas/cumulativos e evita duplicação.
|
||||
- Frontend: renderiza histórico e pede download do item selecionado.
|
||||
Reference in new issue
Block a user