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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
567 lines
39 KiB
Markdown
567 lines
39 KiB
Markdown
# 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`
|