Remove title field from notes and update tests to reflect changes

Refactors the notes feature by removing the `title` field and updating all related tests and UI components to use `content` instead.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7464969c-726a-4ec2-a3b2-8ff63f91f6ed
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/591TuZw
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
riyadhafraa
2026-05-11 07:53:40 +00:00
parent a8db38c70a
commit 2b31b5e3aa
12 changed files with 35 additions and 115 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

@@ -431,9 +431,6 @@ export function IncomingNotePopup() {
saveStoredPos(userId, next);
};
const bodyTitle = replyPayload
? replyPayload.noteTitle?.trim() || t("notes.popup.untitled")
: current.title?.trim() || t("notes.popup.untitled");
const bodyContent = replyPayload
? replyPayload.replyContent
: current.content;
@@ -536,11 +533,6 @@ export function IncomingNotePopup() {
className="p-4 space-y-3 max-h-[50vh] overflow-y-auto"
data-testid="incoming-note-popup-body"
>
{bodyTitle && (
<div className="font-semibold text-sm text-foreground break-words">
{bodyTitle}
</div>
)}
{isChecklistNote ? (
<PopupChecklistPreview
items={checklistItems}
+18 -71
View File
@@ -305,10 +305,7 @@ export default function NotesPage() {
if (folderSelection.kind === "folder" && n.folderId !== folderSelection.id)
return false;
if (!q) return true;
return (
n.title.toLowerCase().includes(q) ||
n.content.toLowerCase().includes(q)
);
return n.content.toLowerCase().includes(q);
});
}, [notes, search, labelFilter, folderSelection]);
@@ -411,20 +408,14 @@ export default function NotesPage() {
const filteredReceived = useMemo(() => {
const q = search.trim().toLowerCase();
if (!q) return receivedNotes;
return receivedNotes.filter(
(n) =>
n.title.toLowerCase().includes(q) ||
n.content.toLowerCase().includes(q),
);
return receivedNotes.filter((n) => n.content.toLowerCase().includes(q));
}, [receivedNotes, search]);
const filteredSent: SentNote[] = useMemo(() => {
const q = search.trim().toLowerCase();
if (!q) return sentNotes;
return sentNotes.filter(
(n: SentNote) =>
n.title.toLowerCase().includes(q) ||
n.content.toLowerCase().includes(q),
return sentNotes.filter((n: SentNote) =>
n.content.toLowerCase().includes(q),
);
}, [sentNotes, search]);
@@ -805,10 +796,7 @@ export default function NotesPage() {
archivedReceived={archivedReceivedNotes.filter((n) => {
const q = search.trim().toLowerCase();
if (!q) return true;
return (
n.title.toLowerCase().includes(q) ||
n.content.toLowerCase().includes(q)
);
return n.content.toLowerCase().includes(q);
})}
onOpenThread={(id) => setOpenThreadId(id)}
/>
@@ -1151,11 +1139,6 @@ function NoteCard({
>
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
{note.title && (
<div className="font-semibold text-sm text-foreground break-words">
{note.title}
</div>
)}
{note.kind === "checklist" ? (
<ChecklistView
items={note.items ?? []}
@@ -1813,11 +1796,6 @@ function ReceivedList({
</div>
<StatusBadge status={n.status} />
</div>
{n.title && (
<div className="font-semibold text-sm text-foreground break-words">
{n.title}
</div>
)}
{n.kind === "checklist" ? (
<ChecklistView
items={n.items ?? []}
@@ -1913,11 +1891,6 @@ function SentList({
/>
</span>
)}
{n.title && (
<div className="font-semibold text-sm text-foreground break-words">
{n.title}
</div>
)}
{n.kind === "checklist" ? (
<ChecklistView
items={n.items ?? []}
@@ -2289,7 +2262,7 @@ function ThreadDialog({
>
<DialogHeader>
<DialogTitle className="text-base">
{thread?.title || t("notes.editTitle", "Edit note")}
{t("notes.editTitle", "Edit note")}
</DialogTitle>
</DialogHeader>
{isLoading || !thread ? (
@@ -2891,7 +2864,6 @@ function Composer({
}) {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [color, setColor] = useState("default");
const [labelIds, setLabelIds] = useState<number[]>([]);
@@ -2941,10 +2913,9 @@ function Composer({
document.addEventListener("mousedown", onClick);
return () => document.removeEventListener("mousedown", onClick);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, title, content, color, labelIds, kind, items]);
}, [open, content, color, labelIds, kind, items]);
const reset = () => {
setTitle("");
setContent("");
setColor("default");
setLabelIds([]);
@@ -2954,10 +2925,10 @@ function Composer({
};
// Build the create-note payload from current composer state and a
// boolean indicating whether the composer is empty (no title and no
// body / checklist items). Shared by save() and sendNew() so the
// "save before send" path uses the exact same data the bare Save
// button would have submitted.
// boolean indicating whether the composer is empty (no body / checklist
// items). Shared by save() and sendNew() so the "save before send"
// path uses the exact same data the bare Save button would have
// submitted.
const buildPayload = () => {
const cleanItems =
kind === "checklist"
@@ -2966,12 +2937,10 @@ function Composer({
.filter((it) => it.text.length > 0)
: [];
const isEmpty =
!title.trim() &&
(kind === "checklist" ? cleanItems.length === 0 : !content.trim());
kind === "checklist" ? cleanItems.length === 0 : !content.trim();
return {
isEmpty,
payload: {
title: title.trim(),
content: kind === "checklist" ? "" : content.trim(),
color,
labelIds,
@@ -3010,10 +2979,9 @@ function Composer({
const sendDisabled =
create.isPending ||
(!title.trim() &&
(kind === "checklist"
? items.every((it) => !it.text.trim())
: !content.trim()));
(kind === "checklist"
? items.every((it) => !it.text.trim())
: !content.trim());
return (
<div
@@ -3025,16 +2993,8 @@ function Composer({
>
{open ? (
<>
<Input
autoFocus
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder={t("notes.titlePlaceholder", "Title")}
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 font-semibold"
data-testid="notes-composer-title"
/>
{kind === "checklist" ? (
<div className="mt-2 px-1">
<div className="px-1">
<ChecklistEditor
items={items}
onChange={setItems}
@@ -3043,6 +3003,7 @@ function Composer({
</div>
) : (
<Textarea
autoFocus
ref={contentRef}
value={content}
onChange={(e) => setContent(e.target.value)}
@@ -3056,7 +3017,7 @@ function Composer({
contentFocused.current = false;
}}
placeholder={t("notes.contentPlaceholder", "Take a note...")}
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 mt-1 min-h-[80px] resize-none"
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 min-h-[80px] resize-none"
data-testid="notes-composer-content"
/>
)}
@@ -3127,7 +3088,6 @@ function EditNoteDialog({
onClose: () => void;
}) {
const { t } = useTranslation();
const [title, setTitle] = useState(note.title);
const [content, setContent] = useState(note.content);
const [color, setColor] = useState(note.color);
const [labelIds, setLabelIds] = useState<number[]>(note.labelIds);
@@ -3145,7 +3105,6 @@ function EditNoteDialog({
update.mutate(
{
id: note.id,
title: title.trim(),
content: kind === "checklist" ? "" : content.trim(),
color,
labelIds,
@@ -3170,12 +3129,6 @@ function EditNoteDialog({
{t("notes.editTitle", "Edit note")}
</DialogTitle>
</DialogHeader>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder={t("notes.titlePlaceholder", "Title")}
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 font-semibold text-base"
/>
{kind === "checklist" ? (
<div className="px-1 min-h-[160px]" data-testid="notes-edit-checklist-wrap">
<ChecklistEditor
@@ -3429,7 +3382,6 @@ function SharedFolderView({
const q = search.trim().toLowerCase();
if (!q) return data.notes;
return data.notes.filter((n) => {
if (n.title?.toLowerCase().includes(q)) return true;
if (n.content?.toLowerCase().includes(q)) return true;
if (n.kind === "checklist" && Array.isArray(n.items)) {
return n.items.some((it) => it.text?.toLowerCase().includes(q));
@@ -3559,11 +3511,6 @@ function SharedFolderView({
n.color,
)}`}
>
{n.title && (
<div className="font-semibold text-sm text-foreground break-words pe-8">
{n.title}
</div>
)}
{n.kind === "checklist" ? (
<ChecklistView
items={n.items ?? []}
@@ -95,9 +95,9 @@ test.describe("note card actions on touch devices", () => {
);
expect(noHover).toBe(true);
const title = `touch-test ${uniq()}`;
const content = `touch-test ${uniq()}`;
await page.getByTestId("notes-composer").click();
await page.getByTestId("notes-composer-title").fill(title);
await page.getByTestId("notes-composer-content").fill(content);
const savePromise = page.waitForResponse(
(r) =>
new URL(r.url()).pathname === "/api/notes" &&
@@ -78,11 +78,8 @@ test("composer creates a checklist note that persists across reloads", async ({
await page.goto("/notes");
await expect(page.getByTestId("notes-page")).toBeVisible();
const title = `checklist-test ${uniq()}`;
// Open composer, set title, switch to checklist.
// Open composer and switch to checklist.
await page.getByTestId("notes-composer").click();
await page.getByTestId("notes-composer-title").fill(title);
await page.getByTestId("notes-composer-kind-toggle").click();
// Add two items via the "Add item" button.
@@ -104,7 +101,6 @@ test("composer creates a checklist note that persists across reloads", async ({
await page.getByTestId("notes-composer-save").click();
const resp = await savePromise;
const note = await resp.json();
expect(note.title).toBe(title);
expect(note.kind).toBe("checklist");
expect(Array.isArray(note.items)).toBe(true);
expect(note.items.map((i) => i.text)).toEqual(["Buy milk", "Walk the dog"]);
@@ -77,10 +77,10 @@ test("composer persists chosen color when saving via outside click", async ({
await page.goto("/notes");
await expect(page.getByTestId("notes-page")).toBeVisible();
// Open the composer and type a title.
const title = `color-test ${uniq()}`;
// Open the composer and type some content.
const content = `color-test ${uniq()}`;
await page.getByTestId("notes-composer").click();
await page.getByTestId("notes-composer-title").fill(title);
await page.getByTestId("notes-composer-content").fill(content);
// Open the color popover and pick a non-default swatch.
await page
@@ -105,6 +105,6 @@ test("composer persists chosen color when saving via outside click", async ({
const resp = await savePromise;
const note = await resp.json();
expect(note.title).toBe(title);
expect(note.content).toBe(content);
expect(note.color).toBe("red");
});
@@ -68,9 +68,9 @@ async function loginViaUi(page, username) {
]);
}
async function createNoteViaComposer(page, title) {
async function createNoteViaComposer(page, content) {
await page.getByTestId("notes-composer").click();
await page.getByTestId("notes-composer-title").fill(title);
await page.getByTestId("notes-composer-content").fill(content);
const savePromise = page.waitForResponse(
(r) =>
new URL(r.url()).pathname === "/api/notes" &&
+3 -5
View File
@@ -105,11 +105,9 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
await page.goto("/notes");
await expect(page.getByTestId("notes-page")).toBeVisible();
const noteTitle = `E2E Note ${uniq()}`;
const noteContent = "Please review attached.";
const noteContent = `Please review attached. ${uniq()}`;
await page.getByTestId("notes-composer-open").click();
await page.getByTestId("notes-composer-title").fill(noteTitle);
await page.getByTestId("notes-composer-content").fill(noteContent);
const createPromise = page.waitForResponse(
@@ -162,7 +160,7 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
await page.getByTestId("notes-overflow-received").click();
const inboxCard = page.getByTestId(`received-note-card-${createdNote.id}`);
await expect(inboxCard).toBeVisible();
await expect(inboxCard).toContainText(noteTitle);
await expect(inboxCard).toContainText(noteContent);
await expect(inboxCard).toContainText(sender.displayEn);
await expect(
inboxCard.getByTestId("status-badge-unread"),
@@ -209,7 +207,7 @@ test("sender → recipient → reply flow updates Inbox + Sent tabs", async ({
const sentCard = page.getByTestId(`sent-note-card-${createdNote.id}`);
await expect(sentCard).toBeVisible();
await expect(sentCard).toContainText(noteTitle);
await expect(sentCard).toContainText(noteContent);
await expect(sentCard).toContainText(recipient.displayEn);
// Per-recipient status pill flipped to "replied".
const recipientPill = page.getByTestId(
@@ -83,7 +83,6 @@ test("recipient ticks a checklist item in the popup; sender sees the update", as
// Compose a checklist note with two items.
await senderPage.getByTestId("notes-composer-open").click();
await senderPage.getByTestId("notes-composer-title").fill(`Collab ${uniq()}`);
await senderPage.getByTestId("notes-composer-kind-toggle").click();
const list = senderPage.getByTestId("notes-composer-checklist-list");
await expect(list).toBeVisible();
@@ -118,10 +118,8 @@ test("recipient sees a popup modal when a note arrives in real time", async ({
await senderPage.goto("/notes");
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
const noteTitle = `Popup Note ${uniq()}`;
const noteContent = "Drop everything and read this.";
const noteContent = `Drop everything and read this. ${uniq()}`;
await senderPage.getByTestId("notes-composer-open").click();
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
await senderPage.getByTestId("notes-composer-content").fill(noteContent);
const createPromise = senderPage.waitForResponse(
(r) =>
@@ -167,7 +165,6 @@ test("recipient sees a popup modal when a note arrives in real time", async ({
const popup = recipientPage.getByTestId("incoming-note-popup");
await expect(popup).toBeVisible({ timeout: 3_000 });
expect(Date.now() - sendCompletedAt).toBeLessThan(3_000);
await expect(popup).toContainText(noteTitle);
await expect(popup).toContainText(noteContent);
await expect(popup).toContainText(sender.displayEn);
// Floating card — NOT a modal: must not render as an alertdialog
@@ -252,10 +249,8 @@ test("sender sees a floating reply card when the recipient replies", async ({
await senderPage.goto("/notes");
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
const noteTitle = `Reply Note ${uniq()}`;
const noteContent = "Please reply with your thoughts.";
const noteContent = `Please reply with your thoughts. ${uniq()}`;
await senderPage.getByTestId("notes-composer-open").click();
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
await senderPage.getByTestId("notes-composer-content").fill(noteContent);
const createPromise = senderPage.waitForResponse(
(r) =>
@@ -403,9 +398,7 @@ test("recipient popup renders checklist items for a checklist note", async ({
await senderPage.goto("/notes");
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
const noteTitle = `Checklist Note ${uniq()}`;
await senderPage.getByTestId("notes-composer-open").click();
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
// Switch composer to checklist mode and add two items.
await senderPage.getByTestId("notes-composer-kind-toggle").click();
const list = senderPage.getByTestId("notes-composer-checklist-list");
@@ -462,7 +455,6 @@ test("recipient popup renders checklist items for a checklist note", async ({
const popup = recipientPage.getByTestId("incoming-note-popup");
await expect(popup).toBeVisible({ timeout: 5_000 });
await expect(popup).toContainText(noteTitle);
// Checklist branch: dedicated container is rendered, with each item
// as a row, and the plain text content branch is NOT used.
const checklist = recipientPage.getByTestId(
@@ -171,10 +171,8 @@ test("recipient on a touch device can tap Reply on the floating note popup", asy
await senderPage.goto("/notes");
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
const noteTitle = `Touch Note ${uniq()}`;
const noteContent = "Tap reply on this card.";
const noteContent = `Tap reply on this card. ${uniq()}`;
await senderPage.getByTestId("notes-composer-open").click();
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
await senderPage.getByTestId("notes-composer-content").fill(noteContent);
const createPromise = senderPage.waitForResponse(
(r) =>
@@ -317,12 +315,10 @@ test("popup + meeting alert simultaneously: tap on popup still works", async ({
await senderPage.goto("/notes");
await expect(senderPage.getByTestId("notes-page")).toBeVisible();
const noteTitle = `Stack Note ${uniq()}`;
await senderPage.getByTestId("notes-composer-open").click();
await senderPage.getByTestId("notes-composer-title").fill(noteTitle);
await senderPage
.getByTestId("notes-composer-content")
.fill("Tap reply with the meeting alert visible too.");
.fill(`Tap reply with the meeting alert visible too. ${uniq()}`);
const createPromise = senderPage.waitForResponse(
(r) =>
new URL(r.url()).pathname === "/api/notes" &&
+1 -1
View File
@@ -38,7 +38,7 @@ export default defineConfig({
await import("@replit/vite-plugin-cartographer").then((m) =>
m.cartographer({
root: path.resolve(import.meta.dirname, ".."),
}),أحذ العنوان من الملاحظات
}),
),
await import("@replit/vite-plugin-dev-banner").then((m) =>
m.devBanner(),