Skip to content

feat(notes): blank note editor + edit flow#1427

Open
rtBot wants to merge 7 commits into
feat/redesignfrom
claude/feat/notes-editor
Open

feat(notes): blank note editor + edit flow#1427
rtBot wants to merge 7 commits into
feat/redesignfrom
claude/feat/notes-editor

Conversation

@rtBot
Copy link
Copy Markdown

@rtBot rtBot commented Jun 3, 2026

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/ with types.ts (NoteEditorMode, NoteDraft), constants.ts (buildEditPath, buildNotesGridPath, buildNewNotePath), and useNoteMutation.ts that wraps both useFrappePostCall(create_project_status_update) and useFrappePostCall(update_project_status_update) behind a single save(draft). route.tsx swaps <UnderConstruction /> for <NoteEditor mode=\"new\" /> on /notes/new and adds the /notes/:noteId/edit route with mode=\"edit\". Both routes nest under LayoutWithSidebar so the global sidebar stays.

Section S2 — Edit-mode fetch. useNoteDetail.ts calls get_project_status_update with a stable SWR key when noteId is present; passes null key to skip in new mode.

Section S3 — Editor shell (index.tsx). <ProjectDetailProvider> wraps the editor so the breadcrumb resolves the project name. Layout is <ProjectDetailHeader /> + a centered max-w-[800px] column containing the sub-header and workspace. The <AboutThisProject> sidebar lives in pages/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 content on mount; later prop changes are ignored). The shell now gates the body render through NoteEditorGate — in edit mode it shows <Spinner /> until useNoteDetail resolves, then mounts NoteEditorBody with initialTitle / initialDescription derived from the fetched note. The inner component uses lazy useState initializers so the TextEditor captures the description on its first paint.

Section S4 — Sub-header. Avatar (xs, image from useUser((s) => s.state.image), label from userName) + full-name span. Right: <Button variant=\"solid\" theme=\"gray\" size=\"sm\" label=\"Save note\">loading={isSubmitting}, disabled={!canSave || isSubmitting} where canSave = title.trim().length > 0. useToasts() surfaces \"Note saved\" on success or parseFrappeErrorMsg(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> with placeholder=\"Type / to format and insert\" and fixedMenu={false} per the Figma's distraction-free spec — no toolbar.

Section S6 — Note card edit dropdown. tabs/notes/noteCard.tsx wraps the existing DotHorizontal icon in a <Dropdown> with a single "Edit" option (uses Edit from @rtcamp/frappe-ui-react/icons) that navigates to buildEditPath(projectId, note.name). The dropdown shows on all cards regardless of owner — BE enforces edit permission.

Side-fix — tabs/index.tsx TAB_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=notes rendered Risks content and clicking the Notes tab generated ?tab=risks URLs. This PR depends on ?tab=notes working 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 RESOLVED on the Slack plan-approval thread): all 12 plan defaults — project tabs strip hidden, styled <textarea> title with Enter-blurs-to-body, TextEditor fixedMenu=false, Save disabled until title is non-empty, status: \"Publish\" on create (grid filters status === \"Publish\"), edit route shape /notes/:noteId/edit, SWR revalidateOnMount for 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

  1. Open 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.
  2. Click CreateNew blank note. URL becomes /projects/PROJ-0205/notes/new. Verify:
    • No "About this project" sidebar on the right.
    • No project tabs strip (only the breadcrumb shows Projects → Project name).
    • Sub-header shows your avatar + full name on the left and a disabled "Save note" button on the right.
    • Title placeholder reads Add note title (24px / semibold / ink-gray-4).
    • Body placeholder reads Type / to format and insert (14px / regular / ink-gray-4) with no toolbar above it.
  3. Type a title — placeholder disappears, text renders 24px / semibold / ink-gray-8, Save note becomes enabled.
  4. Type a body — text appears in the TextEditor, no toolbar.
  5. Click Save note:
    • Network: POST /api/method/next_pms.timesheet.api.project_status_update.create_project_status_update with { project, title, description, status: \"Publish\" }, status 200.
    • Success toast Note saved.
    • URL redirects to /projects/PROJ-0205?tab=notes. The new card appears in the grid.
  6. Click the ellipsis (three dots) on any card → dropdown opens with a single Edit option (Edit icon). Click it. URL becomes /projects/PROJ-0205/notes/<name>/edit.
  7. Verify:
    • Brief <Spinner /> while the note loads.
    • Title and body both pre-populate with the saved content (not the placeholders).
    • Save button is enabled (title is non-empty).
  8. Change either field and click Save note:
    • Network: POST update_project_status_update with { name, title, description }, status 200.
    • Toast + redirect to ?tab=notes. Card reflects the updated content.
  9. Edge cases:
    • Empty title → Save disabled. Type → enabled. Clear → disabled again.
    • Re-opening the same note's Edit a second time keeps both fields pre-populated.

Additional Information

  • Figma frames (Copy fileKey h1EnhdK8swe6FCyxUW1XHx): Full Frame, Sub-header, Note workspace.
  • Slack thread: 1780483005.460559 in #bots-ai-workflow-next-pms.
  • Browser-verified on PROJ-0205 against the real BE (POST 200, GET 200, redirect, edit pre-population, empty-title edge case).
  • TAB_KEYS / TABS side-fix is a 4-line move that the implementation depended on. Pulling it out would leave ?tab=notes broken.

Screenshot/Screencast

NA — text-heavy editor view, all states described in the testing instructions.

Checklist

  • I have carefully reviewed the code before submitting it for review.
  • This code is adequately covered by unit tests to validate its functionality.
  • I have conducted thorough testing to ensure it functions as intended.
  • A member of the QA team has reviewed and tested this PR (To be checked by QA or code reviewer)

Fixes #1097

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>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/new and /projects/:projectId/notes/:noteId/edit routes backed by a new note-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 TABS array to align with TAB_KEYS so 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.

Comment thread frontend/packages/app/src/pages/project-details/note-editor/workspace.tsx Outdated
Comment thread frontend/packages/app/src/pages/project-details/note-editor/useNoteMutation.ts Outdated
rtBot and others added 3 commits June 3, 2026 12:11
- 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>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sidebar is not visible for this page. Update about/index to look at current location for rendering

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e88cf7e — editor%2Findex.tsx (sidebar self-hides via useMatch in about/index.tsx).

Comment on lines +56 to +77
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 />;
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make different routes for this. Use dynamic segment to hold noteId

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e88cf7e — tabs/notes/index.tsx (activeView switch removed; now uses nested dynamic routes).

Comment on lines +42 to +46
onClick: () =>
setSearchParams((prev) => {
prev.set(NOTE_ID, note.name);
prev.set(NOTE_MODE, "edit");
return prev;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a simple navigation to /projects/<project-id>/notes/<notes-id>/edit

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e88cf7e — noteCard.tsx (Dropdown stays; Edit onClick uses useNavigate to /projects//notes//edit).

Comment on lines +5 to +7

export const NOTE_MODE = "mode";
export const NOTE_ID = "note";
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these will be needed if active view var is not used

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@b1ink0 b1ink0 self-requested a review June 4, 2026 13:27
Comment thread frontend/packages/app/src/route.tsx
Comment on lines +90 to +101
<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>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants