diff --git a/src/backend/shared/styles/globals.css b/src/backend/shared/styles/globals.css index 6f923c0e3..d89679fb8 100644 --- a/src/backend/shared/styles/globals.css +++ b/src/backend/shared/styles/globals.css @@ -72,6 +72,16 @@ } } +/* Tailwind's animate-spin utility emits `animation: spin 1s linear infinite` + but our tailwind.config.ts overrides `theme.keyframes` (instead of + extending it), wiping out the default `spin` keyframe. Redefine it here + so animate-spin works app-wide. */ +@keyframes spin { + to { + transform: rotate(360deg); + } +} + .ai-chat-typing-dot { display: inline-block; width: 5px; diff --git a/src/frontend/components/_molecules/menu-bar/menus/help.tsx b/src/frontend/components/_molecules/menu-bar/menus/help.tsx index 30477ba96..712d3351e 100644 --- a/src/frontend/components/_molecules/menu-bar/menus/help.tsx +++ b/src/frontend/components/_molecules/menu-bar/menus/help.tsx @@ -14,7 +14,7 @@ export const HelpMenu = () => { const handleOpenCommunitySupport = () => { try { - window.open('https://openplc.discussion.community/', '_blank') + window.open('https://edge.autonomylogic.com/forum', '_blank') } catch (error) { console.error('Error opening link:', error) } diff --git a/src/frontend/store/__tests__/ai-slice.test.ts b/src/frontend/store/__tests__/ai-slice.test.ts index 3216caa56..09356cea1 100644 --- a/src/frontend/store/__tests__/ai-slice.test.ts +++ b/src/frontend/store/__tests__/ai-slice.test.ts @@ -2,7 +2,6 @@ import { createStore, StoreApi } from 'zustand/vanilla' import { createAISlice, createAISliceFactory } from '../slices/ai/slice' import type { AISlice, ChatMessage } from '../slices/ai/types' -import { MAX_CONVERSATION_MESSAGES } from '../slices/ai/types' // --------------------------------------------------------------------------- // Helpers @@ -282,17 +281,16 @@ describe('createAISlice', () => { expect(messages[1].id).toBe('msg-2') }) - it('enforces MAX_CONVERSATION_MESSAGES by keeping the most recent messages', () => { - expect(MAX_CONVERSATION_MESSAGES).toBe(50) - - for (let i = 0; i < MAX_CONVERSATION_MESSAGES + 5; i++) { + it('retains every appended message without truncation', () => { + const total = 75 + for (let i = 0; i < total; i++) { store.getState().aiActions.addMessage(makeMessage({ id: `msg-${i}`, content: `Message ${i}` })) } const messages = store.getState().ai.messages - expect(messages).toHaveLength(MAX_CONVERSATION_MESSAGES) - expect(messages[0].id).toBe('msg-5') - expect(messages[MAX_CONVERSATION_MESSAGES - 1].id).toBe(`msg-${MAX_CONVERSATION_MESSAGES + 4}`) + expect(messages).toHaveLength(total) + expect(messages[0].id).toBe('msg-0') + expect(messages[total - 1].id).toBe(`msg-${total - 1}`) }) }) @@ -613,17 +611,18 @@ describe('createAISlice', () => { expect(store.getState().ai.error).toBeNull() }) - it('caps at MAX_CONVERSATION_MESSAGES, keeping the most recent', () => { + it('replaces with the full payload without truncation', () => { + const total = 75 const many: ChatMessage[] = [] - for (let i = 0; i < MAX_CONVERSATION_MESSAGES + 7; i++) { + for (let i = 0; i < total; i++) { many.push(makeMessage({ id: `m-${i}`, content: `Message ${i}` })) } store.getState().aiActions.replaceMessages(many) const messages = store.getState().ai.messages - expect(messages).toHaveLength(MAX_CONVERSATION_MESSAGES) - expect(messages[0].id).toBe('m-7') - expect(messages[MAX_CONVERSATION_MESSAGES - 1].id).toBe(`m-${MAX_CONVERSATION_MESSAGES + 6}`) + expect(messages).toHaveLength(total) + expect(messages[0].id).toBe('m-0') + expect(messages[total - 1].id).toBe(`m-${total - 1}`) }) }) diff --git a/src/frontend/store/slices/ai/index.ts b/src/frontend/store/slices/ai/index.ts index e3095fd36..43567e4e5 100644 --- a/src/frontend/store/slices/ai/index.ts +++ b/src/frontend/store/slices/ai/index.ts @@ -1,3 +1,2 @@ export { createAISlice, createAISliceFactory } from './slice' export type { AIActions, AIPreferences, AISlice, AIState, ChatMessage, ChatMessageRole, DiffReviewEntry } from './types' -export { MAX_CONVERSATION_MESSAGES } from './types' diff --git a/src/frontend/store/slices/ai/slice.ts b/src/frontend/store/slices/ai/slice.ts index c041659de..237a87776 100644 --- a/src/frontend/store/slices/ai/slice.ts +++ b/src/frontend/store/slices/ai/slice.ts @@ -3,7 +3,6 @@ import { StateCreator } from 'zustand' import type { AIFeatureConfig } from '../../../../middleware/shared/ports/types' import type { AISlice } from './types' -import { MAX_CONVERSATION_MESSAGES } from './types' const DEFAULT_AI_STATE: AISlice['ai'] = { isEnabled: false, @@ -152,9 +151,6 @@ export function createAISliceFactory(config?: AIFeatureConfig): StateCreator { ai.messages.push(message) - if (ai.messages.length > MAX_CONVERSATION_MESSAGES) { - ai.messages = ai.messages.slice(-MAX_CONVERSATION_MESSAGES) - } }), ) }, @@ -292,8 +288,7 @@ export function createAISliceFactory(config?: AIFeatureConfig): StateCreator { setState( produce(({ ai }: AISlice) => { - ai.messages = - messages.length > MAX_CONVERSATION_MESSAGES ? messages.slice(-MAX_CONVERSATION_MESSAGES) : messages + ai.messages = messages ai.error = null }), ) diff --git a/src/frontend/store/slices/ai/types.ts b/src/frontend/store/slices/ai/types.ts index f3f4f7a81..831ad71a3 100644 --- a/src/frontend/store/slices/ai/types.ts +++ b/src/frontend/store/slices/ai/types.ts @@ -193,9 +193,3 @@ export type AIActions = { export type AISlice = AIState & { aiActions: AIActions } - -// --------------------------------------------------------------------------- -// Constants -// --------------------------------------------------------------------------- - -export const MAX_CONVERSATION_MESSAGES = 50 diff --git a/src/frontend/store/slices/index.ts b/src/frontend/store/slices/index.ts index b9b0206ef..81ff85dc0 100644 --- a/src/frontend/store/slices/index.ts +++ b/src/frontend/store/slices/index.ts @@ -1,6 +1,5 @@ export type { AISlice } from './ai' export { createAISlice, createAISliceFactory } from './ai' -export { MAX_CONVERSATION_MESSAGES } from './ai' export type { ConsoleSlice } from './console' export { createConsoleSlice } from './console' export type { DeviceSlice } from './device'