chore: update client submodule and improve linting results

- Updated the client submodule to the latest commit for enhanced features and stability.
- Cleared previous linting errors, resulting in zero errors and warnings in the linting reports.
- Added a new documentation file outlining cross-stack wiring gaps and implementation checklists for gRPC RPCs.
This commit is contained in:
2026-01-05 04:15:38 +00:00
parent 135796bdb9
commit c9cb17d9a3
41 changed files with 2008 additions and 6579 deletions

View File

@@ -0,0 +1,567 @@
# Cross-Stack Wiring Gaps Analysis
> **Generated:** 2026-01-05
> **Scope:** Frontend (TypeScript), Backend (Python), Desktop Client (Rust/Tauri)
This document identifies areas where the gRPC proto contract is not fully wired across all three layers.
---
## Core Features Status
The following features have their **individual RPCs wired** across all layers, but several lack **automatic orchestration** - they require manual user action to trigger.
| Feature | Proto RPC | Python Mixin | Rust Client | Rust Commands | TS Adapter | Orchestration | Notes |
|---------|-----------|--------------|-------------|---------------|------------|---------------|-------|
| **Transcription** | StreamTranscription | StreamingMixin | streaming/ | recording/ | ✅ | ✅ Auto | Real-time during recording |
| **Summaries** | GenerateSummary | SummarizationMixin | meetings.rs | summary.rs | ✅ | ⚠️ Manual | **GAP-W05**: No auto-trigger after recording |
| **Action Items** | (via Summary) | (via Summary) | (via Summary) | (via Summary) | ✅ | ⚠️ Manual | Depends on summary generation |
| **Annotations** | Add/Get/List/Update/Delete | AnnotationMixin | annotations.rs | annotation.rs | ✅ | ✅ User-driven | CRUD by design |
| **Preferences** | Get/SetPreferences | PreferencesMixin | preferences.rs | preferences.rs | ✅ | ✅ Auto | Sync on change |
| **Meetings** | Create/Get/List/Stop/Delete | MeetingMixin | meetings.rs | meeting.rs | ✅ | ✅ Auto | Full lifecycle |
| **Export** | ExportTranscript | ExportMixin | annotations.rs | export.rs | ✅ | ✅ User-driven | On-demand by design |
| **Diarization** | Refine/Rename/Status/Cancel | DiarizationMixin | diarization.rs | diarization.rs | ✅ | ⚠️ Manual | **GAP-W05**: No auto-trigger after recording |
| **Entities (NER)** | Extract/Update/Delete | EntitiesMixin | annotations.rs | entities.rs | ✅ | ⚠️ Manual | **GAP-W05**: Auto-extract disabled |
| **Calendar** | List/Providers/OAuth | CalendarMixin | calendar.rs | calendar.rs | ✅ | ✅ Auto | Sync scheduled |
| **Webhooks** | Register/List/Update/Delete | WebhooksMixin | webhooks.rs | webhooks.rs | ✅ | ✅ Auto | Fire on events |
| **Projects** | Full CRUD + Membership | ProjectMixin | projects.rs | projects.rs | ✅ | ✅ User-driven | CRUD by design |
| **Identity** | GetCurrentUser | IdentityMixin | identity.rs | identity.rs | ✅ | ✅ Auto | On connection |
| **Observability** | Logs/Metrics | ObservabilityMixin | observability.rs | observability.rs | ✅ | ✅ Auto | Background |
| **Sync** | Start/Status/History | SyncMixin | sync.rs | sync.rs | ✅ | ✅ Auto | Scheduled |
**Legend:**
-**Auto**: Triggers automatically at the appropriate time
-**User-driven**: Requires user action by design (CRUD, export, etc.)
- ⚠️ **Manual**: Requires manual trigger but should be automatic
### Tasks Page Note
The "Tasks" feature (`/tasks` page) aggregates **action items from summaries** across meetings. There is no separate Task entity in the proto - tasks are derived from `Summary.action_items` which includes:
- `text`: Action item description
- `assignee`: Person assigned (if mentioned)
- `due_date`: Due date (if mentioned)
- `priority`: Priority level (high/medium/low)
- `segment_ids`: Links to transcript segments
Task completion status is stored in local preferences (`completed_tasks`).
**Note:** Tasks only appear after summaries are generated, which currently requires manual triggering (see GAP-W05).
---
## Identified Gaps
## Summary
| Gap ID | Feature | Severity | Backend | Rust Client | TS Adapter | Desktop Testing |
|--------|---------|----------|---------|-------------|------------|-----------------|
| GAP-W05 | Post-Processing Orchestration | **Critical** | ✅ | ✅ | ❌ Missing | Required |
| GAP-W01 | OIDC Provider Management | **Critical** | ✅ | ❌ None | ❌ None | Required |
| GAP-W02 | Workspace gRPC Methods | Informational | ✅ | ⚠️ Fallback | ✅ | Not Required |
| GAP-W03 | Preferences Sync Constants | Low | ✅ | ✅ | Bypassed | Not Required |
| ~~GAP-W04~~ | ~~OAuth Loopback Adapter~~ | ~~Removed~~ | — | — | — | — |
**Audit Notes (2026-01-05):**
- GAP-W01: Sprint-17 doc claims client wiring done, but verification shows **no client files exist**
- GAP-W02: Downgraded - intentional local-first fallback behavior, not a bug
- GAP-W04: **Removed** - `use-oauth-flow.ts` directly invokes `initiate_oauth_loopback` (line 193); it works
---
## GAP-W05: Post-Processing Orchestration (Critical)
> **Full Details:** See [sprint-gap-011-post-processing-pipeline/README.md](./sprint-gap-011-post-processing-pipeline/README.md)
### Description
After a meeting recording completes, the system fails to automatically trigger post-processing workflows (summarization, entity extraction, diarization refinement). Users see only raw recordings without automatic transcriptions, summaries, or extracted intelligence. **The architecture has all the RPC components but lacks orchestration to connect them.**
### The Disconnect
```
┌────────────────────────────────────────────────────────────┐
│ WHAT EXISTS (Individual RPCs - All Working) │
├────────────────────────────────────────────────────────────┤
│ GenerateSummary ─────────► Manual button click only │
│ ExtractEntities ─────────► Auto-extract disabled │
│ RefineSpeakerDiarization ► Manual button click only │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ WHAT'S MISSING (Orchestration Layer) │
├────────────────────────────────────────────────────────────┤
│ No usePostProcessing hook │
│ Recording.tsx navigates away immediately after stop │
│ MeetingDetail only fetches, doesn't trigger generation │
│ No ProcessingStatus component │
│ Summary progress events emitted but not listened to │
└────────────────────────────────────────────────────────────┘
```
### Implementation Status
| Layer | File | Status |
|-------|------|--------|
| **Python Backend** | `src/noteflow/grpc/_mixins/summarization.py` | ✅ RPC works |
| **Python Backend** | `src/noteflow/grpc/_mixins/entities.py` | ✅ RPC works |
| **Python Backend** | `src/noteflow/grpc/_mixins/diarization/_mixin.py` | ✅ RPC works |
| **Rust gRPC Client** | `client/src-tauri/src/grpc/client/meetings.rs` | ✅ Wired |
| **Rust Commands** | `client/src-tauri/src/commands/summary.rs` | ✅ Emits progress events |
| **TS Adapter** | `client/src/api/tauri-adapter.ts` | ✅ All methods available |
| **TS Hooks** | `client/src/hooks/use-entity-extraction.ts` | ⚠️ Auto-extract disabled |
| **TS Hooks** | `client/src/hooks/use-diarization.ts` | ⚠️ No auto-start |
| **TS Pages** | `client/src/pages/Recording.tsx:313-346` | ❌ Navigates immediately |
| **TS Pages** | `client/src/pages/MeetingDetail.tsx:79-98` | ❌ Fetches only, no triggers |
| **TS Orchestration** | N/A | ❌ `usePostProcessing` hook missing |
| **TS Progress** | N/A | ❌ `ProcessingStatus` component missing |
### Code Excerpts
**Recording.tsx immediately navigates away:**
```typescript
// client/src/pages/Recording.tsx:313-346
const stopRecording = async () => {
setIsRecording(false);
streamRef.current?.close();
const stoppedMeeting = await api.stopMeeting(meeting.id);
setMeeting(stoppedMeeting);
// GAP: Immediately navigates away - no processing triggered
navigate(
projectId
? `/projects/${projectId}/meetings/${meeting.id}`
: '/projects'
);
};
```
**MeetingDetail only fetches existing data:**
```typescript
// client/src/pages/MeetingDetail.tsx:79-98
useEffect(() => {
const loadMeeting = async () => {
const data = await getAPI().getMeeting({
meeting_id: id,
include_segments: true,
include_summary: true, // Fetches existing, doesn't generate
});
setMeeting(data.meeting);
setSegments(data.segments || []);
setSummary(data.summary); // null if not generated - stays null
};
loadMeeting();
}, [id]);
```
**Auto-extract feature exists but is disabled:**
```typescript
// client/src/hooks/use-entity-extraction.ts:116-121
useEffect(() => {
if (autoExtract && meetingId && meetingState === 'completed') {
extract(false); // This would work!
}
}, [autoExtract, meetingId, meetingState, extract]);
// client/src/pages/MeetingDetail.tsx:72-76
const { extract: extractEntities } = useEntityExtraction({
meetingId: id,
meetingTitle: meeting?.title,
meetingState: meeting?.state,
// autoExtract: true <-- MISSING - feature disabled
});
```
**Summary progress events emitted but ignored:**
```rust
// client/src-tauri/src/commands/summary.rs:96-134
tauri::async_runtime::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
let elapsed_s = start.elapsed().as_secs();
// Emits event but no React component listens
emit_summary_progress(app.clone(), meeting_id.clone(), elapsed_s);
if elapsed_s >= 300 { break; }
}
});
```
### Blockers
1. **No orchestration hook** (`usePostProcessing`) to coordinate processing after recording
2. **Immediate navigation** in Recording.tsx prevents client-side orchestration
3. **Auto-extract disabled** - simple fix but not done
4. **No progress UI** - summary progress events ignored
5. **No ProcessingStatus tracking** on Meeting entity
### Required Changes
**Quick Wins (No new code):**
1. Enable `autoExtract: true` in MeetingDetail.tsx entity extraction hook
**New Components:**
1. Create `client/src/hooks/use-post-processing.ts` - Orchestrates summary + entities + diarization
2. Create `client/src/components/meeting/processing-status.tsx` - Progress UI
**Modifications:**
1. `client/src/pages/Recording.tsx` - Trigger processing before/after navigation
2. `client/src/pages/MeetingDetail.tsx` - Wire `usePostProcessing` hook
3. `client/src/hooks/use-diarization.ts` - Add auto-start capability
4. `src/noteflow/domain/entities/meeting.py` - Add `ProcessingStatus` dataclass
5. `src/noteflow/grpc/proto/noteflow.proto` - Add `ProcessingStatus` message
### Desktop Testing Required
**Yes** - The full recording → stop → processing → display flow must be tested on the desktop app to verify:
- Processing triggers after recording stops
- Progress events display correctly
- Failed processing steps don't block others
- Navigation and state transitions work correctly
---
## GAP-W01: OIDC Provider Management (Sprint 17)
### Description
The OIDC provider management feature is fully implemented in the Python backend but has **no client wiring at all**. The Sprint-17 documentation claims client files were created, but **verification shows they do not exist**.
### Documentation vs Reality
**Sprint-17 doc claims (lines 97-106):**
- `client/src/api/types/oidc.ts` - **DOES NOT EXIST**
- `client/src/hooks/use-oidc-providers.ts` - **DOES NOT EXIST**
- `client/src/components/settings/oidc-providers-section.tsx` - **DOES NOT EXIST**
- OIDC methods in `tauri-adapter.ts` - **NOT PRESENT**
- 9 OIDC constants in `tauri-constants.ts` - **NOT PRESENT**
**What actually exists:**
- Generic `Integration` type with `oidc_config` field in `integration-config-panel.tsx`
- This saves to **local preferences only** - no gRPC calls
### User Flow Trace
```
User Action What Actually Happens
─────────────────────────────────────────────────────────────────────────
Settings → OIDC tab Shows generic Integration panel
(integration-config-panel.tsx:660-810)
Configure issuer_url, client_id Updates local state only
(onUpdate → preferences.updateIntegration)
Click "Test OIDC Connection" FAKE TEST:
1. Sleep 1.5 seconds (line 205)
2. Check if fields filled
3. Update local preferences
4. Show "Connection test passed" toast
NO gRPC call to backend
NO actual OIDC discovery
NO token validation
Save/Close Stored in localStorage only
Backend has no knowledge of provider
```
### Code Evidence
**The "test" is completely fake:**
```typescript
// client/src/components/settings/integrations-section.tsx:203-226
const handleTestIntegration = async (integration: Integration) => {
setTestingIntegration(integration.id);
await new Promise((resolve) => setTimeout(resolve, 1500)); // Just sleeps!
if (hasRequiredIntegrationFields(integration)) {
toast({
title: 'Connection test passed', // Lies to user
description: `${integration.name} is configured correctly`,
});
preferences.updateIntegration(integration.id, { status: 'connected' });
}
// NO gRPC call anywhere
};
```
### Proto RPCs (7 endpoints - Backend Only)
```protobuf
// src/noteflow/grpc/proto/noteflow.proto:90-96
rpc RegisterOidcProvider(RegisterOidcProviderRequest) returns (OidcProviderProto);
rpc ListOidcProviders(ListOidcProvidersRequest) returns (ListOidcProvidersResponse);
rpc GetOidcProvider(GetOidcProviderRequest) returns (OidcProviderProto);
rpc UpdateOidcProvider(UpdateOidcProviderRequest) returns (OidcProviderProto);
rpc DeleteOidcProvider(DeleteOidcProviderRequest) returns (DeleteOidcProviderResponse);
rpc RefreshOidcDiscovery(RefreshOidcDiscoveryRequest) returns (RefreshOidcDiscoveryResponse);
rpc ListOidcPresets(ListOidcPresetsRequest) returns (ListOidcPresetsResponse);
```
### Implementation Status
| Layer | File | Status |
|-------|------|--------|
| **Python Backend** | `src/noteflow/grpc/_mixins/oidc.py` | ✅ Complete (7 RPCs) |
| **Python Tests** | `tests/grpc/test_oidc_mixin.py` | ✅ 27 tests passing |
| **Rust gRPC Client** | `client/src-tauri/src/grpc/client/mod.rs` | ❌ No `oidc` module |
| **Rust Commands** | `client/src-tauri/src/commands/` | ❌ No `oidc.rs` |
| **TS Constants** | `client/src/api/tauri-constants.ts` | ❌ No OIDC commands |
| **TS Interface** | `client/src/api/interface.ts` | ❌ No OIDC methods |
| **TS Adapter** | `client/src/api/tauri-adapter.ts` | ❌ No OIDC implementation |
| **TS Types** | `client/src/api/types/oidc.ts` | ❌ Does not exist |
| **TS Hooks** | `client/src/hooks/use-oidc-providers.ts` | ❌ Does not exist |
| **UI Component** | `client/src/components/settings/oidc-providers-section.tsx` | ❌ Does not exist |
### Impact
- **User deception**: UI shows "Connection test passed" when nothing was tested
- **No SSO functionality**: OIDC providers cannot actually authenticate users
- **Sprint doc inaccuracy**: Claimed deliverables don't exist
### Blockers
1. **No Rust gRPC wrapper module** for OIDC operations
2. **No Tauri commands** to invoke OIDC RPCs
3. **No TypeScript adapter methods** to call Tauri commands
4. **No dedicated OIDC UI component** (current UI uses generic Integration panel)
### Required Changes
1. Create `client/src-tauri/src/grpc/client/oidc.rs` wrapper
2. Create `client/src-tauri/src/commands/oidc.rs` with Tauri commands
3. Add OIDC constants to `client/src/api/tauri-constants.ts`
4. Add OIDC methods to `NoteFlowAPI` interface and `tauri-adapter.ts`
5. Create `client/src/api/types/oidc.ts` with proper types
6. Create `client/src/hooks/use-oidc-providers.ts`
7. Create `client/src/components/settings/oidc-providers-section.tsx`
8. **Update Sprint-17 doc** to reflect actual vs claimed deliverables
### Desktop Testing Required
**Yes** - OIDC involves OAuth redirects, token exchange, and discovery that must be tested end-to-end.
---
## GAP-W02: Workspace Management via gRPC (Informational)
### Description
The workspace management RPCs are implemented in Python. The Rust commands return local defaults instead of calling gRPC. **This is intentional local-first behavior.**
### User Flow Trace
```
User Action What Actually Happens
─────────────────────────────────────────────────────────────────────────
App starts workspace-context.tsx:71-102
├─ Calls api.getCurrentUser()
├─ Calls api.listWorkspaces()
└─ Falls back to local defaults if error
Click workspace dropdown workspace-switcher.tsx calls switchWorkspace()
Select different workspace workspace-context.tsx:108-133
├─ Calls api.switchWorkspace(workspaceId)
├─ Uses response.workspace if available
├─ Falls back to local list match
└─ Persists to localStorage
Backend unavailable ✅ Works - uses fallback workspace
Backend available ✅ Works - uses backend response
```
### Why This Is Not A Bug
The `workspace-context.tsx` implementation (lines 71-102, 108-133) demonstrates **graceful degradation**:
```typescript
// workspace-context.tsx:81-82
const availableWorkspaces =
workspaceResponse.workspaces.length > 0 ? workspaceResponse.workspaces : [fallbackWorkspace];
```
When the backend returns real workspaces, they're used. When unavailable, the app continues working with local defaults.
### Status: Design Decision Complete
| Behavior | Status |
|----------|--------|
| Local-first operation | ✅ Implemented |
| Backend integration | ⚠️ Optional - could add gRPC-first with fallback |
| User experience | ✅ Seamless - works offline and online |
### Recommendation
No action required unless multi-user workspace sync is needed. The current implementation correctly prioritizes local-first operation while still accepting backend responses when available.
### Desktop Testing Required
**No** - behavior is intentional and works correctly.
---
## GAP-W03: Preferences Sync Constants
### Description
The preferences sync commands exist in Rust but are not registered in `TauriConstants` or the `NoteFlowAPI` interface. They are called directly via `invoke()` in `preferences-sync.ts`.
### Tauri Commands
```rust
// client/src-tauri/src/commands/preferences.rs:40-66
#[tauri::command(rename_all = "snake_case")]
pub async fn get_preferences_sync(/* ... */) -> Result<PreferencesSyncResult> { /* ... */ }
#[tauri::command(rename_all = "snake_case")]
pub async fn set_preferences_sync(/* ... */) -> Result<SetPreferencesResult> { /* ... */ }
```
### Usage (Bypasses Adapter)
```typescript
// client/src/lib/preferences-sync.ts:148-151
const response = await invoke<GetPreferencesResult>('get_preferences_sync', {
keys: keys ?? [],
});
// client/src/lib/preferences-sync.ts:200-205
const response = await invoke<SetPreferencesResult>('set_preferences_sync', {
preferences: encoded,
if_match: options?.force ? null : meta.etag,
// ...
});
```
### Assessment
**Low severity** - Functionality works, but pattern is inconsistent with the rest of the codebase.
### Recommendation
Add to `TauriConstants` for consistency:
```typescript
GET_PREFERENCES_SYNC: 'get_preferences_sync',
SET_PREFERENCES_SYNC: 'set_preferences_sync',
```
### Desktop Testing Required
Optional - basic functionality already works.
---
## ~~GAP-W04: OAuth Loopback Adapter Method~~ (Resolved)
### Status: **REMOVED** - Works correctly
### User Flow Trace
```
User Action What Actually Happens
─────────────────────────────────────────────────────────────────────────
Settings → Calendar → Connect integrations-section.tsx:131
└─ Calls initiateAuth(provider)
initiateAuth runs use-oauth-flow.ts:163-243
├─ Checks isTauriEnvironment()
├─ If Tauri: invoke('initiate_oauth_loopback')
│ (lines 189-193)
└─ If browser: window.open(authUrl)
Rust handles OAuth calendar.rs:73-77
├─ Starts local HTTP server
├─ Opens browser with OAuth URL
├─ Waits for callback
└─ Completes OAuth with server
Success use-oauth-flow.ts:197-208
├─ Fetches connection status
└─ Updates state to 'connected'
```
### Why This Was Incorrectly Flagged
The original analysis only checked if the method was in `tauri-adapter.ts`. It is not - but `use-oauth-flow.ts` **directly invokes** the Tauri command:
```typescript
// use-oauth-flow.ts:189-193
const result = await invoke<{
success: boolean;
integration_id: string | null;
error_message: string | null;
}>('initiate_oauth_loopback', { provider });
```
This pattern (direct `invoke()` bypassing the adapter) is used intentionally for specialized flows. The feature **works correctly**.
### Lesson Learned
Checking if a method exists in an adapter is insufficient. Must trace actual user flows to verify functionality.
---
## Verification Checklist
### For GAP-W05 (Post-Processing Orchestration) Implementation
**Quick Win (Day 1):**
- [ ] Enable `autoExtract: true` in MeetingDetail.tsx
**Client Components (Days 2-4):**
- [ ] Create `client/src/hooks/use-post-processing.ts`
- [ ] Create `client/src/components/meeting/processing-status.tsx`
- [ ] Add summary progress event listener
- [ ] Add auto-diarization trigger to `use-diarization.ts`
- [ ] Wire `usePostProcessing` into MeetingDetail.tsx
- [ ] Update Recording.tsx to handle processing transitions
**Backend (Days 3-5):**
- [ ] Add `ProcessingStatus` dataclass to Meeting entity
- [ ] Add `ProcessingStatus` message to proto
- [ ] Regenerate proto stubs
- [ ] Update GetMeeting to include processing status
- [ ] Add `ENTITIES_EXTRACTED` webhook event
- [ ] Add `DIARIZATION_COMPLETED` webhook event
**Testing:**
- [ ] Unit test: `usePostProcessing` hook state transitions
- [ ] Unit test: `ProcessingStatus` component rendering
- [ ] Integration test: Full post-processing flow
- [ ] E2E test: Recording → Processing → Complete flow on desktop
---
### For GAP-W01 (OIDC) Implementation
- [ ] Create Rust gRPC client module (`grpc/client/oidc.rs`)
- [ ] Add module to `grpc/client/mod.rs`
- [ ] Create Rust command handlers (`commands/oidc.rs`)
- [ ] Register commands in Tauri
- [ ] Add types to `grpc/types/`
- [ ] Add constants to `tauri-constants.ts`
- [ ] Add methods to `interface.ts`
- [ ] Implement in `tauri-adapter.ts`
- [ ] Add mock implementation in `mock-adapter.ts`
- [ ] Test OIDC flow on desktop app
### General Wiring Checklist
When adding a new gRPC RPC:
1. **Proto**`noteflow.proto` + regenerate stubs
2. **Python** → Create/update mixin in `_mixins/`
3. **Rust gRPC Client** → Add method to appropriate module in `grpc/client/`
4. **Rust Types** → Add types to `grpc/types/`
5. **Rust Commands** → Add Tauri command in `commands/`
6. **TS Constants** → Add command name to `tauri-constants.ts`
7. **TS Types** → Add request/response types to `api/types/`
8. **TS Interface** → Add method signature to `interface.ts`
9. **TS Adapter** → Implement in `tauri-adapter.ts`
10. **Mock Adapter** → Add mock implementation for browser dev
11. **Cached Adapter** → Add offline implementation or explicitly reject
12. **Tests** → Add tests at each layer