# 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 ```bash # 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`