-
Notifications
You must be signed in to change notification settings - Fork 0
feat: consolidate Note and Link editing into NoteModal #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: consolidate Note and Link editing into NoteModal #39
Conversation
Merge ProjectInfoModal into NoteModal for unified note and links editing. Users now access both via the Note button instead of separate entry points. Changes: - Extend Redux draft slice with links field for persistence - Extend useNoteModal to fetch/save both notes and links - Add Links section to NoteModal (EditableUrlItem, LinkTypeCombobox) - Remove "Edit Project Info..." from OverflowMenu - Remove onEdit/onEditProjectInfo props from component chain - Delete deprecated ProjectInfoModal and useProjectInfoModal - Update unit tests, stories, and E2E tests Closes #37
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughConsolidates project metadata editing: removes ProjectInfoModal and its hook, enhances NoteModal to handle notes + links, removes onEdit/onEditProjectInfo props and menu item, updates hooks, stories, tests, and draft state to persist links alongside notes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as "User"
participant RepoCard as "RepoCard (UI)"
participant Hook as "useNoteModal"
participant NoteModal as "NoteModal (UI)"
participant Redux as "Redux (draftSlice)"
participant API as "Server API (get/upsertProjectInfo)"
rect rgba(100,149,237,0.5)
User->>RepoCard: Click Note button / press Enter
end
rect rgba(60,179,113,0.5)
RepoCard->>Hook: open(cardId)
Hook->>API: getProjectInfo(cardId)
API-->>Hook: projectInfo { note, links }
Hook->>NoteModal: open(initialNote, initialLinks)
NoteModal-->>User: render editor (note + links)
end
rect rgba(255,215,0,0.5)
User->>NoteModal: Edit note & links → Save
NoteModal->>Hook: save(note, links)
Hook->>Redux: updateDraftNote(cardId, note, links)
Hook->>API: upsertProjectInfo(cardId, { note, links })
API-->>Hook: success
Hook->>NoteModal: close()
NoteModal-->>User: show toast / close
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #39 +/- ##
==========================================
- Coverage 76.92% 75.31% -1.61%
==========================================
Files 121 119 -2
Lines 3904 3849 -55
Branches 981 975 -6
==========================================
- Hits 3003 2899 -104
- Misses 886 934 +48
- Partials 15 16 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
e2e/logged-in/maintenance-project-info.spec.ts (1)
94-94: Test name still referencesProjectInfoModal.Line 94's test is named
'should close ProjectInfoModal on Cancel'but the modal is now NoteModal. Update for consistency with the consolidation.♻️ Suggested fix
- test('should close ProjectInfoModal on Cancel', async ({ page }) => { + test('should close NoteModal on Cancel', async ({ page }) => {
🤖 Fix all issues with AI agents
In `@app/maintenance/MaintenanceClient.tsx`:
- Around line 312-320: openNoteModal currently races with async
getMaintenanceProjectInfo and can overwrite currentNote/currentLinks when the
user switches repos or closes the modal; fix by resetting state on open/close
and guarding the async result: when opening (openNoteModal / setNoteModalOpen)
immediately clear/set currentNote/currentLinks to empty, capture the repo id (or
create an abort token) before calling
startLoadingProjectInfo(getMaintenanceProjectInfo(repo.id)), and when the
promise resolves only call setCurrentNote/setCurrentLinks if the selected repo
id still matches that captured id and the modal is still open; apply the same
guard/reset pattern to the other similar block around lines 737-746.
In `@components/Modals/NoteModal.tsx`:
- Around line 95-133: The effect that resets note/link UI state (useEffect that
calls setNote, setLinks, setEditingUrlIndex, setDeletedLink) is currently
dependent on draft?.content and draft?.links and thus clears editingUrlIndex and
deletedLink on every keystroke; change the dependency list so the reset runs
only when the modal opens or the card changes (e.g., depend on isOpen and cardId
or initialNote/initialLinks) instead of draft?.content/draft?.links, ensuring
editingUrlIndex and deletedLink are only initialized when the modal opens (or
cardId changes) and not on each draft update.
🧹 Nitpick comments (5)
tests/unit/redux/draftSlice.test.ts (2)
47-63: Consider adding a test for backward compatibility.The reducer preserves existing links when
linksis not provided in the payload (line 70 of draftSlice.ts), but there's no test verifying this behavior. This is important since the payload haslinks?: ProjectLink[](optional).📝 Suggested test case
it('should preserve existing links when updating content without links', () => { const stateWithDraft = { ...initialState, notes: { 'card-123': { cardId: 'card-123', content: 'Old content', links: [{ url: 'https://example.com', type: 'docs' }], lastModified: mockNow - 1000, }, }, } // Update content without providing links const action = updateDraftNote({ cardId: 'card-123', content: 'Updated content', // links intentionally omitted }) const nextState = draftSlice(stateWithDraft, action) expect(nextState.notes['card-123']!.content).toBe('Updated content') // Links should be preserved expect(nextState.notes['card-123']!.links).toEqual([ { url: 'https://example.com', type: 'docs' }, ]) })
198-227: Consider adding a test forselectDraftLinksselector.The new
selectDraftLinksselector is exported but not tested. Add coverage to ensure it returns the links array or empty array fallback.📝 Suggested test cases
import { // ... existing imports selectDraftLinks, } from '@/lib/redux/slices/draftSlice' describe('selectDraftLinks', () => { it('should return links for specific card', () => { const links = [{ url: 'https://example.com', type: 'docs' }] const rootState = { draft: { notes: { 'card-123': { cardId: 'card-123', content: 'Test', links, lastModified: mockNow, }, }, }, } const selector = selectDraftLinks('card-123') expect(selector(rootState)).toEqual(links) }) it('should return empty array for non-existent card', () => { const rootState = { draft: initialState } const selector = selectDraftLinks('non-existent') expect(selector(rootState)).toEqual([]) }) })e2e/logged-in/project-info-links.spec.ts (2)
27-44: Consider extracting duplicateopenNoteModalhelper.The
openNoteModalhelper is defined identically in both test describe blocks (lines 27-44 and 460-476). Extract it to the file scope to reduce duplication.♻️ Suggested refactor
+/** + * Helper to open NoteModal from a repo card's Note button + */ +async function openNoteModal( + page: import('@playwright/test').Page, +): Promise<import('@playwright/test').Locator> { + const card = page.locator('[data-testid^="repo-card-"]').first() + await expect(card).toBeVisible({ timeout: 10000 }) + + const noteButton = card.getByRole('button', { name: 'Open note' }) + await expect(noteButton).toBeVisible() + await noteButton.click() + + const dialog = page.getByRole('dialog') + await expect(dialog).toBeVisible({ timeout: 5000 }) + + return dialog +} + test.describe('ProjectInfo Links (Authenticated)', () => { test.use({ storageState: 'e2e/.auth/user.json' }) const BOARD_URL = '/board/board-1' - /** - * Helper to open NoteModal from a repo card's Note button - * (Previously opened ProjectInfoModal via overflow menu, now unified into NoteModal) - */ - async function openNoteModal( - page: import('@playwright/test').Page, - ): Promise<import('@playwright/test').Locator> { - // ... implementation - }Also applies to: 460-476
98-98: FixedwaitForTimeoutcalls may introduce flakiness.Multiple
waitForTimeout(200-400)calls are used for waiting on UI updates. While acceptable for E2E tests, these can cause flakiness in slower CI environments. Consider usingwaitForSelectororexpect(...).toBeVisible()with appropriate timeouts instead where feasible.Also applies to: 129-129, 184-184, 192-192, 216-216, 222-222, 300-300, 362-362, 369-369, 390-390, 531-531, 559-559, 564-564
hooks/board/useNoteModal.ts (1)
121-132: Consider adding error handling in thesavefunction.The
savefunction callsupsertProjectInfobut doesn't handle potential errors. If the save fails, the caller (NoteModal) won't know unless it catches the propagated exception.Looking at the NoteModal implementation from the relevant_code_snippets, it appears NoteModal handles errors with try/catch, so this is likely fine. However, you may want to add Sentry logging here for consistency with
open().♻️ Optional: Add error logging
const save = useCallback( async (note: string, links: ProjectLink[]) => { if (!cardId) return + try { await upsertProjectInfo(cardId, { note, comment: '', links, }) + } catch (error) { + Sentry.captureException(error, { tags: { action: 'saveProjectInfo' } }) + throw error // Re-throw so caller can handle + } }, [cardId], )
Address CodeRabbit review comments for PR #39: 1. MaintenanceClient.tsx: Add activeNoteRepoId ref to guard async getMaintenanceProjectInfo calls. If user switches repos before the promise resolves, the stale data is discarded. 2. NoteModal.tsx: Add didInitRef to prevent useEffect from resetting editingUrlIndex and deletedLink on every draft update. Now only initializes when modal opens, not on each keystroke.
The CI workflow expected coverage reports at `coverage-e2e-shard-N/`
but Playwright outputs to `coverage-e2e/`. Fixed by:
1. Changed upload path from `coverage-e2e-shard-${{ matrix.shardIndex }}/`
to `coverage-e2e/` to match actual Playwright output
2. Added defensive check for missing `all-coverage-reports` directory
in merge step to prevent ENOENT errors
🧪 E2E Coverage Report (Sharded: 12 parallel jobs)
📊 Full report available in workflow artifacts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @.github/workflows/e2e.yml:
- Around line 122-127: The workflow currently only checks for the existence of
the all-coverage-reports directory, but actions/download-artifact may create an
empty directory; update the guard so the job treats an empty directory the same
as a missing one by also checking for the presence of any coverage summary files
(e.g., test shard summaries) inside all-coverage-reports and writing the zeroed
JSON to coverage-e2e/coverage/coverage-summary.json when none are found; apply
the same change for the later identical block (lines referenced in the comment)
so both places use a combined "directory exists AND contains files" condition
rather than just -d, and remove the redundant Node.js guard mentioned in the
comment.
| # Check if coverage reports were downloaded | ||
| if [ ! -d "all-coverage-reports" ]; then | ||
| echo "No coverage reports downloaded - coverage collection may have failed in shards" | ||
| echo '{"total":{"lines":{"total":0,"covered":0,"pct":0},"statements":{"total":0,"covered":0,"pct":0},"functions":{"total":0,"covered":0,"pct":0},"branches":{"total":0,"covered":0,"pct":0}}}' > coverage-e2e/coverage/coverage-summary.json | ||
| exit 0 | ||
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle empty all-coverage-reports to keep summary deterministic.
actions/download-artifact can create the directory even when no artifacts match. In that case the current check won’t fire, and no zeroed summary is produced, so later steps won’t post coverage. Consider treating “no summaries found” the same as missing dir, and drop the redundant Node guard.
🔧 Suggested guard update
- if [ ! -d "all-coverage-reports" ]; then
+ if [ ! -d "all-coverage-reports" ] || ! find all-coverage-reports -type f -name 'coverage-summary.json' -print -quit | grep -q .; then
echo "No coverage reports downloaded - coverage collection may have failed in shards"
echo '{"total":{"lines":{"total":0,"covered":0,"pct":0},"statements":{"total":0,"covered":0,"pct":0},"functions":{"total":0,"covered":0,"pct":0},"branches":{"total":0,"covered":0,"pct":0}}}' > coverage-e2e/coverage/coverage-summary.json
exit 0
fi- if (!fs.existsSync('all-coverage-reports')) {
- console.log('No coverage reports directory found');
- process.exit(0);
- }
+ // (Optional) remove this guard since the bash check already handles missing/empty coverage.Also applies to: 148-151
🤖 Prompt for AI Agents
In @.github/workflows/e2e.yml around lines 122 - 127, The workflow currently
only checks for the existence of the all-coverage-reports directory, but
actions/download-artifact may create an empty directory; update the guard so the
job treats an empty directory the same as a missing one by also checking for the
presence of any coverage summary files (e.g., test shard summaries) inside
all-coverage-reports and writing the zeroed JSON to
coverage-e2e/coverage/coverage-summary.json when none are found; apply the same
change for the later identical block (lines referenced in the comment) so both
places use a combined "directory exists AND contains files" condition rather
than just -d, and remove the redundant Node.js guard mentioned in the comment.
Summary
ProjectInfoModalintoNoteModalfor unified note and links editingChanges
Redux & State
linksfield for persistence across browser sessionsHooks
useNoteModalto fetch/save both notes and linksuseProjectInfoModal(consolidated)Components
NoteModal(EditableUrlItem, LinkTypeCombobox, CreateLinkTypeDialog)onEdit/onEditProjectInfoprops from component chainProjectInfoModalcomponentTests
Test plan
pnpm typecheckpassespnpm lintpassespnpm test- 1265 tests passpnpm buildsucceedspnpm e2e- 293 tests passFiles Changed
Closes #37
Summary by CodeRabbit
New Features
UX Improvements
Tests / Stories
✏️ Tip: You can customize this high-level summary in your review settings.