Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

39 KiB

Scratchpad: AI Streaming + Experimental Features

Key Discoveries

Storage Location for Experimental Features

After exploring the codebase, the best location for experimental feature settings is:

Recommended: tenant_settings.settings JSONB column

  • Already exists in tenant_settings table
  • Pattern already used for analytics settings (settings.analytics)
  • No migration needed for schema changes - just add keys to JSONB
  • Access via existing getTenantSettings() / updateTenantSettings() actions
  • Location: /packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts

Alternative considered but rejected:

  • PostHog feature flags: External dependency, not tenant-controlled
  • Separate table: Overkill for simple boolean toggles
  • Environment variables: Not tenant-specific

Current AI Gating

  • AI features currently gated by isEnterpriseEdition check (edition-level)
  • No tenant-level gating exists currently
  • Quick Ask and Chat both require EE but don't check tenant preferences

Relevant Files

  • Settings page: /server/src/components/settings/SettingsPage.tsx
  • Tenant settings actions: /packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts
  • Feature flags (for reference): /server/src/lib/feature-flags/featureFlags.ts
  • DefaultLayout (Quick Ask trigger): /server/src/components/layout/DefaultLayout.tsx
  • QuickAskOverlay: /server/src/components/chat/QuickAskOverlay.tsx (CE stub)
  • QuickAskOverlay (EE): /ee/server/src/components/chat/QuickAskOverlay.tsx
  • RightSidebar: /server/src/components/layout/RightSidebar.tsx
  • Chat completions: /ee/server/src/services/chatCompletionsService.ts
  • Chat stream: /ee/server/src/services/chatStreamService.ts

