Files
noteflow/docs/sprints/phase-ongoing/sprint-gap-012-state-sync-and-observability/IMPLEMENTATION_CHECKLIST.md
Travis Vasceannie 0a18f2d23d chore: update linting artifacts
- 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.
2026-01-08 21:45:05 -05:00

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 quality to 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 e2e or npx 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