From 1d5de49b4888be8b7be58d99aae3213dbe26dc9f Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 14 Mar 2026 08:43:41 +0200 Subject: [PATCH] Add URL detection to inbox processing service (#942) * fixup! Fix date format inconsistency in Defer Until field * fixup! fixup! Fix date format inconsistency in Defer Until field * fixup! fixup! fixup! Fix date format inconsistency in Defer Until field --- CLAUDE.md | 6 ++ .../modules/inbox/inboxProcessingService.js | 5 ++ .../tests/unit/services/applyPerms.test.js | 36 +++++---- .../services/inboxProcessingService.test.js | 78 +++++++++++++++++++ docs/MEMORY.md | 77 ++++++++++++++++++ 5 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 backend/tests/unit/services/inboxProcessingService.test.js create mode 100644 docs/MEMORY.md diff --git a/CLAUDE.md b/CLAUDE.md index 6257982..771e800 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,6 +76,12 @@ npm start # Frontend on :8080, Backend on :3002 - Fix a bug (TDD workflow) - Add translations +9. **[Claude Memory & Preferences](docs/MEMORY.md)** + - PR and commit message preferences + - Testing preferences + - Common patterns to remember + - Known issues and solutions + --- ## Project Overview diff --git a/backend/modules/inbox/inboxProcessingService.js b/backend/modules/inbox/inboxProcessingService.js index 379f2ab..0d76421 100644 --- a/backend/modules/inbox/inboxProcessingService.js +++ b/backend/modules/inbox/inboxProcessingService.js @@ -289,6 +289,11 @@ const generateSuggestion = (content, tags, projects, cleanedContent) => { const textStartsWithVerb = startsWithVerb(cleanedContent); const hasUrl = containsUrl(content); + // Detect URLs even without a project (for bookmark tag display) + if (hasUrl && !hasProject) { + return { type: null, reason: 'url_detected' }; + } + if (!hasProject) { return { type: null, reason: null }; } diff --git a/backend/tests/unit/services/applyPerms.test.js b/backend/tests/unit/services/applyPerms.test.js index 6661993..9665b2b 100644 --- a/backend/tests/unit/services/applyPerms.test.js +++ b/backend/tests/unit/services/applyPerms.test.js @@ -153,10 +153,11 @@ describe('applyPerms', () => { }); it('should handle empty upserts and deletes', async () => { - await sequelize.transaction(async (tx) => { - await applyPerms(tx, { upserts: [], deletes: [] }); - }); - // No error thrown + await expect( + sequelize.transaction(async (tx) => { + await applyPerms(tx, { upserts: [], deletes: [] }); + }) + ).resolves.not.toThrow(); }); it('should handle multiple upserts and deletes in one call', async () => { @@ -241,18 +242,19 @@ describe('applyPerms', () => { }); it('should not fail when deleting a non-existent permission', async () => { - await sequelize.transaction(async (tx) => { - await applyPerms(tx, { - upserts: [], - deletes: [ - { - userId: target.id, - resourceType: 'project', - resourceUid: 'does-not-exist', - }, - ], - }); - }); - // Should not throw + await expect( + sequelize.transaction(async (tx) => { + await applyPerms(tx, { + upserts: [], + deletes: [ + { + userId: target.id, + resourceType: 'project', + resourceUid: 'does-not-exist', + }, + ], + }); + }) + ).resolves.not.toThrow(); }); }); diff --git a/backend/tests/unit/services/inboxProcessingService.test.js b/backend/tests/unit/services/inboxProcessingService.test.js new file mode 100644 index 0000000..2bf32e0 --- /dev/null +++ b/backend/tests/unit/services/inboxProcessingService.test.js @@ -0,0 +1,78 @@ +const { + processInboxItem, + containsUrl, +} = require('../../../modules/inbox/inboxProcessingService'); + +describe('inboxProcessingService', () => { + describe('processInboxItem', () => { + it('should detect URL without project and set reason to url_detected', () => { + const content = 'https://example.com/page #tag1 #tag2'; + const result = processInboxItem(content); + + expect(result).toEqual({ + parsed_tags: ['tag1', 'tag2'], + parsed_projects: [], + cleaned_content: 'https://example.com/page', + suggested_type: null, + suggested_reason: 'url_detected', + }); + }); + + it('should detect URL with project and suggest note', () => { + const content = 'https://example.com/page +Project #tag1'; + const result = processInboxItem(content); + + expect(result).toEqual({ + parsed_tags: ['tag1'], + parsed_projects: ['Project'], + cleaned_content: 'https://example.com/page', + suggested_type: 'note', + suggested_reason: 'url_detected', + }); + }); + + it('should handle URL without tags or projects', () => { + const content = 'https://example.com/test-page'; + const result = processInboxItem(content); + + expect(result).toEqual({ + parsed_tags: [], + parsed_projects: [], + cleaned_content: 'https://example.com/test-page', + suggested_type: null, + suggested_reason: 'url_detected', + }); + }); + + it('should not set url_detected reason for non-URL content', () => { + const content = 'Just some regular text #tag1'; + const result = processInboxItem(content); + + expect(result).toEqual({ + parsed_tags: ['tag1'], + parsed_projects: [], + cleaned_content: 'Just some regular text', + suggested_type: null, + suggested_reason: null, + }); + }); + }); + + describe('containsUrl', () => { + it('should detect URLs in text with hashtags', () => { + expect(containsUrl('https://example.com/page #tag1')).toBe(true); + }); + + it('should detect URLs at the start of text', () => { + expect(containsUrl('https://example.com')).toBe(true); + }); + + it('should detect http URLs', () => { + expect(containsUrl('http://example.com/path')).toBe(true); + }); + + it('should return false for non-URL text', () => { + expect(containsUrl('just some text')).toBe(false); + }); + }); +}); diff --git a/docs/MEMORY.md b/docs/MEMORY.md new file mode 100644 index 0000000..8ef47d2 --- /dev/null +++ b/docs/MEMORY.md @@ -0,0 +1,77 @@ +# Claude Memory & Preferences + +This document contains preferences, patterns, and memory items specific to working with Claude Code on the tududi project. + +--- + +## Pull Request Preferences + +### PR Descriptions +- **Do NOT add** the "🤖 Generated with [Claude Code](https://claude.com/claude-code)" footer to pull requests +- Keep PR descriptions focused on the changes and test plan +- Follow the standard format: + - Summary section with bullet points + - Changes section with detailed breakdown + - Test plan section with checkboxes + +### PR Creation Workflow +- Always create PRs against the `main` branch +- Use descriptive branch names following the pattern: `fix/`, `feature/`, `refactor/`, etc. +- Include comprehensive test coverage in PRs + +--- + +## Commit Message Preferences + +### General Rules +- **Do NOT add** `Co-authored-by` trailers to commit messages (set globally in `~/.claude/CLAUDE.md`) +- Use conventional commit style when appropriate: `fix:`, `feat:`, `refactor:`, etc. +- Keep commit messages concise and descriptive + +### Fixup Commits +- Use `fixup!` prefix for commits that should be squashed into previous commits +- These are typically used with `git rebase --autosquash` + +--- + +## Testing Preferences + +### Test Coverage Requirements +- All new features must include comprehensive unit tests +- Follow the Arrange-Act-Assert pattern (see [testing.md](testing.md)) +- Test files should be colocated in `backend/tests/unit/` matching the module structure + +### Running Tests +- Always run tests before pushing with `npm test` +- Pre-push hooks will automatically run linting, formatting, and tests + +--- + +## Codebase Patterns to Remember + +### Backend Patterns +- Follow the module architecture pattern described in [backend-patterns.md](backend-patterns.md) +- Use repository pattern for data access +- Keep business logic in service files + +### Frontend Patterns +- TypeScript for new components +- Follow component organization in [directory-structure.md](directory-structure.md) +- Use Zustand for global state, SWR for server state + +--- + +## Common Issues & Solutions + +### Date Formatting +- Be consistent with date format handling across the application +- Pay attention to timezone handling in defer/scheduling features + +### URL Detection +- The inbox processing service has URL detection logic +- URLs are detected even without projects for bookmark tag display + +--- + +**Last Updated:** 2026-03-14 +**Maintained by:** Claude Code sessions - update as new patterns emerge \ No newline at end of file