Owner can share a whole folder with other users; recipients see it
under "Shared with me" in the folders rail and view notes read-only
(cannot edit, add, move, or delete).
- DB: new noteFolderSharesTable (folderId/recipientUserId, cascade,
unique idx). Pushed via drizzle-kit.
- API (artifacts/api-server/src/routes/notes.ts):
- GET/PATCH/POST /note-folders return sharedWithCount.
- GET /note-folders/shared-with-me, GET /note-folders/:id/shares,
PUT /note-folders/:id/shares (idempotent diff), DELETE
/note-folders/:id/shares/:userId, GET /note-folders/:id/shared-notes
(verifies recipient via noteFolderSharesTable, stamps readOnly).
- Emits note-folder-shared / note-folder-unshared on share changes.
- Existing note write paths remain owner-scoped → recipients can't
mutate owner notes.
- Frontend types/hooks (notes-api.ts): SharedFolder, FolderShareRecipient,
SharedFolderNote/View; useFolderShares, useUpdateFolderShares,
useSharedWithMeFolders, useSharedFolderNotes.
- FoldersRail: Share menu item, sharedWithCount badge, "Shared with me"
section, "shared-folder" selection kind, onShareFolder prop.
- notes.tsx: SharedFolderView (no Composer, read-only cards using
ChecklistView), FolderShareDialog (seeds existing recipients,
idempotent PUT on save), revocation fallback effect, dialog mount.
- use-notifications-socket.ts: subscribe to note-folder-shared /
-unshared → invalidate shared-with-me + open shared-folder notes for
live rail/view refresh.
- i18n: AR/EN keys for share/sharedBy/sharedByName/sharedWithCount/
sharedWithMe/readOnly/shareRevoked/sharedEmpty/shareSaved/shareFailed.
Pre-existing failing `test` workflow (executive-meetings type errors)
is unrelated and out of scope.
Task #420 — answers the user request "وين خيار اضيف list to do?".
Schema (lib/db/src/schema/notes.ts):
- notes + note_recipients gain `kind` (varchar(16) default 'text')
and `items` (jsonb<ChecklistItem[]> nullable). Snapshot copy on
note_recipients keeps delivered checklists immutable across sender
edits/deletes.
- Drizzle push applied; lib/db .d.ts rebuilt.
API (artifacts/api-server/src/routes/notes.ts):
- ChecklistItem type + bounded parser (≤200 items, id ≤64, text ≤500).
- parseNoteInput normalizes items↔kind on create and on PATCH that
carries `kind`; PATCH handler additionally coerces items-only
patches against the note's existing kind so a text note can never
end up with checklist items (and vice versa).
- POST/PATCH/send/loadRecipientsForNote/received/thread responses and
the realtime `note_received` payload all carry kind+items, with the
thread response falling back to the recipient snapshot.
Client (artifacts/tx-os/src/lib/notes-api.ts + pages/notes.tsx):
- Note/ReceivedNote/NoteThread/SentNoteRecipient extended with
kind+items.
- New `ChecklistEditor`, `ChecklistView`, and `KindToggle` components.
- Composer and EditNoteDialog gain the to-do toggle (ListTodo icon)
and switch between Textarea and ChecklistEditor; saves send
kind+items, with empty-checklist auto-discard mirroring the existing
empty-text behaviour.
- NoteCard, inbox list, sent list, and ThreadDialog body render the
checklist (read-only on snapshots; owner cards can toggle done via
PATCH with stopPropagation so the edit dialog doesn't open).
i18n: notes.checklist.{toggle,addItem,itemPlaceholder,emptyHint,
removeItem,progress} added to en.json + ar.json.
Tests: new artifacts/tx-os/tests/notes-checklist.spec.mjs covers
composer→persist→reload→toggle, items-only PATCH normalization on
both kinds, and checklist delivery to recipient snapshot. All 3 pass.
Architect review (evaluate_task) flagged one real issue: items-only
PATCH normalization. Fixed in the PATCH handler and locked in by the
new normalization test.
Pre-existing executive-meetings.ts tsc errors are unchanged and
unrelated to this task.
- DB: new note_folders table (per-user unique name) + nullable folder_id on
notes with ON DELETE SET NULL; pushed via @workspace/db.
- API: full /note-folders CRUD with user scoping; /notes POST/PATCH validate
folder ownership; folder noteCount is tab-aware (active vs archived) via
?archived= query param computed by GROUP BY (drizzle correlated subquery
was returning 0 — replaced with two queries merged in JS).
- Client: NoteFolder type, useNoteFolders/useCreateFolder/useUpdateFolder/
useDeleteFolder/useMoveNoteToFolder hooks (optimistic update for moves
across all ["notes", ...] caches).
- UI: new FoldersRail (artifacts/tx-os/src/components/notes/folders-rail.tsx)
rendered next to My Notes + Archive tabs; HTML5 draggable note cards with
module-scoped DRAGGING_NOTE_ID fallback; visible drop highlight; rename +
delete + create inline; folder-scoped + Unfiled filtering in notes.tsx.
- Bilingual: notes.folders.* keys added to en.json and ar.json (RTL works
via existing dir prop wired from i18n.language).
- Test: artifacts/tx-os/tests/notes-folders.spec.mjs covers create folder,
drag note to folder, filter by folder/Unfiled, drag between folders,
rename, delete a non-empty folder (verifies FK SET NULL + Unfiled count),
cross-user folder rejection (400), and Arabic+RTL rendering.
Drift from plan: none — all originally scoped work shipped. Strengthened
e2e + cross-user check + tab-aware counts added per code review feedback.
Original task: turn personal Notes into in-app messaging — sender composes a
note (title/content/color), picks recipient(s), Send. Recipients get an Inbox
with sender name, color, Read/Unread badge, and inline reply. Sender sees
Sent Notes with per-recipient status. Sender's and recipient's copies must
be INDEPENDENT, with proper backend access checks (admin sees everything),
realtime updates, and full i18n + RTL.
Initial implementation review FAILED because the recipient view still read
from the sender's notes table, so sender edits/deletes mutated recipient
copies. This commit completes the fix:
- Schema (lib/db/src/schema/notes.ts): added immutable snapshot columns
(title/content/color) on note_recipients; dropped the FK on
note_recipients.note_id and note_replies.note_id and made them plain
integers so recipient threads survive the sender deleting their note.
- Routes (artifacts/api-server/src/routes/notes.ts):
- /notes/received and /notes/:id/thread now serve the recipient snapshot
(sender/admin still see the live note).
- /notes/:id/reply derives the owner from note_recipients.senderUserId
so it works after sender deletion, clears archivedAt, and bumps
status to "replied".
- /notes/sent filters out null noteIds.
- Tests (artifacts/api-server/tests/notes-share.test.mjs): added
snapshot-independence test (sender edit + delete must not mutate
recipient copy; thread + reply still work after sender delete) and
archived-reply-clears-archivedAt test. All 5 backend tests pass; the
existing notes-inbox e2e still passes.
- Architect review: PASS (conditional only on the schema migration being
applied, which has been pushed via `pnpm --filter @workspace/db push`).
Pre-existing executive-meetings TS errors and the failing `test` workflow
are unrelated to this task.
Integrates a new Notes feature, including backend API routes for notes and labels, database schema updates, frontend UI components for creating, viewing, editing, and deleting notes, and internationalization support for notes in both English and Arabic.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 4ab7f101-06a9-4dd7-94c8-617f1751327c
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fYGOSe0
Replit-Helium-Checkpoint-Created: true