Existing Streaming Infrastructure

  • SSE endpoints exist at /api/chat/stream/*
  • ChatStreamService handles streaming but uses simulated typing effect currently
  • OpenRouter supports streaming via OpenAI SDK
  • Need to implement true token-by-token streaming

Settings Tab Pattern

Tabs are added to SettingsPage.tsx with:

  1. Tab label in tabLabels array
  2. Tab slug in tabLabelToSlug mapping
  3. Content component in tabs array with lazy loading

Decisions

  1. Experimental features stored in tenant_settings.settings.experimentalFeatures
  2. AI Assistant is the first (and only initial) experimental feature - key: aiAssistant
  3. Check tenant setting before allowing Quick Ask/Chat activation
  4. Streaming will use OpenRouter's native streaming via OpenAI SDK
  5. Permission: Reuse settings:update permission (no new permission needed)
  6. Effect timing: Page reload is acceptable after toggling - simpler than real-time context refresh

Open Questions (resolved)

  1. Q: Where to store experimental features? A: tenant_settings.settings.experimentalFeatures JSONB

  2. Q: How to gate Quick Ask globally? A: Check tenant setting in DefaultLayout before opening overlay

Commands / Runbook

# Test tenant settings
curl -X GET localhost:3000/api/v1/tenant-settings

# Check current streaming endpoint
curl -X POST localhost:3000/api/chat/stream/chat \
  -H "Content-Type: application/json" \
  -d '{"inputs": [{"role": "user", "content": "Hello"}]}'

Work Log

2026-01-23

  • Implemented getExperimentalFeatures() server action returning tenant_settings.settings.experimentalFeatures (defaults to {} when unset): packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts
  • Implemented updateExperimentalFeatures(features) server action with settings:update permission check and merge-into-JSON behavior via updateTenantSettings(): packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts
  • Validation: npm -w @alga-psa/tenancy run typecheck
  • Implemented isExperimentalFeatureEnabled(featureKey) server action (strict === true check; unknown/unset keys return false): packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts
  • Validation: npm -w @alga-psa/tenancy run typecheck
  • Implemented ExperimentalFeaturesSettings client component with load-on-mount + local toggle state: server/src/components/settings/general/ExperimentalFeaturesSettings.tsx
  • Validation: npx eslint server/src/components/settings/general/ExperimentalFeaturesSettings.tsx --max-warnings=0
  • Note: npm -w server run typecheck currently fails due to missing @ee/components/chat/QuickAskOverlay module import in server/src/components/chat/QuickAskOverlay.tsx (unrelated to experimental features UI).
  • Implemented "Experimental Features" settings entry point:
    • Added experimental-features slug mapping + tab content using dynamic import: server/src/components/settings/SettingsPage.tsx
    • Added Settings sidebar navigation item linking to /msp/settings?tab=experimental-features: server/src/config/menuConfig.ts
    • Validation: npx eslint server/src/components/settings/SettingsPage.tsx server/src/config/menuConfig.ts --max-warnings=0
  • Implemented AI Assistant toggle display copy per PRD (name + description): server/src/components/settings/general/ExperimentalFeaturesSettings.tsx
  • Validation: npx eslint server/src/components/settings/general/ExperimentalFeaturesSettings.tsx --max-warnings=0
  • Added warning banner copy for experimental feature stability: server/src/components/settings/general/ExperimentalFeaturesSettings.tsx
  • Validation: npx eslint server/src/components/settings/general/ExperimentalFeaturesSettings.tsx --max-warnings=0
  • Wired Save button to persist experimental feature toggles via updateExperimentalFeatures(); includes disabled state when unchanged and a success toast reminding to reload: server/src/components/settings/general/ExperimentalFeaturesSettings.tsx
  • Validation: npx eslint server/src/components/settings/general/ExperimentalFeaturesSettings.tsx --max-warnings=0
  • Implemented default-disabled experimental features behavior:
    • getExperimentalFeatures() now normalizes unset/malformed values to { aiAssistant: false }: packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts
    • initializeTenantSettings() now seeds settings.experimentalFeatures with { aiAssistant: false } for new tenants: packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts
  • Implemented Quick Ask shortcut gating (⌘↑/Ctrl↑) behind aiAssistant experimental feature flag in DefaultLayout (disabled until flag loads; no preventDefault when disabled): server/src/components/layout/DefaultLayout.tsx
  • Validation: npx eslint server/src/components/layout/DefaultLayout.tsx --max-warnings=0
  • Implemented Quick Ask overlay gating (only render QuickAskOverlay when aiAssistant is enabled): server/src/components/layout/DefaultLayout.tsx
  • Validation: npx eslint server/src/components/layout/DefaultLayout.tsx --max-warnings=0
  • Note: npm -w server run typecheck still fails with TS2307 for @ee/components/chat/QuickAskOverlay from server/src/components/chat/QuickAskOverlay.tsx (pre-existing).
  • Implemented Sidebar Chat gating:
    • ⌘L/Ctrl+L shortcut now ignored unless aiAssistant is enabled (no preventDefault when disabled)
    • Right sidebar is not rendered unless aiAssistant is enabled; also auto-closes if disabled
    • Updated Quick Ask “Open in Sidebar” handoff to no-op if aiAssistant is disabled
    • File: server/src/components/layout/DefaultLayout.tsx
  • Validation: npx eslint server/src/components/layout/DefaultLayout.tsx --max-warnings=0
  • Implemented API gating for chat completions:
    • /api/chat/v1/completions returns 403 with "AI Assistant is not enabled for this tenant" when aiAssistant is disabled
    • File: server/src/app/api/chat/v1/completions/route.ts
  • Validation: npx eslint server/src/app/api/chat/v1/completions/route.ts (existing no-undef warnings for process in route handlers)
  • Note: npm -w server run lint currently fails (next lint reports invalid project directory /server/lint).
  • Implemented API gating for chat execute:
    • /api/chat/v1/execute returns 403 with "AI Assistant is not enabled for this tenant" when aiAssistant is disabled
    • File: server/src/app/api/chat/v1/execute/route.ts
  • Validation: npx eslint server/src/app/api/chat/v1/execute/route.ts (existing no-undef warnings for process in route handlers)
  • Implemented API gating for chat streaming:
    • /api/chat/stream/title returns 403 with "AI Assistant is not enabled for this tenant" when aiAssistant is disabled
    • /api/chat/stream/* (slugged) returns 403 with "AI Assistant is not enabled for this tenant" when aiAssistant is disabled
    • Files: server/src/app/api/chat/stream/title/route.ts, server/src/app/api/chat/stream/[...slug]/route.ts
  • Validation: npx eslint server/src/app/api/chat/stream/title/route.ts server/src/app/api/chat/stream/[...slug]/route.ts --max-warnings=0
  • Note: npm -w server run typecheck still fails with TS2307 for @ee/components/chat/QuickAskOverlay from server/src/components/chat/QuickAskOverlay.tsx (pre-existing).
  • Implemented /api/chat/v1/completions/stream POST endpoint returning an SSE response (placeholder stream) with EE + aiAssistant gating:
    • File: server/src/app/api/chat/v1/completions/stream/route.ts
    • Note: currently sends a single SSE comment (: ok) then closes; token streaming is implemented in later items.
  • Validation: npx eslint server/src/app/api/chat/v1/completions/stream/route.ts
  • Implemented OpenRouter streaming support in ChatCompletionsService:
    • Added createRawCompletionStream() public helper for later SSE endpoints
    • Added generateStreamingCompletion() internal helper that calls OpenRouter with stream: true
    • File: ee/server/src/services/chatCompletionsService.ts
  • Validation: npm -w sebastian-ee run typecheck currently fails due to pre-existing TS2307 imports in ee/server/src/components/chat/QuickAskOverlay.tsx (unrelated to streaming support)
  • Implemented SSE token chunk formatting for streaming completions endpoint:
    • /api/chat/v1/completions/stream now reads request messages and streams tokens as data: {"content":"...","done":false}\n\n
    • File: server/src/app/api/chat/v1/completions/stream/route.ts
  • Validation: npx eslint server/src/app/api/chat/v1/completions/stream/route.ts --max-warnings=0
  • Implemented final SSE completion event:
    • /api/chat/v1/completions/stream now sends data: {"content":"","done":true}\n\n when the upstream stream completes (best-effort; skipped when request is aborted)
    • File: server/src/app/api/chat/v1/completions/stream/route.ts
  • Validation: npx eslint server/src/app/api/chat/v1/completions/stream/route.ts --max-warnings=0
  • Implemented Chat.tsx wiring to streaming endpoint:
    • ee/server/src/components/chat/Chat.tsx now posts to /api/chat/v1/completions/stream
    • Reads SSE response via response.body.getReader() and reconstructs final assistant content from data: {content, done} events
    • Note: UI still uses existing “typing” reveal once streaming completes; incremental token display is handled in the next feature item.
  • Validation: npx eslint ee/server/src/components/chat/Chat.tsx (warnings present in file; no errors)
  • Implemented true incremental token rendering in Chat.tsx:
    • readAssistantContentFromSse() now supports per-token callbacks
    • handleSend() updates incomingMessage as tokens arrive (throttled to animation frames) and removes the simulated typewriter reveal
    • Added generationIdRef guard so Stop invalidates the active generation and prevents late stream updates/persistence from racing in (does not abort the network request yet)
    • File: ee/server/src/components/chat/Chat.tsx
  • Validation: npx eslint ee/server/src/components/chat/Chat.tsx (warnings present in file; no errors)
  • Validation: npm -w sebastian-ee run typecheck still fails due to pre-existing TS2307 imports in ee/server/src/components/chat/QuickAskOverlay.tsx (unrelated to F021)
  • Implemented AbortController cancellation for true streaming:
    • handleSend() creates an AbortController per generation and passes signal to fetch()
    • Stop button now triggers AbortController.abort() so the network request is actually canceled mid-stream (no error banner for AbortError)
    • File: ee/server/src/components/chat/Chat.tsx
  • Validation: npx eslint ee/server/src/components/chat/Chat.tsx
  • Next feature item: F023 Display streaming indicator/cursor while tokens are being received

2026-01-23 (cont.)

  • Implemented streaming cursor indicator while tokens are being received:
    • Added showStreamingCursor prop to Message and render a blinking cursor glyph () after markdown content
    • Wired Chat incoming assistant message to set showStreamingCursor={generatingResponse && !isFunction} so it only shows during token streaming (not during the initial “Thinking...” phase)
    • Files: ee/server/src/components/message/Message.tsx, ee/server/src/components/message/message.css, ee/server/src/components/chat/Chat.tsx
  • Validation: npx eslint ee/server/src/components/chat/Chat.tsx ee/server/src/components/message/Message.tsx --max-warnings=9999 (warnings present; no errors)
  • Next feature item: F024 Handle stream interruption gracefully - show partial response with error indicator

2026-01-23 (cont.)

  • Implemented graceful stream interruption handling:
    • If the SSE stream ends without a done: true event, Chat now treats it as an interruption and shows the partial assistant content with an "Interrupted" indicator.
    • If a non-abort network/error occurs mid-stream, Chat shows the partial assistant content (if any) with an "Interrupted" indicator.
    • Partial/interrupted responses are not persisted; only fully completed (done: true) responses are persisted.
    • Files: ee/server/src/components/chat/Chat.tsx, ee/server/src/components/message/Message.tsx, ee/server/src/components/message/message.css
  • Validation: npx eslint ee/server/src/components/chat/Chat.tsx ee/server/src/components/message/Message.tsx --max-warnings=9999 (warnings present; no errors)
  • Next feature item: F025 Persist assistant message to database after streaming completes (final content)

2026-01-24

  • Finalized assistant message persistence after streaming completes:

    • Streaming completions now stash the persisted assistant message id per-generation (ref) so UI messages never reuse a prior assistant id when persistence is skipped (interrupts) or fails.
    • Only completed streams (done: true) attempt persistence; interrupted/partial messages still render but never persist.
    • File: ee/server/src/components/chat/Chat.tsx
  • Validation: npx eslint ee/server/src/components/chat/Chat.tsx --max-warnings=9999 (warnings present; no errors)

  • Next feature item: F026 Ensure Quick Ask expanded state uses streaming for responses

  • Fixed EE Quick Ask overlay module wiring so the expanded view can use the streaming Chat implementation:

    • Added @ee/components/chat/QuickAskOverlay shim that re-exports the real EE overlay from ee/server: packages/ee/src/components/chat/QuickAskOverlay.tsx
    • Updated EE overlay to use shared UI components (@alga-psa/ui) instead of non-existent server/src/components/ui/* imports: ee/server/src/components/chat/QuickAskOverlay.tsx
  • Validation: npm -w server run typecheck

  • Next feature item: F027 Ensure Sidebar Chat uses streaming for responses

  • Fixed Sidebar Chat EE component wiring so Sidebar chat renders the EE streaming Chat implementation (including for CE-first localhost dev):

    • Replaced @ee/components/layout/RightSidebar CE stub with a re-export shim to ee/server: packages/ee/src/components/layout/RightSidebar.tsx
  • Validation: npm -w server run typecheck

  • Next item: T001 getExperimentalFeatures() returns defaults when tenant settings are unavailable

2026-01-24 (cont.)

  • Implemented T001 (unit test):
    • Added Vitest unit test covering the no-tenant/no-settings path; expects { aiAssistant: false } defaults (PRD goal: experimental features default disabled).
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
    • Note: Updated tests.json wording since getExperimentalFeatures() intentionally normalizes to defaults rather than returning {}.
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts (repo-root npm run test:local currently fails due to dotenv -e CLI incompatibility)
  • Next test item: T002 getExperimentalFeatures() returns saved experimental features from tenant_settings

2026-01-24 (cont.)

  • Implemented T002 (unit test):
    • Added Vitest unit test covering the saved-settings path; expects stored experimentalFeatures.aiAssistant: true to be returned as { aiAssistant: true }.
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Next test item: T003 updateExperimentalFeatures() creates settings entry if none exists

2026-01-24 (cont.)

  • Implemented T003 (unit test):
    • Verifies updateExperimentalFeatures() upserts tenant_settings via insert(...).onConflict('tenant').merge(...) when no settings row exists.
    • Asserts written JSON includes experimentalFeatures.aiAssistant: true.
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Next test item: T004 updateExperimentalFeatures() merges with existing settings without overwriting other keys

2026-01-24 (cont.)

  • Implemented T004 (unit test):
    • Verifies updateExperimentalFeatures() preserves unrelated tenant_settings.settings keys (e.g. analytics) while updating experimentalFeatures.
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Next test item: T005 updateExperimentalFeatures() requires settings:update permission

2026-01-24 (cont.)

  • Implemented T005 (unit test):
    • Verifies updateExperimentalFeatures() rejects when the current user lacks settings:update.
    • Asserts no tenant DB access occurs (permission check happens before settings load/write).
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Next test item: T006 isExperimentalFeatureEnabled() returns false for unknown feature keys

2026-01-24 (cont.)

  • Implemented T006 (unit test):
    • Verifies isExperimentalFeatureEnabled() returns false for unknown keys (even when aiAssistant is enabled).
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Next test item: T007 isExperimentalFeatureEnabled('aiAssistant') returns false when not set

2026-01-24 (cont.)

  • Implemented T007 (unit test):
    • Verifies isExperimentalFeatureEnabled('aiAssistant') resolves to false when tenant_settings.settings.experimentalFeatures.aiAssistant is unset.
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Next test item: T008 isExperimentalFeatureEnabled('aiAssistant') returns true when enabled

2026-01-24 (cont.)

  • Implemented T008 (unit test):
    • Verifies isExperimentalFeatureEnabled('aiAssistant') resolves to true when tenant_settings.settings.experimentalFeatures.aiAssistant is set to true.
    • File: server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
  • Next test item: T009 ExperimentalFeaturesSettings component renders list of features with toggles

2026-01-24 (cont.)

  • Implemented T009 (unit test):
    • Verifies the Experimental Features settings page renders feature rows with toggles (UI reflection data-automation-id on switches).
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
    • Note: Added missing Vitest alias mapping for @alga-psa/tenancy/actions so client settings components can be tested under Vitest.
      • File: server/vitest.config.ts
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T010 ExperimentalFeaturesSettings loads current settings on mount

2026-01-24 (cont.)

  • Implemented T010 (unit test):
    • Verifies the feature toggle reflects the value loaded from getExperimentalFeatures() on mount (aria-checked state updates).
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T011 ExperimentalFeaturesSettings toggle updates local state

2026-01-24 (cont.)

  • Implemented T011 (unit test):
    • Verifies clicking the switch updates local UI state (aria-checked flips without saving).
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T012 Experimental Features tab appears in Settings navigation

2026-01-24 (cont.)

  • Implemented T012 (unit test):
    • Verifies Settings navigation includes an "Experimental Features" entry pointing at ?tab=experimental-features.
    • File: server/src/test/unit/menuConfig.experimentalFeatures.test.ts
  • Validation: npx vitest run server/src/test/unit/menuConfig.experimentalFeatures.test.ts
  • Next test item: T013 Experimental Features tab loads lazily

2026-01-24 (cont.)

  • Implemented T013 (unit test):
    • Verifies SettingsPage wires the Experimental Features tab via next/dynamic (lazy load).
    • File: server/src/test/unit/SettingsPage.experimentalFeatures.lazy.test.ts
    • Added test stubs/aliases so SettingsPage can be imported in Vitest without resolving product-only extension entrypoints.
      • Files: server/src/test/stubs/product-settings-extensions-entry.ts, server/vitest.config.ts
  • Validation: npx vitest run server/src/test/unit/SettingsPage.experimentalFeatures.lazy.test.ts
  • Next test item: T014 AI Assistant feature shows name 'AI Assistant' and description

2026-01-24 (cont.)

  • Implemented T014 (unit test):
    • Verifies the AI Assistant feature row renders the expected name + description text.
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T015 AI Assistant toggle defaults to off

2026-01-24 (cont.)

  • Implemented T015 (unit test):
    • Verifies AI Assistant defaults to off when the server returns no saved value (unset key).
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T016 Warning banner displays experimental features disclaimer

2026-01-24 (cont.)

  • Implemented T016 (unit test):
    • Verifies the warning banner copy renders on the Experimental Features settings screen.
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T017 Save button calls updateExperimentalFeatures() with current toggle states

2026-01-24 (cont.)

  • Implemented T017 (unit test):
    • Verifies Save calls updateExperimentalFeatures() with the current toggle state after changing it.
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T018 Save button shows success feedback after saving

2026-01-24 (cont.)

  • Implemented T018 (unit test):
    • Verifies save triggers a success toast (prompting the user to reload to apply changes).
    • File: server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Validation: npx vitest run server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
  • Next test item: T019 Quick Ask shortcut (⌘↑) is ignored when aiAssistant is disabled

2026-01-24 (cont.)

  • Implemented T019 (unit test):
    • Verifies ⌘↑/Ctrl↑ keydown is ignored (no preventDefault, overlay stays unrendered) when aiAssistant is disabled.
    • File: server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
  • Next test item: T020 Quick Ask shortcut (⌘↑) works when aiAssistant is enabled

2026-01-24 (cont.)

  • Implemented T020 (unit test):
    • Verifies ⌘↑ keydown preventDefault() is called and QuickAskOverlay opens when aiAssistant is enabled.
    • File: server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
  • Next test item: T021 QuickAskOverlay is not rendered when aiAssistant is disabled

2026-01-24 (cont.)

  • Implemented T021 (unit test):
    • Verifies DefaultLayout does not render QuickAskOverlay at all when aiAssistant is disabled (even before any shortcut is pressed).
    • File: server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
  • Next test item: T022 QuickAskOverlay is rendered when aiAssistant is enabled

2026-01-24 (cont.)

  • Implemented T022 (unit test):
    • Verifies DefaultLayout renders QuickAskOverlay (closed) when aiAssistant is enabled, without needing the shortcut.
    • File: server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
  • Next test item: T023 Sidebar Chat toggle (⌘L) is ignored when aiAssistant is disabled

2026-01-24 (cont.)

  • Implemented T023 (unit test):
    • Verifies ⌘L/Ctrl+L keydown is ignored (no preventDefault) when aiAssistant is disabled.
    • File: server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
  • Next test item: T024 RightSidebar chat is hidden when aiAssistant is disabled

2026-01-24 (cont.)

  • Implemented T024 (unit test):
    • Verifies DefaultLayout does not render RightSidebar when aiAssistant is disabled.
    • File: server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
  • Next test item: T025 Sidebar Chat works normally when aiAssistant is enabled

2026-01-24 (cont.)

  • Implemented T025 (unit test):
    • Verifies DefaultLayout renders RightSidebar when aiAssistant is enabled and that ⌘L/Ctrl+L toggles it open/closed (with preventDefault()).
    • File: server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
  • Next test item: T026 /api/chat/v1/completions returns 403 when aiAssistant is disabled

2026-01-24 (cont.)

  • Implemented T026 (unit test):
    • Verifies /api/chat/v1/completions returns 403 + JSON error when aiAssistant is disabled.
    • File: server/src/test/unit/api/chatCompletions.route.gating.test.ts
    • Added Vitest alias stub for @product/chat/entry so Next route modules can be imported in tests.
      • Files: server/vitest.config.ts, server/src/test/stubs/product-chat-entry.ts
  • Validation: npx vitest run server/src/test/unit/api/chatCompletions.route.gating.test.ts
  • Next test item: T027 /api/chat/v1/completions returns 200 when aiAssistant is enabled

2026-01-24 (cont.)

  • Implemented T027 (unit test):
    • Verifies /api/chat/v1/completions returns 200 when aiAssistant is enabled and delegates to ChatCompletionsService.handleRequest().
    • File: server/src/test/unit/api/chatCompletions.route.gating.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatCompletions.route.gating.test.ts
  • Next test item: T028 /api/chat/v1/execute returns 403 when aiAssistant is disabled

2026-01-24 (cont.)

  • Implemented T028 (unit test):
    • Verifies /api/chat/v1/execute returns 403 + JSON error when aiAssistant is disabled.
    • File: server/src/test/unit/api/chatExecute.route.gating.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatExecute.route.gating.test.ts
  • Next test item: T029 /api/chat/stream/* returns 403 when aiAssistant is disabled

2026-01-24 (cont.)

  • Implemented T029 (unit test):
    • Verifies /api/chat/stream/title and /api/chat/stream/[...slug] return 403 + JSON error when aiAssistant is disabled.
    • File: server/src/test/unit/api/chatStream.route.gating.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatStream.route.gating.test.ts
  • Next test item: T030 /api/chat/v1/completions/stream endpoint exists and accepts POST

2026-01-24 (cont.)

  • Implemented T030 (unit test):
    • Verifies /api/chat/v1/completions/stream exports POST and returns a 200 response for a valid POST request when aiAssistant is enabled.
    • File: server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Next test item: T031 /api/chat/v1/completions/stream returns Content-Type: text/event-stream

2026-01-24 (cont.)

  • Implemented T031 (unit test):
    • Verifies /api/chat/v1/completions/stream responds with Content-Type: text/event-stream (allows charset suffix).
    • File: server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Next test item: T032 Streaming endpoint passes stream: true to OpenRouter API

2026-01-24 (cont.)

  • Implemented T032 (unit test):
    • Verifies ChatCompletionsService.createRawCompletionStream() passes stream: true into the OpenAI/OpenRouter SDK call.
    • File: server/src/test/unit/services/chatCompletionsService.streaming.test.ts
    • Note: Added Vitest path aliases for @alga-psa/users so EE service modules can be imported in unit tests.
      • File: server/vitest.config.ts
  • Validation: npx vitest run server/src/test/unit/services/chatCompletionsService.streaming.test.ts
  • Next test item: T033 Streaming response chunks follow SSE format with data: prefix

2026-01-24 (cont.)

  • Implemented T033 (unit test):
    • Verifies /api/chat/v1/completions/stream emits SSE events that start with data: (SSE framing) when tokens are streamed.
    • File: server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Next test item: T034 Each SSE chunk contains JSON with content and done fields

2026-01-24 (cont.)

  • Implemented T034 (unit test):
    • Verifies each SSE data: event is valid JSON containing content (string) and done (boolean).
    • File: server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Next test item: T035 Final SSE message has done: true

2026-01-24 (cont.)

  • Implemented T035 (unit test):
    • Verifies the streaming completions endpoint finishes with a final SSE event { content: "", done: true } (and prior events are done:false).
    • File: server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Validation: npx vitest run server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
  • Next test item: T036 Chat.tsx uses streaming endpoint for new messages

2026-01-24 (cont.)

  • Implemented T036 (unit test):
    • Verifies EE Chat.tsx targets /api/chat/v1/completions/stream (and not /api/chat/v1/completions) and posts messages: conversationWithUser.
    • Uses ?raw source import to avoid executing the Next.js client component in Vitest.
    • File: server/src/test/unit/Chat.streamingEndpoint.test.ts
  • Validation: npx vitest run server/src/test/unit/Chat.streamingEndpoint.test.ts
  • Next test item: T037 Chat.tsx reads streaming response via getReader()

2026-01-24 (cont.)

  • Implemented T037 (unit test):
    • Verifies EE Chat.tsx reads the streaming response via response.body.getReader() and await reader.read().
    • File: server/src/test/unit/Chat.streamingEndpoint.test.ts
  • Validation: npx vitest run server/src/test/unit/Chat.streamingEndpoint.test.ts
  • Next test item: T038 Tokens are appended to message display as they arrive

2026-01-24 (cont.)

  • Implemented T038 (unit test):
    • Verifies SSE token chunks are appended incrementally (per-chunk) by readAssistantContentFromSse() via the onToken callback.
    • Files: server/src/test/unit/readAssistantContentFromSse.test.ts, ee/server/src/components/chat/readAssistantContentFromSse.ts, ee/server/src/components/chat/Chat.tsx
  • Validation: npx vitest run server/src/test/unit/readAssistantContentFromSse.test.ts
  • Next test item: T039 Message state updates incrementally during streaming

2026-01-24 (cont.)

  • Implemented T039 (unit test):
    • Renders EE Chat and drives a controlled SSE Response to verify the in-progress assistant message updates as tokens arrive.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
    • Added EE component entrypoint shims to re-export from the .tsx source so Vitest can import @ee/components/chat/Chat and @ee/components/message/Message cleanly.
    • Files: ee/server/src/components/chat/Chat.js, ee/server/src/components/message/Message.js
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T040 Stop button triggers AbortController.abort() during streaming

2026-01-24 (cont.)

  • Implemented T040 (unit test):
    • Verifies clicking STOP during an active streaming request calls AbortController.abort().
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T041 Aborting stream stops token display and ends generation state

2026-01-24 (cont.)

  • Implemented T041 (unit test):
    • Verifies clicking STOP ends generating state (button returns to SEND, input enabled) and prevents additional streamed tokens from updating the message display.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T042 Streaming indicator is visible while receiving tokens

2026-01-24 (cont.)

  • Implemented T042 (unit test):
    • Verifies the assistant message shows the streaming cursor (.message-streaming-cursor) after the first streamed token arrives.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T043 Streaming indicator disappears when done: true received

2026-01-24 (cont.)

  • Implemented T043 (unit test):
    • Verifies the streaming cursor is removed after the final SSE { done: true } event is received.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T044 Network error during streaming shows partial response

2026-01-24 (cont.)

  • Implemented T044 (unit test):
    • Verifies a mid-stream read error results in an Interrupted assistant message with the partial content preserved.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T045 Stream interruption shows error indicator on message

2026-01-24 (cont.)

  • Implemented T045 (unit test):
    • Verifies a stream that ends without a { done: true } event shows the Interrupted badge and keeps the partial text.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T046 Assistant message is persisted after streaming completes successfully

2026-01-24 (cont.)

  • Implemented T046 (unit test):
    • Verifies the completed stream triggers an assistant persistence write (chat_role: 'bot') after { done: true }.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T047 Persisted message content matches final streamed content

2026-01-24 (cont.)

  • Implemented T047 (unit test):
    • Verifies the persisted assistant message content matches the concatenated streamed tokens.
    • File: server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Validation: npx vitest run server/src/test/unit/Chat.streamingIncrementalState.test.tsx
  • Next test item: T048 Quick Ask expanded view streams responses

2026-01-24 (cont.)

  • Implemented T048 (unit test):
    • Verifies the Quick Ask overlay expands into the EE Chat component and posts to /api/chat/v1/completions/stream.
    • File: server/src/test/unit/QuickAskOverlay.streaming.test.tsx
  • Validation: npx vitest run server/src/test/unit/QuickAskOverlay.streaming.test.tsx
  • Next test item: T049 Sidebar Chat streams responses

2026-01-24 (cont.)

  • Implemented T049 (unit test):
    • Verifies the EE Right Sidebar renders the streaming Chat implementation and posts to /api/chat/v1/completions/stream.
    • File: server/src/test/unit/RightSidebar.streaming.test.tsx
  • Validation: npx vitest run server/src/test/unit/RightSidebar.streaming.test.tsx
  • Next test item: T050 Enabling AI Assistant allows Quick Ask usage after page reload

2026-01-24 (cont.)

  • Implemented T050 (unit test):
    • Verifies DefaultLayout reads the experimental feature state on mount; after a simulated "reload" (unmount/mount) with aiAssistant enabled, the Quick Ask shortcut opens the overlay.
    • File: server/src/test/unit/layout/DefaultLayout.aiAssistantReload.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.aiAssistantReload.test.tsx
  • Next test item: T051 Disabling AI Assistant prevents Quick Ask usage after page reload

2026-01-24 (cont.)

  • Implemented T051 (unit test):
    • Verifies after a simulated "reload" with aiAssistant disabled, the Quick Ask overlay is not rendered and the ⌘↑ shortcut is ignored.
    • File: server/src/test/unit/layout/DefaultLayout.aiAssistantReload.test.tsx
  • Validation: npx vitest run server/src/test/unit/layout/DefaultLayout.aiAssistantReload.test.tsx