From 00eda4b9368b3f759de7d78cccab08a9cc950f3e Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 2 Nov 2025 14:14:55 +0200 Subject: [PATCH] Fix/descriptive ai feat (#464) * fix AI feature not working * fixup! fix AI feature not working --- backend/tests/integration/users.test.js | 116 ++++++++ .../components/Profile/ProfileSettings.tsx | 2 +- frontend/components/Task/NewTask.tsx | 6 +- .../__tests__/TaskTitleSection.test.tsx | 258 ++++++++++++++++++ frontend/components/Task/TaskModal.tsx | 25 +- public/locales/en/translation.json | 2 +- 6 files changed, 401 insertions(+), 8 deletions(-) create mode 100644 frontend/components/Task/TaskForm/__tests__/TaskTitleSection.test.tsx diff --git a/backend/tests/integration/users.test.js b/backend/tests/integration/users.test.js index 78da05e..d2f840c 100644 --- a/backend/tests/integration/users.test.js +++ b/backend/tests/integration/users.test.js @@ -115,6 +115,122 @@ describe('Users Routes', () => { }); }); + describe('Task Intelligence Settings', () => { + describe('GET /api/profile - task_intelligence_enabled field', () => { + it('should return task_intelligence_enabled in profile', async () => { + const response = await agent.get('/api/profile'); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty( + 'task_intelligence_enabled' + ); + expect(typeof response.body.task_intelligence_enabled).toBe( + 'boolean' + ); + }); + + it('should default to true for new users', async () => { + const response = await agent.get('/api/profile'); + + expect(response.status).toBe(200); + // New users should have task intelligence enabled by default + expect(response.body.task_intelligence_enabled).toBe(true); + }); + }); + + describe('PATCH /api/profile - disable task intelligence', () => { + it('should disable task intelligence (AI popups)', async () => { + const response = await agent + .patch('/api/profile') + .send({ task_intelligence_enabled: false }); + + expect(response.status).toBe(200); + expect(response.body.task_intelligence_enabled).toBe(false); + + // Verify it persisted + const getResponse = await agent.get('/api/profile'); + expect(getResponse.body.task_intelligence_enabled).toBe(false); + }); + + it('should enable task intelligence (AI popups)', async () => { + // First disable it + await user.update({ task_intelligence_enabled: false }); + + // Then enable it via API + const response = await agent + .patch('/api/profile') + .send({ task_intelligence_enabled: true }); + + expect(response.status).toBe(200); + expect(response.body.task_intelligence_enabled).toBe(true); + + // Verify it persisted + const getResponse = await agent.get('/api/profile'); + expect(getResponse.body.task_intelligence_enabled).toBe(true); + }); + + it('should allow updating task intelligence with other fields', async () => { + const response = await agent.patch('/api/profile').send({ + task_intelligence_enabled: false, + appearance: 'dark', + language: 'es', + }); + + expect(response.status).toBe(200); + expect(response.body.task_intelligence_enabled).toBe(false); + expect(response.body.appearance).toBe('dark'); + expect(response.body.language).toBe('es'); + }); + + it('should handle boolean conversion from string', async () => { + // Test that "false" string gets converted to boolean false + const response = await agent + .patch('/api/profile') + .send({ task_intelligence_enabled: 'false' }); + + expect(response.status).toBe(200); + // Should handle string to boolean conversion + expect([false, 'false']).toContain( + response.body.task_intelligence_enabled + ); + }); + }); + + describe('Task Intelligence Feature Verification', () => { + it('should respect disabled setting - no AI suggestions shown', async () => { + // Disable task intelligence + await agent + .patch('/api/profile') + .send({ task_intelligence_enabled: false }); + + // Verify the setting is stored correctly + const response = await agent.get('/api/profile'); + expect(response.body.task_intelligence_enabled).toBe(false); + + // This verifies that the backend correctly stores the setting + // Frontend tests verify that popups are actually hidden + }); + + it('should persist across sessions', async () => { + // Disable task intelligence + await agent + .patch('/api/profile') + .send({ task_intelligence_enabled: false }); + + // Simulate new session by creating new agent + const newAgent = request.agent(app); + await newAgent.post('/api/login').send({ + email: user.email, + password: 'password123', + }); + + // Verify setting persisted + const response = await newAgent.get('/api/profile'); + expect(response.body.task_intelligence_enabled).toBe(false); + }); + }); + }); + describe('POST /api/profile/task-summary/toggle', () => { beforeEach(async () => { await user.update({ task_summary_enabled: false }); diff --git a/frontend/components/Profile/ProfileSettings.tsx b/frontend/components/Profile/ProfileSettings.tsx index bb47c74..2324139 100644 --- a/frontend/components/Profile/ProfileSettings.tsx +++ b/frontend/components/Profile/ProfileSettings.tsx @@ -1710,7 +1710,7 @@ const ProfileSettings: React.FC = ({

{t( 'profile.taskIntelligenceDescription', - 'Get helpful suggestions to make your task names more descriptive and actionable.' + 'Show popup alerts while typing task names that suggest improvements like "Make it more descriptive!", "Be more specific!", or "Add an action verb!". Disable this if you prefer typing in your own shorthand without suggestions.' )}

diff --git a/frontend/components/Task/NewTask.tsx b/frontend/components/Task/NewTask.tsx index 602c18f..5a07f0a 100644 --- a/frontend/components/Task/NewTask.tsx +++ b/frontend/components/Task/NewTask.tsx @@ -12,7 +12,7 @@ const NewTask: React.FC = ({ onTaskCreate }) => { const [taskName, setTaskName] = useState(''); const [showNameLengthHelper, setShowNameLengthHelper] = useState(false); const [taskIntelligenceEnabled, setTaskIntelligenceEnabled] = - useState(true); + useState(false); const { showErrorToast } = useToast(); const { t } = useTranslation(); @@ -35,10 +35,10 @@ const NewTask: React.FC = ({ onTaskCreate }) => { JSON.stringify(enabled) ); } catch { - setTaskIntelligenceEnabled(true); // Default to enabled + setTaskIntelligenceEnabled(false); // Default to disabled on error localStorage.setItem( 'taskIntelligenceEnabled', - JSON.stringify(true) + JSON.stringify(false) ); } }; diff --git a/frontend/components/Task/TaskForm/__tests__/TaskTitleSection.test.tsx b/frontend/components/Task/TaskForm/__tests__/TaskTitleSection.test.tsx new file mode 100644 index 0000000..999fb4a --- /dev/null +++ b/frontend/components/Task/TaskForm/__tests__/TaskTitleSection.test.tsx @@ -0,0 +1,258 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import TaskTitleSection from '../TaskTitleSection'; + +// Mock react-i18next +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string, defaultValue: string) => defaultValue, + }), +})); + +describe('TaskTitleSection - Task Intelligence Settings', () => { + const mockOnChange = jest.fn(); + const mockOnSubmit = jest.fn(); + + const defaultProps = { + taskId: 1, + value: '', + onChange: mockOnChange, + taskAnalysis: null, + taskIntelligenceEnabled: false, + onSubmit: mockOnSubmit, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('When task intelligence is DISABLED', () => { + it('should NOT show AI suggestions even when task is vague', () => { + const vagueTaskAnalysis = { + isVague: true, + severity: 'high' as const, + reason: 'short', + suggestion: 'Try adding more details', + }; + + render( + + ); + + // AI suggestion messages should NOT be in the document + expect( + screen.queryByText('Make it more descriptive!') + ).not.toBeInTheDocument(); + expect( + screen.queryByText('Be more specific!') + ).not.toBeInTheDocument(); + expect( + screen.queryByText('Add an action verb!') + ).not.toBeInTheDocument(); + }); + + it('should NOT show AI suggestions for short task names', () => { + const shortTaskAnalysis = { + isVague: true, + severity: 'medium' as const, + reason: 'short', + suggestion: + 'Try adding more details like "Call dentist to schedule cleaning appointment" instead of just "Call dentist"', + }; + + render( + + ); + + expect( + screen.queryByText('Make it more descriptive!') + ).not.toBeInTheDocument(); + expect( + screen.queryByText(/Try adding more details/) + ).not.toBeInTheDocument(); + }); + + it('should NOT show AI suggestions for tasks without action verbs', () => { + const noVerbAnalysis = { + isVague: true, + severity: 'low' as const, + reason: 'no_verb', + suggestion: + 'What specific action do you need to take? Try starting with a verb.', + }; + + render( + + ); + + expect( + screen.queryByText('Add an action verb!') + ).not.toBeInTheDocument(); + expect( + screen.queryByText(/What specific action/) + ).not.toBeInTheDocument(); + }); + + it('should NOT show AI suggestions for vague patterns', () => { + const vaguePatternAnalysis = { + isVague: true, + severity: 'medium' as const, + reason: 'vague_pattern', + suggestion: 'Try to be more specific', + }; + + render( + + ); + + expect( + screen.queryByText('Be more specific!') + ).not.toBeInTheDocument(); + }); + }); + + describe('When task intelligence is ENABLED', () => { + it('should show "Make it more descriptive!" for short task names', () => { + const shortTaskAnalysis = { + isVague: true, + severity: 'medium' as const, + reason: 'short', + suggestion: + 'Try adding more details like "Call dentist to schedule cleaning appointment" instead of just "Call dentist"', + }; + + render( + + ); + + expect( + screen.getByText('Make it more descriptive!') + ).toBeInTheDocument(); + }); + + it('should show "Add an action verb!" for tasks without verbs', () => { + const noVerbAnalysis = { + isVague: true, + severity: 'low' as const, + reason: 'no_verb', + suggestion: + 'What specific action do you need to take? Try starting with a verb.', + }; + + render( + + ); + + expect(screen.getByText('Add an action verb!')).toBeInTheDocument(); + }); + + it('should show "Be more specific!" for vague patterns', () => { + const vaguePatternAnalysis = { + isVague: true, + severity: 'medium' as const, + reason: 'vague_pattern', + suggestion: 'Try to be more specific', + }; + + render( + + ); + + expect(screen.getByText('Be more specific!')).toBeInTheDocument(); + }); + + it('should NOT show suggestions when task is not vague', () => { + const goodTaskAnalysis = { + isVague: false, + severity: 'low' as const, + reason: '', + }; + + render( + + ); + + expect( + screen.queryByText('Make it more descriptive!') + ).not.toBeInTheDocument(); + expect( + screen.queryByText('Be more specific!') + ).not.toBeInTheDocument(); + expect( + screen.queryByText('Add an action verb!') + ).not.toBeInTheDocument(); + }); + }); + + describe('Task input rendering', () => { + it('should render task name input with correct value', () => { + const { container } = render( + + ); + + const input = container.querySelector('input[name="name"]'); + expect(input).toBeInTheDocument(); + expect(input).toHaveValue('My task name'); + }); + + it('should render with placeholder text', () => { + const { container } = render( + + ); + + const input = container.querySelector('input[name="name"]'); + expect(input).toBeInTheDocument(); + expect(input).toHaveAttribute('placeholder', 'Add Task Name'); + }); + + it('should have required attribute', () => { + const { container } = render( + + ); + + const input = container.querySelector('input[name="name"]'); + expect(input).toHaveAttribute('required'); + }); + }); +}); diff --git a/frontend/components/Task/TaskModal.tsx b/frontend/components/Task/TaskModal.tsx index d289560..79cb460 100644 --- a/frontend/components/Task/TaskModal.tsx +++ b/frontend/components/Task/TaskModal.tsx @@ -11,6 +11,7 @@ import { TaskAnalysis, } from '../../utils/taskIntelligenceService'; import { useTranslation } from 'react-i18next'; +import { getTaskIntelligenceEnabled } from '../../utils/profileService'; import { TagIcon, FolderIcon, @@ -78,7 +79,8 @@ const TaskModal: React.FC = ({ const [parentTask, setParentTask] = useState(null); const [parentTaskLoading, setParentTaskLoading] = useState(false); const [taskAnalysis, setTaskAnalysis] = useState(null); - const [taskIntelligenceEnabled] = useState(true); + const [taskIntelligenceEnabled, setTaskIntelligenceEnabled] = + useState(false); const [subtasks, setSubtasks] = useState([]); // Collapsible section states - subtasks is derived from autoFocusSubtasks @@ -205,8 +207,25 @@ const TaskModal: React.FC = ({ fetchParentTask(); }, [task.recurring_parent_id, isOpen]); - // Don't fetch task intelligence setting - use default enabled state - // This prevents unnecessary API calls when opening the modal + // Fetch task intelligence setting from user profile + useEffect(() => { + const fetchIntelligenceSetting = async () => { + try { + const enabled = await getTaskIntelligenceEnabled(); + setTaskIntelligenceEnabled(enabled); + } catch (error) { + console.error( + 'Error fetching task intelligence setting:', + error + ); + setTaskIntelligenceEnabled(false); // Default to disabled on error + } + }; + + if (isOpen) { + fetchIntelligenceSetting(); + } + }, [isOpen]); // Auto-scroll to subtasks section when modal opens with autoFocusSubtasks useEffect(() => { diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b0da9c9..773da91 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -256,7 +256,7 @@ }, "frequencyHelp": "Choose how often you want to receive task summaries", "taskIntelligence": "Task Intelligence", - "taskIntelligenceDescription": "Get helpful suggestions to make your task names more descriptive and actionable.", + "taskIntelligenceDescription": "Show popup alerts while typing task names that suggest improvements like \"Make it more descriptive!\", \"Be more specific!\", or \"Add an action verb!\". Disable this if you prefer typing in your own shorthand without suggestions.", "enableTaskIntelligence": "Enable Task Intelligence Assistant", "autoSuggestNextActions": "Auto-Suggest Next Actions", "autoSuggestNextActionsDescription": "When creating a project, automatically prompt for the very next physical action to take.",