Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
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_settingstable - 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
isEnterpriseEditioncheck (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/* ChatStreamServicehandles 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:
- Tab label in
tabLabelsarray - Tab slug in
tabLabelToSlugmapping - Content component in tabs array with lazy loading
Decisions
- Experimental features stored in
tenant_settings.settings.experimentalFeatures - AI Assistant is the first (and only initial) experimental feature - key:
aiAssistant - Check tenant setting before allowing Quick Ask/Chat activation
- Streaming will use OpenRouter's native streaming via OpenAI SDK
- Permission: Reuse
settings:updatepermission (no new permission needed) - Effect timing: Page reload is acceptable after toggling - simpler than real-time context refresh
Open Questions (resolved)
-
Q: Where to store experimental features? A:
tenant_settings.settings.experimentalFeaturesJSONB -
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 returningtenant_settings.settings.experimentalFeatures(defaults to{}when unset):packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts - Implemented
updateExperimentalFeatures(features)server action withsettings:updatepermission check and merge-into-JSON behavior viaupdateTenantSettings():packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts - Validation:
npm -w @alga-psa/tenancy run typecheck - Implemented
isExperimentalFeatureEnabled(featureKey)server action (strict=== truecheck; unknown/unset keys return false):packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts - Validation:
npm -w @alga-psa/tenancy run typecheck - Implemented
ExperimentalFeaturesSettingsclient 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 typecheckcurrently fails due to missing@ee/components/chat/QuickAskOverlaymodule import inserver/src/components/chat/QuickAskOverlay.tsx(unrelated to experimental features UI). - Implemented "Experimental Features" settings entry point:
- Added
experimental-featuresslug 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
- Added
- 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.tsinitializeTenantSettings()now seedssettings.experimentalFeatureswith{ aiAssistant: false }for new tenants:packages/tenancy/src/actions/tenant-settings-actions/tenantSettingsActions.ts
- Implemented Quick Ask shortcut gating (⌘↑/Ctrl↑) behind
aiAssistantexperimental feature flag inDefaultLayout(disabled until flag loads; nopreventDefaultwhen 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
QuickAskOverlaywhenaiAssistantis enabled):server/src/components/layout/DefaultLayout.tsx - Validation:
npx eslint server/src/components/layout/DefaultLayout.tsx --max-warnings=0 - Note:
npm -w server run typecheckstill fails withTS2307for@ee/components/chat/QuickAskOverlayfromserver/src/components/chat/QuickAskOverlay.tsx(pre-existing). - Implemented Sidebar Chat gating:
- ⌘L/Ctrl+L shortcut now ignored unless
aiAssistantis enabled (nopreventDefaultwhen disabled) - Right sidebar is not rendered unless
aiAssistantis enabled; also auto-closes if disabled - Updated Quick Ask “Open in Sidebar” handoff to no-op if
aiAssistantis disabled - File:
server/src/components/layout/DefaultLayout.tsx
- ⌘L/Ctrl+L shortcut now ignored unless
- Validation:
npx eslint server/src/components/layout/DefaultLayout.tsx --max-warnings=0 - Implemented API gating for chat completions:
/api/chat/v1/completionsreturns 403 with"AI Assistant is not enabled for this tenant"whenaiAssistantis disabled- File:
server/src/app/api/chat/v1/completions/route.ts
- Validation:
npx eslint server/src/app/api/chat/v1/completions/route.ts(existingno-undefwarnings forprocessin route handlers) - Note:
npm -w server run lintcurrently fails (next lintreports invalid project directory/server/lint). - Implemented API gating for chat execute:
/api/chat/v1/executereturns 403 with"AI Assistant is not enabled for this tenant"whenaiAssistantis disabled- File:
server/src/app/api/chat/v1/execute/route.ts
- Validation:
npx eslint server/src/app/api/chat/v1/execute/route.ts(existingno-undefwarnings forprocessin route handlers) - Implemented API gating for chat streaming:
/api/chat/stream/titlereturns 403 with"AI Assistant is not enabled for this tenant"whenaiAssistantis disabled/api/chat/stream/*(slugged) returns 403 with"AI Assistant is not enabled for this tenant"whenaiAssistantis 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 typecheckstill fails withTS2307for@ee/components/chat/QuickAskOverlayfromserver/src/components/chat/QuickAskOverlay.tsx(pre-existing). - Implemented
/api/chat/v1/completions/streamPOST endpoint returning an SSE response (placeholder stream) with EE +aiAssistantgating:- 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.
- File:
- 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 withstream: true - File:
ee/server/src/services/chatCompletionsService.ts
- Added
- Validation:
npm -w sebastian-ee run typecheckcurrently fails due to pre-existing TS2307 imports inee/server/src/components/chat/QuickAskOverlay.tsx(unrelated to streaming support) - Implemented SSE token chunk formatting for streaming completions endpoint:
/api/chat/v1/completions/streamnow reads requestmessagesand streams tokens asdata: {"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/streamnow sendsdata: {"content":"","done":true}\n\nwhen 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.tsxnow posts to/api/chat/v1/completions/stream- Reads SSE response via
response.body.getReader()and reconstructs final assistant content fromdata: {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 callbackshandleSend()updatesincomingMessageas tokens arrive (throttled to animation frames) and removes the simulated typewriter reveal- Added
generationIdRefguard 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 typecheckstill fails due to pre-existing TS2307 imports inee/server/src/components/chat/QuickAskOverlay.tsx(unrelated to F021) - Implemented AbortController cancellation for true streaming:
handleSend()creates anAbortControllerper generation and passessignaltofetch()- 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
showStreamingCursorprop toMessageand 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
- Added
- 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: trueevent, 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
- If the SSE stream ends without a
- 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
Chatimplementation:- Added
@ee/components/chat/QuickAskOverlayshim that re-exports the real EE overlay fromee/server:packages/ee/src/components/chat/QuickAskOverlay.tsx - Updated EE overlay to use shared UI components (
@alga-psa/ui) instead of non-existentserver/src/components/ui/*imports:ee/server/src/components/chat/QuickAskOverlay.tsx
- Added
-
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
Chatimplementation (including for CE-first localhost dev):- Replaced
@ee/components/layout/RightSidebarCE stub with a re-export shim toee/server:packages/ee/src/components/layout/RightSidebar.tsx
- Replaced
-
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.jsonwording sincegetExperimentalFeatures()intentionally normalizes to defaults rather than returning{}.
- Added Vitest unit test covering the no-tenant/no-settings path; expects
- Validation:
npx vitest run server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts(repo-rootnpm run test:localcurrently fails due todotenv -eCLI 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: trueto be returned as{ aiAssistant: true }. - File:
server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
- Added Vitest unit test covering the saved-settings path; expects stored
- 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()upsertstenant_settingsviainsert(...).onConflict('tenant').merge(...)when no settings row exists. - Asserts written JSON includes
experimentalFeatures.aiAssistant: true. - File:
server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
- Verifies
- 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 unrelatedtenant_settings.settingskeys (e.g.analytics) while updatingexperimentalFeatures. - File:
server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
- Verifies
- 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 lackssettings:update. - Asserts no tenant DB access occurs (permission check happens before settings load/write).
- File:
server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
- Verifies
- 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()returnsfalsefor unknown keys (even whenaiAssistantis enabled). - File:
server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
- Verifies
- 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 tofalsewhentenant_settings.settings.experimentalFeatures.aiAssistantis unset. - File:
server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
- Verifies
- 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 totruewhentenant_settings.settings.experimentalFeatures.aiAssistantis set totrue. - File:
server/src/test/unit/tenantSettingsActions.experimentalFeatures.test.ts
- Verifies
- 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-idon switches). - File:
server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx - Note: Added missing Vitest alias mapping for
@alga-psa/tenancy/actionsso client settings components can be tested under Vitest.- File:
server/vitest.config.ts
- File:
- Verifies the Experimental Features settings page renders feature rows with toggles (UI reflection
- 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-checkedstate updates). - File:
server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
- Verifies the feature toggle reflects the value loaded from
- 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-checkedflips without saving). - File:
server/src/test/unit/components/ExperimentalFeaturesSettings.test.tsx
- Verifies clicking the switch updates local UI state (
- 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
- Verifies Settings navigation includes an "Experimental Features" entry pointing at
- 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
- Files:
- Verifies SettingsPage wires the Experimental Features tab via
- 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
- Verifies Save calls
- 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
aiAssistantis disabled. - File:
server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
- Verifies ⌘↑/Ctrl↑ keydown is ignored (no preventDefault, overlay stays unrendered) when
- 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 andQuickAskOverlayopens whenaiAssistantis enabled. - File:
server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
- Verifies ⌘↑ keydown
- 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
DefaultLayoutdoes not renderQuickAskOverlayat all whenaiAssistantis disabled (even before any shortcut is pressed). - File:
server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
- Verifies
- 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
DefaultLayoutrendersQuickAskOverlay(closed) whenaiAssistantis enabled, without needing the shortcut. - File:
server/src/test/unit/layout/DefaultLayout.quickAskShortcut.test.tsx
- Verifies
- 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
aiAssistantis disabled. - File:
server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
- Verifies ⌘L/Ctrl+L keydown is ignored (no preventDefault) when
- 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
DefaultLayoutdoes not renderRightSidebarwhenaiAssistantis disabled. - File:
server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
- Verifies
- 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
DefaultLayoutrendersRightSidebarwhenaiAssistantis enabled and that ⌘L/Ctrl+L toggles it open/closed (withpreventDefault()). - File:
server/src/test/unit/layout/DefaultLayout.sidebarChatShortcut.test.tsx
- Verifies
- 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/completionsreturns 403 + JSON error whenaiAssistantis disabled. - File:
server/src/test/unit/api/chatCompletions.route.gating.test.ts - Added Vitest alias stub for
@product/chat/entryso Next route modules can be imported in tests.- Files:
server/vitest.config.ts,server/src/test/stubs/product-chat-entry.ts
- Files:
- Verifies
- 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/completionsreturns 200 whenaiAssistantis enabled and delegates toChatCompletionsService.handleRequest(). - File:
server/src/test/unit/api/chatCompletions.route.gating.test.ts
- Verifies
- 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/executereturns 403 + JSON error whenaiAssistantis disabled. - File:
server/src/test/unit/api/chatExecute.route.gating.test.ts
- Verifies
- 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/titleand/api/chat/stream/[...slug]return 403 + JSON error whenaiAssistantis disabled. - File:
server/src/test/unit/api/chatStream.route.gating.test.ts
- Verifies
- 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/streamexportsPOSTand returns a 200 response for a valid POST request whenaiAssistantis enabled. - File:
server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
- Verifies
- 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/streamresponds withContent-Type: text/event-stream(allows charset suffix). - File:
server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
- Verifies
- 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()passesstream: trueinto the OpenAI/OpenRouter SDK call. - File:
server/src/test/unit/services/chatCompletionsService.streaming.test.ts - Note: Added Vitest path aliases for
@alga-psa/usersso EE service modules can be imported in unit tests.- File:
server/vitest.config.ts
- File:
- Verifies
- 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/streamemits SSE events that start withdata:(SSE framing) when tokens are streamed. - File:
server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
- Verifies
- 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 containingcontent(string) anddone(boolean). - File:
server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
- Verifies each SSE
- 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 aredone:false). - File:
server/src/test/unit/api/chatCompletionsStream.route.exists.test.ts
- Verifies the streaming completions endpoint finishes with a final SSE event
- 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.tsxtargets/api/chat/v1/completions/stream(and not/api/chat/v1/completions) and postsmessages: conversationWithUser. - Uses
?rawsource import to avoid executing the Next.js client component in Vitest. - File:
server/src/test/unit/Chat.streamingEndpoint.test.ts
- Verifies EE
- 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.tsxreads the streaming response viaresponse.body.getReader()andawait reader.read(). - File:
server/src/test/unit/Chat.streamingEndpoint.test.ts
- Verifies EE
- 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 theonTokencallback. - Files:
server/src/test/unit/readAssistantContentFromSse.test.ts,ee/server/src/components/chat/readAssistantContentFromSse.ts,ee/server/src/components/chat/Chat.tsx
- Verifies SSE token chunks are appended incrementally (per-chunk) by
- 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
Chatand drives a controlled SSEResponseto 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
.tsxsource so Vitest can import@ee/components/chat/Chatand@ee/components/message/Messagecleanly. - Files:
ee/server/src/components/chat/Chat.js,ee/server/src/components/message/Message.js
- Renders EE
- 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
STOPduring an active streaming request callsAbortController.abort(). - File:
server/src/test/unit/Chat.streamingIncrementalState.test.tsx
- Verifies clicking
- 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
STOPends generating state (button returns toSEND, input enabled) and prevents additional streamed tokens from updating the message display. - File:
server/src/test/unit/Chat.streamingIncrementalState.test.tsx
- Verifies clicking
- 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
- Verifies the assistant message shows the streaming cursor (
- 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
- Verifies the streaming cursor is removed after the final SSE
- 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
Interruptedassistant message with the partial content preserved. - File:
server/src/test/unit/Chat.streamingIncrementalState.test.tsx
- Verifies a mid-stream read error results in an
- 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 theInterruptedbadge and keeps the partial text. - File:
server/src/test/unit/Chat.streamingIncrementalState.test.tsx
- Verifies a stream that ends without a
- 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
- Verifies the completed stream triggers an assistant persistence write (
- 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
Chatcomponent and posts to/api/chat/v1/completions/stream. - File:
server/src/test/unit/QuickAskOverlay.streaming.test.tsx
- Verifies the Quick Ask overlay expands into the EE
- 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
Chatimplementation and posts to/api/chat/v1/completions/stream. - File:
server/src/test/unit/RightSidebar.streaming.test.tsx
- Verifies the EE Right Sidebar renders the streaming
- 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
aiAssistantenabled, the Quick Ask shortcut opens the overlay. - File:
server/src/test/unit/layout/DefaultLayout.aiAssistantReload.test.tsx
- Verifies DefaultLayout reads the experimental feature state on mount; after a simulated "reload" (unmount/mount) with
- 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
aiAssistantdisabled, the Quick Ask overlay is not rendered and the ⌘↑ shortcut is ignored. - File:
server/src/test/unit/layout/DefaultLayout.aiAssistantReload.test.tsx
- Verifies after a simulated "reload" with
- Validation:
npx vitest run server/src/test/unit/layout/DefaultLayout.aiAssistantReload.test.tsx