- Updated basedpyright linting results (705 files analyzed, analysis time reduced from 22.928s to 13.105s). - Updated biome linting artifact with warning about unnecessary hook dependency (preferencesVersion) in MeetingDetail.tsx.
10 KiB
Implementation Checklist: SPRINT-GAP-012
Step-by-step implementation guide for state synchronization and observability fixes.
Pre-Implementation
- Read and understand the full README.md
- Ensure the dev backend is running (do not run Docker commands without explicit permission)
- Run
make qualityto establish baseline (only if you plan code changes)
Phase 1: Server Address Timing Fix (Priority: P1)
1.1 Add effectiveServerUrl Refresh After Connect
File: client/src/pages/Settings.tsx
Find handleConnect() (around line 230) and add URL refresh after successful connection:
const handleConnect = async () => {
setIsConnecting(true);
try {
const normalized = normalizeServerInput(serverHost, serverPort);
if (normalized.host !== serverHost) {
setServerHost(normalized.host);
}
if (normalized.port !== serverPort) {
setServerPort(normalized.port);
}
const hostValue = normalized.host;
const portValue = normalized.port;
localStorage.setItem('noteflow_server_address_override', 'true');
localStorage.setItem(
'noteflow_server_address_override_value',
JSON.stringify({ host: hostValue, port: portValue, updated_at: Date.now() })
);
if (hostValue || portValue) {
preferences.setServerConnection(hostValue, portValue);
}
const updatedPrefs = preferences.get();
const api = isTauriEnvironment() ? await initializeTauriAPI() : getAPI();
await api.savePreferences(updatedPrefs);
const info = await api.connect(buildServerUrl(hostValue, portValue));
setIsConnected(true);
setServerInfo(info);
// ADD: Refresh effective URL after successful connection
const urlInfo = await api.getEffectiveServerUrl();
setEffectiveServerUrl(urlInfo);
} catch (error) {
setIsConnected(false);
toastError({
title: 'Connection Failed',
error,
fallback: 'Unable to reach server',
});
} finally {
setIsConnecting(false);
}
};
Verify:
- Connect to a new server address
- Navigate to another page
- Navigate back to Settings
- Address should match what was saved
1.2 Add E2E Test for Address Persistence
File: client/e2e/settings-ui.spec.ts (extend existing suite)
import { test, expect } from '@playwright/test';
import { navigateTo, waitForLoadingComplete } from './fixtures';
test.describe('Server Connection Section', () => {
test('server address persists across navigation', async ({ page }) => {
await navigateTo(page, '/settings?tab=status');
await waitForLoadingComplete(page);
const hostInput = page.locator('#host');
const portInput = page.locator('#port');
await hostInput.clear();
await hostInput.fill('127.0.0.1');
await portInput.clear();
await portInput.fill('50051');
// Navigate away
await page.goto('/');
await page.waitForLoadState('networkidle');
await navigateTo(page, '/settings?tab=status');
await waitForLoadingComplete(page);
// Verify values persisted
await expect(hostInput).toHaveValue('127.0.0.1');
await expect(portInput).toHaveValue('50051');
});
});
Verify:
- Run
make e2eornpx playwright test settings-ui - Tests pass
Phase 2: OAuth State Consistency (Priority: P1)
2.1 Extend Existing Validation Helper
File: client/src/lib/integration-utils.ts
The file already has hasRequiredIntegrationFields(). Add OIDC case (and optionally a wrapper):
// In the existing switch statement, add OIDC case:
export function hasRequiredIntegrationFields(integration: Integration): boolean {
switch (integration.type) {
case 'auth':
return !!(integration.oauth_config?.client_id && integration.oauth_config?.client_secret);
case 'email':
return integration.email_config?.provider_type === 'api'
? !!integration.email_config?.api_key
: !!(integration.email_config?.smtp_host && integration.email_config?.smtp_username);
case 'calendar':
return !!(integration.oauth_config?.client_id && integration.oauth_config?.client_secret);
case 'pkm':
return !!(integration.pkm_config?.api_key || integration.pkm_config?.vault_path);
case 'custom':
return !!integration.webhook_config?.url;
// ADD THIS CASE:
case 'oidc':
return !!(integration.oidc_config?.issuer_url && integration.oidc_config?.client_id);
default:
return false;
}
}
// Optional convenience wrapper:
/** Check if integration is connected AND has required credentials */
export const isEffectivelyConnected = (integration: Integration): boolean =>
integration.status === 'connected' && hasRequiredIntegrationFields(integration);
Verify:
- File compiles without errors
- Run
npm run type-check
2.2 Add Validation to handleIntegrationToggle
File: client/src/components/settings/integrations-section.tsx
Find handleIntegrationToggle (around line 122) and add validation:
import { hasRequiredIntegrationFields } from '@/lib/integration-utils';
const handleIntegrationToggle = (integration: Integration) => {
// ADD: Validate before allowing connect
if (integration.status === ConnStatus.DISCONNECTED) {
if (!hasRequiredIntegrationFields(integration)) {
toast({
title: 'Missing credentials',
description: `Configure ${integration.name} credentials before connecting`,
variant: 'destructive',
});
return;
}
}
const newStatus =
integration.status === ConnStatus.CONNECTED ? ConnStatus.DISCONNECTED : ConnStatus.CONNECTED;
preferences.updateIntegration(integration.id, { status: newStatus });
setIntegrations(preferences.getIntegrations());
toast({
title: newStatus === ConnStatus.CONNECTED ? 'Connected' : 'Disconnected',
description: `${integration.name} ${newStatus}`,
});
};
Verify:
- Toggling integration without credentials shows error toast
- Toggling integration with valid credentials succeeds
- Disconnecting always works (no validation needed)
2.3 Add Credential Warning Indicator (Optional Enhancement)
File: Find the integration card/list component
Add visual indicator for "connected" integrations missing credentials:
import { AlertTriangle } from 'lucide-react';
import { hasRequiredIntegrationFields } from '@/lib/integration-utils';
// Near the status indicator:
{integration.status === 'connected' && !hasRequiredIntegrationFields(integration) && (
<Tooltip>
<TooltipTrigger>
<AlertTriangle className="h-4 w-4 text-warning" />
</TooltipTrigger>
<TooltipContent>
Credentials missing - integration may not work correctly
</TooltipContent>
</Tooltip>
)}
Verify:
- Warning icon appears on misconfigured integrations
- Tooltip provides helpful guidance
Phase 3: E2E Test Enrichment (Priority: P2)
Use existing fixtures from client/e2e/fixtures.ts (callAPI, navigateTo, waitForAPI, waitForLoadingComplete).
3.1 Add Integration Validation Test
File: client/e2e/ui-integration.spec.ts or client/e2e/settings-ui.spec.ts (extend existing suite)
import { test, expect } from '@playwright/test';
import { navigateTo, waitForLoadingComplete } from './fixtures';
const shouldRun = process.env.NOTEFLOW_E2E === '1';
test.describe('Integration Validation', () => {
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable end-to-end tests.');
test('integration without credentials warns on connect', async ({ page }) => {
await navigateTo(page, '/settings?tab=integrations');
await waitForLoadingComplete(page);
const toggleButton = page.locator('button, [role=\"switch\"]').filter({ hasText: /connect/i });
if (await toggleButton.first().isVisible()) {
await toggleButton.first().click();
const toast = page.locator('[role=\"alert\"]');
await expect(toast).toBeVisible();
}
});
});
Verify:
- Run
NOTEFLOW_E2E=1 npx playwright test ui-integration settings-ui - Tests pass
Phase 4: Logging Improvements (Priority: P2)
4.1 Audit contextlib.suppress Usage
Run this search to find all suppress usages:
rg -n "contextlib\\.suppress" src/noteflow
For each occurrence, evaluate:
- Is the exception being logged before suppression?
- Should this be a try/except with logging instead?
4.2 Add Logging to Silent Returns
Search for silent returns in critical paths:
rg -n "return$" src/noteflow/grpc/_mixins | head -20
For each early return:
- Add
logger.debug()explaining why the return happened - Include relevant context (IDs, state)
4.3 Document Logging Standards
File: docs/observability.md (append section) or docs/logging-standards.md (new file under docs/)
## Logging Standards
### Principles
1. **Never suppress without logging**: Replace `contextlib.suppress()` with explicit try/except
2. **Log early returns**: Any function that returns early should log at DEBUG level
3. **Structured logging**: Always include context (IDs, operation names)
4. **Appropriate levels**:
- ERROR: Unexpected failures
- WARNING: Expected failures (validation errors)
- INFO: Significant state changes
- DEBUG: Operational flow details
### Examples
#### Before (Bad)
```python
with contextlib.suppress(Exception):
await risky_operation()
After (Good)
try:
await risky_operation()
except SpecificError as e:
logger.warning("Expected error in operation", error=str(e))
except Exception as e:
logger.error("Unexpected error in operation", error=str(e), exc_info=True)
---
## Validation Checklist
After completing all phases:
- [ ] `make quality` passes (when code changes are made)
- [ ] `make e2e` passes (or tests appropriately skipped when added)
- [ ] Server address persists across navigation (manual test)
- [ ] Integration warning badges appear correctly
- [ ] No new TypeScript or Python lint errors
## Rollback Plan
If issues arise:
1. Revert changes to `Settings.tsx`
2. Remove new test files (they don't affect production)
3. Keep documentation updates