feat(notes): blank note editor + edit flow#1427
Conversation
Wires the "New blank note" dropdown option to a full-page editor view that creates Project Status Updates via the BE `create_project_status_update` endpoint, and re-instates the per-card ellipsis dropdown with an "Edit" option that opens the same editor in update mode (`update_project_status_update`). The editor hides the "About this project" sidebar and the project tabs strip for a distraction-free authoring experience, leaving only the breadcrumb + sub-header (avatar + name + "Save note") and a TextEditor workspace with placeholder text. Also fixes a pre-existing tab-order mismatch in `tabs/index.tsx` where TAB_KEYS had "notes" at index 3 but TABS had Risks at index 3 — `?tab=notes` rendered Risks content and clicks on the Notes tab generated `?tab=risks` URLs. Reordering TABS to match TAB_KEYS unbreaks the redirect target this PR depends on and the view-selector dropdown on the Risks tab. Fixes #1097 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a dedicated full-page note editor under Project Details to support creating a new “blank note” and editing existing notes (Project Status Updates), including routing and a per-note card “Edit” action. Also fixes a tab ordering mismatch so ?tab=notes correctly renders the Notes tab content (needed for post-save redirect).
Changes:
- Introduces
/projects/:projectId/notes/newand/projects/:projectId/notes/:noteId/editroutes backed by a newnote-editor/page and supporting hooks. - Adds an ellipsis dropdown on note cards with an “Edit” action that navigates to the edit route.
- Reorders the Project Details
TABSarray to align withTAB_KEYSso Notes/Risks tabs map correctly.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/packages/app/src/route.tsx | Registers new note editor routes and lazy-loads the editor page. |
| frontend/packages/app/src/pages/project-details/tabs/notes/noteCard.tsx | Turns the note card ellipsis into an actions dropdown with “Edit”. |
| frontend/packages/app/src/pages/project-details/tabs/index.tsx | Fixes Notes/Risks tab content ordering to match query-string tab keys. |
| frontend/packages/app/src/pages/project-details/note-editor/index.tsx | New full-page editor shell with edit-mode gating + save/redirect behavior. |
| frontend/packages/app/src/pages/project-details/note-editor/workspace.tsx | New title textarea + TextEditor workspace UI for the note body. |
| frontend/packages/app/src/pages/project-details/note-editor/subHeader.tsx | New sub-header showing avatar/name and Save button. |
| frontend/packages/app/src/pages/project-details/note-editor/useNoteMutation.ts | New unified create/update “save” hook for notes. |
| frontend/packages/app/src/pages/project-details/note-editor/useNoteDetail.ts | New note detail fetch hook for edit mode. |
| frontend/packages/app/src/pages/project-details/note-editor/constants.ts | New path builders for editor routes + notes grid redirect URL. |
| frontend/packages/app/src/pages/project-details/note-editor/types.ts | New shared types for editor mode and draft shape. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- workspace.tsx: pass fixedMenu={false} explicitly to TextEditor so the
distraction-free editor never grows a toolbar regardless of upstream
default changes.
- useNoteMutation.ts: drop unused NoteEditorMode import flagged by lint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
The sidebar is not visible for this page. Update about/index to look at current location for rendering
There was a problem hiding this comment.
Addressed in e88cf7e — editor%2Findex.tsx (sidebar self-hides via useMatch in about/index.tsx).
| const activeView = | ||
| noteId && noteMode === "edit" | ||
| ? "edit" | ||
| : noteMode === "new" | ||
| ? "new" | ||
| : noteId | ||
| ? "details" | ||
| : "list"; | ||
|
|
||
| switch (activeView) { | ||
| case "new": | ||
| case "edit": | ||
| return <NoteEditor noteId={noteId} mode={activeView} />; | ||
|
|
||
| case "details": | ||
| return <UnderConstruction />; | ||
|
|
||
| default: | ||
| return <NotesGrid />; | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Make different routes for this. Use dynamic segment to hold noteId
There was a problem hiding this comment.
Addressed in e88cf7e — tabs/notes/index.tsx (activeView switch removed; now uses nested dynamic routes).
| onClick: () => | ||
| setSearchParams((prev) => { | ||
| prev.set(NOTE_ID, note.name); | ||
| prev.set(NOTE_MODE, "edit"); | ||
| return prev; |
There was a problem hiding this comment.
This will be a simple navigation to /projects/<project-id>/notes/<notes-id>/edit
There was a problem hiding this comment.
Addressed in e88cf7e — noteCard.tsx (Dropdown stays; Edit onClick uses useNavigate to /projects//notes//edit).
|
|
||
| export const NOTE_MODE = "mode"; | ||
| export const NOTE_ID = "note"; |
There was a problem hiding this comment.
I don't think these will be needed if active view var is not used
There was a problem hiding this comment.
Addressed in e88cf7e — constants.ts (NOTE_ID + NOTE_MODE removed; CREATE_OPTIONS kept).
Per maintainer review on PR #1427: replace the search-param view-switch in tabs/notes with real `/projects/:projectId/notes/{new,:noteId/edit}` routes nested under the project detail. - route.tsx: nest two child routes under :projectId, lazy-load NoteEditor - project-details/index.tsx: lift NotesProvider; detect editor routes via useMatch and swap the Tabs strip for <Outlet /> - about/index.tsx: split out content; return null on editor routes so the sidebar self-hides - tabs/notes/index.tsx: drop the activeView switch; Notes just renders the grid (NotesProvider lives one level up) - tabs/notes/noteCard.tsx: Dropdown Edit option navigates to the edit URL - tabs/notes/subHeader.tsx: New blank note navigates to .../notes/new - tabs/notes/editor/index.tsx: read noteId/projectId from useParams, infer mode from noteId, useNavigate to return to ?tab=notes after save - tabs/notes/constants.ts: drop NOTE_ID/NOTE_MODE search-param keys Browser-verified on PROJ-0205: - /?tab=notes renders tabs + grid + sidebar - /notes/new renders the editor full-width, no tabs/sidebar - /notes/<id>/edit pre-populates title + body, no tabs/sidebar Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| <Route index element={<Overview />} /> | ||
| <Route path="overview" element={<Overview />} /> | ||
| <Route path="calendar" element={<CalendarTab />} /> | ||
| <Route path="tracking" element={<Tracking />} /> | ||
| <Route path="risks" element={<RisksTab />} /> | ||
| <Route path="notes" element={<Notes />} /> | ||
| <Route path="notes/new" element={<NoteEditor />} /> | ||
| <Route path="notes/:noteId/edit" element={<NoteEditor />} /> | ||
| <Route path="email" element={<UnderConstruction />} /> | ||
| <Route path="to-do" element={<UnderConstruction />} /> | ||
| <Route path="feedback" element={<UnderConstruction />} /> | ||
| </Route> |
There was a problem hiding this comment.
Due to the requirement for dedicated note editor routes (/notes/new and /notes/:noteId/edit), I had to move away from the existing parameter-based tab navigation.
My initial approach kept note creation and editing within the tab state itself, which avoided most of these issues. However, with dedicated routes required, the Notes section started behaving differently from the other tabs.
To keep the tab bar visible, I switched the tabs to use routing. The problem is that this causes a full page reload when changing tabs. To support lazy loading at the tab level, I had to replace the default frappe-ui-react Tabs implementation with TabList from frappe-ui-react and the base UI Tabs component, as the default component does not provide the required flexibility.
I'm not sure if this is the direction we want to take. The dedicated note routes add a fair amount of complexity compared to keeping note creation and editing within the existing tab navigation.
cc: @ayushnirwal
Description
Implements the Blank Note Editor view from issue #1097. Wires the "New blank note" option in the Notes tab's Create dropdown to a dedicated full-page editor (
/projects/:projectId/notes/new) that creates a Project Status Update, and re-instates the per-card ellipsis with an "Edit" option (/projects/:projectId/notes/:noteId/edit) that loads the same editor in update mode. Save returns the user to the Notes grid (?tab=notes), where SWR revalidates on mount and shows the new / updated card.Relevant Technical Choices
Section S1 — Foundation + routes. New folder
pages/project-details/note-editor/withtypes.ts(NoteEditorMode,NoteDraft),constants.ts(buildEditPath,buildNotesGridPath,buildNewNotePath), anduseNoteMutation.tsthat wraps bothuseFrappePostCall(create_project_status_update)anduseFrappePostCall(update_project_status_update)behind a singlesave(draft).route.tsxswaps<UnderConstruction />for<NoteEditor mode=\"new\" />on/notes/newand adds the/notes/:noteId/editroute withmode=\"edit\". Both routes nest underLayoutWithSidebarso the global sidebar stays.Section S2 — Edit-mode fetch.
useNoteDetail.tscallsget_project_status_updatewith a stable SWR key whennoteIdis present; passesnullkey to skip innewmode.Section S3 — Editor shell (
index.tsx).<ProjectDetailProvider>wraps the editor so the breadcrumb resolves the project name. Layout is<ProjectDetailHeader />+ a centeredmax-w-[800px]column containing the sub-header and workspace. The<AboutThisProject>sidebar lives inpages/project-details/index.tsx, so the standalone editor route never mounts it. The project tabs strip is also not rendered, per locked decision 1 (distraction-free, breadcrumb conveys context).Section S3b — Defer Workspace mount until edit data is loaded. The first browser-check found that the TextEditor body did not pre-populate in edit mode (tiptap only reads
contenton mount; later prop changes are ignored). The shell now gates the body render throughNoteEditorGate— in edit mode it shows<Spinner />untiluseNoteDetailresolves, then mountsNoteEditorBodywithinitialTitle/initialDescriptionderived from the fetched note. The inner component uses lazyuseStateinitializers so the TextEditor captures the description on its first paint.Section S4 — Sub-header. Avatar (xs, image from
useUser((s) => s.state.image), label fromuserName) + full-name span. Right:<Button variant=\"solid\" theme=\"gray\" size=\"sm\" label=\"Save note\">—loading={isSubmitting},disabled={!canSave || isSubmitting}wherecanSave = title.trim().length > 0.useToasts()surfaces\"Note saved\"on success orparseFrappeErrorMsg(error)on failure.Section S5 — Workspace. Title is a single-row
<textarea>styled as a 24px / 600 / ink-gray-8 heading with an ink-gray-4 placeholder; auto-grows on input, Enter blurs to the body. Body is<TextEditor>withplaceholder=\"Type / to format and insert\"andfixedMenu={false}per the Figma's distraction-free spec — no toolbar.Section S6 — Note card edit dropdown.
tabs/notes/noteCard.tsxwraps the existingDotHorizontalicon in a<Dropdown>with a single "Edit" option (usesEditfrom@rtcamp/frappe-ui-react/icons) that navigates tobuildEditPath(projectId, note.name). The dropdown shows on all cards regardless of owner — BE enforces edit permission.Side-fix —
tabs/index.tsxTAB_KEYS / TABS alignment. A previous commit (eb01397b) swapped TAB_KEYS so\"notes\"comes before\"risks\"(to fix the view-selector dropdown), but the matching TABS array still had Risks at index 3 and Notes at index 4. The result:?tab=notesrendered Risks content and clicking the Notes tab generated?tab=risksURLs. This PR depends on?tab=notesworking correctly for the post-save redirect, so the TABS array is reordered to match the new TAB_KEYS order. This also unbreaks the Risks-tab view-selector dropdown (the original intent of the prior commit).Locked decisions (from Aditya's
RESOLVEDon the Slack plan-approval thread): all 12 plan defaults — project tabs strip hidden, styled<textarea>title with Enter-blurs-to-body, TextEditorfixedMenu=false, Save disabled until title is non-empty,status: \"Publish\"on create (grid filtersstatus === \"Publish\"), edit route shape/notes/:noteId/edit, SWRrevalidateOnMountfor the post-save grid refresh, ellipsis dropdown shown on all cards,<Spinner />while edit-mode data loads, success / error toasts, redirect to?tab=notes, batch-mode execution.Testing Instructions
https://pms-temp.frappe.rt.gw/next-pms/projects/PROJ-0205?tab=notes(or any project with at least one note). Confirm the Notes tab is selected and the existing cards render./projects/PROJ-0205/notes/new. Verify:Projects → Project name).Add note title(24px / semibold / ink-gray-4).Type / to format and insert(14px / regular / ink-gray-4) with no toolbar above it.POST /api/method/next_pms.timesheet.api.project_status_update.create_project_status_updatewith{ project, title, description, status: \"Publish\" }, status 200.Note saved./projects/PROJ-0205?tab=notes. The new card appears in the grid./projects/PROJ-0205/notes/<name>/edit.<Spinner />while the note loads.POST update_project_status_updatewith{ name, title, description }, status 200.?tab=notes. Card reflects the updated content.Additional Information
h1EnhdK8swe6FCyxUW1XHx): Full Frame, Sub-header, Note workspace.1780483005.460559in#bots-ai-workflow-next-pms.PROJ-0205against the real BE (POST 200, GET 200, redirect, edit pre-population, empty-title edge case).?tab=notesbroken.Screenshot/Screencast
NA — text-heavy editor view, all states described in the testing instructions.
Checklist
Fixes #1097