eef94caf725f83b36fa7bcfdaa08446a6eba7e2b
777 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a9f40bd145 |
Task #433: incoming-note popup — render full note + 3-button action row
- Inner body now renders as a real sticky note: keeps colored
surface, supports checklist (read-only) when noteKind === "checklist".
- Action row trimmed to exactly 3 buttons: Done (تم), Open note
(فتح الملاحظة), Reply (رد). Removed the standalone Dismiss button;
header X still closes.
- Reply variant uses the same 3-button row (Done is a dismiss-only
no-op since the note owner can't mark their own note read).
- Wire kind + items from backend `note_received` payload into client
IncomingNotePayload (noteKind/items) via use-notifications-socket.
- Locales: markRead → "Done"/"تم"; new openNote/done keys; openThread
unified to "Open note"/"فتح الملاحظة".
- Tests: replaced removed `incoming-note-popup-dismiss` testid usages
with header `-close`; updated reply variant assertions to expect the
3-button row; fixed pre-existing pluralization bug in reply path
(`/replies` → `/reply`) that was unrelated to this task but blocked
the reply popup test from validating.
Labels and pin were not surfaced in the popup because they are
sender-private state (note_recipients only snapshots
title/content/color/kind/items); the recipient never receives the
sender's labelIds/isPinned, so showing them would be misleading.
Added a new e2e ("recipient popup renders checklist items for a
checklist note") that composes a checklist note via the UI, sends it,
and asserts `incoming-note-popup-checklist` is visible with both
items — locks in the noteKind/items socket plumbing.
All affected e2e tests pass (notes-popup-on-receive note + reply +
checklist, notes-popup-touch-tap both, notes-inbox); queue unit
tests pass.
|
||
|
|
1ea075d2b6 |
Task #433: incoming-note popup — render full note + 3-button action row
- Inner body now renders as a real sticky note: keeps colored surface, supports checklist (read-only) when noteKind === "checklist". - Action row trimmed to exactly 3 buttons: Done (تم), Open note (فتح الملاحظة), Reply (رد). Removed the standalone Dismiss button; header X still closes. - Reply variant uses the same 3-button row (Done is a dismiss-only no-op since the note owner can't mark their own note read). - Wire kind + items from backend `note_received` payload into client IncomingNotePayload (noteKind/items) via use-notifications-socket. - Locales: markRead → "Done"/"تم"; new openNote/done keys; openThread unified to "Open note"/"فتح الملاحظة". - Tests: replaced removed `incoming-note-popup-dismiss` testid usages with header `-close`; updated reply variant assertions to expect the 3-button row; fixed pre-existing pluralization bug in reply path (`/replies` → `/reply`) that was unrelated to this task but blocked the reply popup test from validating. Labels and pin were not surfaced in the popup because they are sender-private state (note_recipients only snapshots title/content/color/kind/items); the recipient never receives the sender's labelIds/isPinned, so showing them would be misleading. All affected e2e tests pass (notes-popup-on-receive note + reply, notes-popup-touch-tap both, notes-inbox); queue unit tests pass. |
||
|
|
c65f22d45d |
Task #431: polish Send Note dialog (RTL X + search overlap)
Two visual issues in the "إرسال الملاحظة" dialog:
1. The shadcn DialogContent renders a built-in close (X) button hard
pinned at `right-4 top-4`. In Arabic the dialog title is
right-aligned and crashed into the X.
Fix: add `pr-8` to the SendNoteDialog header so the title row
reserves physical right-padding matching where the X actually
lives in both LTR and RTL. (Initially used `pe-8` but corrected to
`pr-8` after code review noted the close button is pinned with
physical `right-4`, so a logical end-padding flips to the wrong
side in RTL.) The shared dialog component is unchanged so other
dialogs are untouched (out of scope per the task plan).
2. The recipient search field used inline `style={{ right|left: 10 }}`
for the magnifier and a conditional `pe-9` / `ps-9` based on
`i18n.language`. In RTL the icon collided with the placeholder.
Fix: drop the conditional branching — position the icon with the
logical class `start-2.5` and always pad the input with `ps-9`.
The icon now sits at the inline-start edge in both LTR and RTL
with a clear gutter to the placeholder/caret.
Skipped step 3 of the plan (i18n aria-label override on the X). It
required no locale changes and no rendering improvement, so per the
plan's own conditional ("only if step 3 keeps the override; otherwise
no locale changes") it was dropped to keep scope tight.
Verification:
- `pnpm exec tsc --noEmit` clean.
- Playwright: `notes-inbox.spec.mjs` (1/1) and
`notes-popup-touch-tap.spec.mjs` (2/2) green — both exercise the
send-note dialog rendering. `notes-popup-on-receive.spec.mjs` runs
two browser contexts and exceeds the local 120s shell cap; not
re-run here, but the diff is CSS-only and cannot affect the wire
flow it covers.
|
||
|
|
7392ba6867 |
Task #431: polish Send Note dialog (RTL X + search overlap)
Two visual issues in the "إرسال الملاحظة" dialog:
1. The shadcn DialogContent renders a built-in close (X) button hard
pinned at `right-4 top-4`. In Arabic the dialog title is
right-aligned and crashed into the X.
Fix: add `pe-8` to the SendNoteDialog header so the title row
reserves logical end-padding for the X in both directions. The
shared dialog component is unchanged so other dialogs are untouched
(out of scope per the task plan).
2. The recipient search field used inline `style={{ right|left: 10 }}`
for the magnifier and a conditional `pe-9` / `ps-9` based on
`i18n.language`. In RTL the icon collided with the placeholder.
Fix: drop the conditional branching — position the icon with the
logical class `start-2.5` and always pad the input with `ps-9`.
The icon now sits at the inline-start edge in both LTR and RTL
with a clear gutter to the placeholder/caret.
Skipped step 3 of the plan (i18n aria-label override on the X). It
required no locale changes and no rendering improvement, so per the
plan's own conditional ("only if step 3 keeps the override; otherwise
no locale changes") it was dropped to keep scope tight.
Verification:
- `pnpm exec tsc --noEmit` clean.
- Playwright: `notes-inbox.spec.mjs` (1/1) and
`notes-popup-touch-tap.spec.mjs` (2/2) green — both exercise the
send-note dialog rendering. `notes-popup-on-receive.spec.mjs` runs
two browser contexts and exceeds the local 120s shell cap; not
re-run here, but the diff is CSS-only and cannot affect the wire
flow it covers.
|
||
|
|
e1b83b62f2 |
Task #430: fix folder note creation + add in-app back to folders
Fixes two regressions surfaced after the folder browser shipped in #429: 1. Note created from inside a folder went to Unfiled - Composer now takes `folderSelection` and resolves the target folder: `folder` → that id, `unfiled`/`all` → null. The new note appears in the active folder view immediately and the folder count increments via the existing notes query invalidation. 2. No in-app back from a folder view - Added a "← Back to folders / المجلدات" chip above the composer whenever a folder or Unfiled is selected (active tab only). Tapping it resets the selection to "all" and lands the user back on the folders browser. The global header back button is unchanged. - Chevron flips for RTL via `rtl:rotate-180`. - Locale keys: `notes.folders.backToFolders` (EN/AR). Verification: - tsc --noEmit clean. - Playwright suites all green: notes-folders, notes-checklist, notes-card-touch-actions, notes-delete-confirm (8 tests). |
||
|
|
595e36bfa0 |
Task #429: folders-as-files browser at "All notes" view
- Notes restored to original sticky-note cards (Section + NoteCard) at all views, reverting the file-manager-style note rendering from #427. - Folders render as a file-manager browser at the top of the active "All notes" view: black folder icon, name, relative time, item-count badge, "…" kebab with rename/delete, and a checkbox for multi-select. Honors the reused grid/list view-toggle. - Multi-select bar appears when ≥1 folder is checked, with bulk Delete (confirmation dialog) and Cancel actions. Bulk delete uses Promise.all over useDeleteFolder; selection auto-prunes deleted ids. - View-toggle is hidden when not at active+all (it now only governs the folder browser). - Added `mode: "rail" | "chips-only"` to FoldersRail. Chips-only is scoped to active+all only — archived/inbox/sent rail behavior is unchanged. - Drop targets preserved (data-folder-drop, folder-drop-${id}) so desktop DnD and touch-long-press pipelines keep working. - Click propagation stopped in DropdownMenuContent and around the checkbox so menu/checkbox interactions don't navigate into the folder. - Removed obsolete FilesView/FileItem/SelectionBar/DeleteDialog/colorTint helpers (~650 lines). - Added EN/AR locale keys: notes.folders.actions, renameFailed, itemCount, itemCountOne, select, bulkSelected, bulkDeleteTitle, bulkDeleteConfirm, bulkDeleteFailed. - Verified: tsc --noEmit clean; notes-folders, notes-card-touch-actions, notes-checklist, notes-delete-confirm Playwright suites all green. Drift note: kept "All notes" as a mixed surface (folders browser + sticky notes below the composer) per the user's task wording, which explicitly requires both sticky-note cards and file-manager folders. Code reviewer suggested folders-only as primary surface, but doing so would hide notes the user explicitly asked to see and break tests at the default view. |
||
|
|
b46bf97fa1 |
Task #429: folders-as-files browser at "All notes" view
- Notes restored to original sticky-note cards (Section + NoteCard) at all views, reverting the file-manager-style note rendering from #427. - Folders now render as a file-manager browser at the top of the "All notes" view: black folder icon, name, relative time, item-count badge, "…" kebab with rename/delete, plus a checkbox for multi-select. - Multi-select bar appears when ≥1 folder is checked, with bulk Delete (confirmation dialog) and Cancel actions. Bulk delete uses Promise.all over useDeleteFolder; selection auto-prunes deleted ids. - Grid/list view-toggle reused from #427; toggle is hidden when not at "All notes" since it now only governs the folder browser. - Added `mode: "rail" | "chips-only"` to FoldersRail. At "All notes" the rail switches to chips-only (All / Unfiled / + New folder) so folder rows live exclusively in the new browser and test ids never collide. - Drop targets preserved (data-folder-drop, folder-drop-${id}) so the desktop DnD and touch-long-press pipelines keep working. - Stop click propagation in DropdownMenuContent and around the checkbox to prevent rename/delete/checkbox clicks from navigating into the folder. - Removed obsolete FilesView/FileItem/SelectionBar/DeleteDialog/colorTint helpers (~650 lines). - Added EN/AR locale keys: notes.folders.actions, renameFailed, itemCount, itemCountOne, select, bulkSelected, bulkDeleteTitle, bulkDeleteConfirm, bulkDeleteFailed. - Verified: tsc --noEmit clean; all 8 tests in notes-folders, notes-card-touch-actions, notes-checklist, notes-delete-confirm pass. |
||
|
|
be05c15a97 |
Task #429: folders-as-files browser at "all" notes view
- Notes reverted to original sticky-note cards (Section + NoteCard) at all views, removing the file-manager-style note rendering from task #427. - Folders now render as a file-manager browser at the top of the "All notes" view: big black folder icon, name, relative time, item-count badge, and a "…" kebab with rename/delete. Grid and list layouts honor the existing view-toggle, plus a "{{count}} folders" footer. - Added `mode: "rail" | "chips-only"` prop to FoldersRail. At "all" view the rail switches to chips-only (All / Unfiled / + New folder) so folder rows live exclusively in the new browser and test ids never collide. - View-toggle is now hidden when not at "all" (folders-only control). - New FoldersBrowser/FolderItem reuse useUpdateFolder/useDeleteFolder, set data-folder-drop for the touch DnD pipeline, and stop click propagation inside the dropdown so rename/delete don't navigate the card. - Added locale keys: notes.folders.actions/renameFailed/itemCount(One). - Removed obsolete FilesView/FileItem/SelectionBar/DeleteDialog/colorTint helpers (~650 lines). - Verified: tsc --noEmit clean; notes-folders, notes-card-touch-actions, notes-checklist, notes-delete-confirm Playwright suites all green. |
||
|
|
5f57354414 | Transitioned from Plan to Build mode | ||
|
|
971973963c |
Add file manager view to notes section with grid and list options
Adds new translation keys for UI elements and implements a view mode toggle (grid/list) for the notes page, persisting the selection in local storage. |
||
|
|
133790d655 |
Task #427: fix floating note popup hanging on touch devices
Root cause on iOS Safari (iPad/phone): the `IncomingNotePopup` rendered a `fixed inset-0 z-[110]` full-viewport wrapper. Stacking that layer on top of the upcoming-meeting alert plus a Radix toast intermittently caused the next finger tap to be swallowed — the user saw the popup "hang". A simultaneous re-render storm from synchronous query invalidations on multi-event socket fanouts made it worse on slower hardware. Fixes: - Drop the full-viewport wrapper: render the popup card directly as a sized `fixed` element. No more invisible viewport-sized layer between touch and the rest of the UI. - Suppress the redundant note/reply toast when the floating popup actually accepts the event. The popup IS the surface; doubling up just stacks one more floating layer. - Remove `autoFocus` on the Reply button — focus-steal was contributing to the perceived hang. - Coalesce `react-query` `invalidateQueries` calls in the notifications socket: collect into a Set and flush once per animation frame so a burst of socket events triggers a single refetch pass instead of N synchronous re-renders. Cleaned up on socket disconnect. Tests (artifacts/tx-os/tests/notes-popup-touch-tap.spec.mjs): - "recipient on a touch device can tap Reply on the floating note popup" — verifies the popup is no longer viewport-sized, the duplicate toast is suppressed, and a real `locator.tap()` succeeds. - "popup + meeting alert simultaneously: tap on popup still works" — the user's exact scenario: seeds an imminent meeting so the alert appears, triggers a real cross-user note delivery so both float simultaneously, then taps Reply on the popup and confirms it dismisses while the alert remains. Both tests + tsc --noEmit clean. Files: - artifacts/tx-os/src/components/notes/incoming-note-popup.tsx - artifacts/tx-os/src/hooks/use-notifications-socket.ts - artifacts/tx-os/tests/notes-popup-touch-tap.spec.mjs (new) |
||
|
|
6adf42617f |
Task #427: fix floating note popup hanging on touch devices
Root cause on iOS Safari (iPad/phone): the `IncomingNotePopup` rendered a `fixed inset-0 z-[110]` full-viewport wrapper with `pointer-events-none`. Stacking that layer on top of the upcoming meeting alert plus a Radix toast intermittently caused the next finger tap to be swallowed — the user saw the popup "hang". Fixes: - Drop the full-viewport wrapper: render the popup card directly as a sized `fixed` element. No more invisible viewport-sized layer between touch and the rest of the UI. - Suppress the redundant note/reply toast when the floating popup actually accepts the event. The popup IS the surface; doubling up just stacks one more floating layer. - Remove `autoFocus` on the Reply button. A new popup arriving while the user is mid-tap would steal focus, contributing to the perceived hang. Tests: - New `notes-popup-touch-tap.spec.mjs`: emulates a touch viewport, sends a real cross-user note, verifies popup bounding box is card-sized (not viewport-sized), the redundant toast is suppressed, and a `locator.tap()` on Reply dismisses the popup and navigates. - `tsc --noEmit` clean. Files: - artifacts/tx-os/src/components/notes/incoming-note-popup.tsx - artifacts/tx-os/src/hooks/use-notifications-socket.ts - artifacts/tx-os/tests/notes-popup-touch-tap.spec.mjs (new) |
||
|
|
569492f17d | Transitioned from Plan to Build mode | ||
|
|
52bb25c3ea |
notes: show note-card actions on touch devices (task #425)
Original task: on phones and iPad the per-card action row (color,
label, send, archive, delete) was hidden by `opacity-0
group-hover:opacity-100`. Touch devices have no hover, so users
couldn't send a freshly-created note (or change its color, label,
archive it).
Changes
- artifacts/tx-os/src/pages/notes.tsx
- Add Tailwind v4 arbitrary variant `[@media(hover:none)]:opacity-100`
to the per-card action row so it stays always-visible on touch
devices while desktops keep the existing hover-reveal behaviour.
- Apply the same variant to the unpinned pin button (which uses the
identical hover-only treatment).
Tests
- artifacts/tx-os/tests/notes-card-touch-actions.spec.mjs (new)
- Uses `test.use({ hasTouch: true, isMobile: true, viewport })` on
chromium (webkit isn't installed) so the page reports
`(hover: none)`. Sanity-checks that via matchMedia, then creates a
note via the composer (tap-outside auto-save) and asserts both
that the Send button is visible and that the action row's
computed opacity is exactly 1 — Playwright's `toBeVisible` alone
doesn't catch opacity-0.
Verification
- pnpm --filter @workspace/tx-os exec tsc --noEmit: clean.
- pnpm --filter @workspace/tx-os test:e2e -- notes-delete-confirm
notes-card-touch-actions: 3 passed (touch test + the two
delete-confirm specs from #423 to confirm no regression).
- Architect code review: no severe issues. Desktop hover-reveal is
preserved because `(hover: none)` doesn't match on pointer devices.
Out of scope (per task plan): the trash button inside the
manage-labels dialog (different surface, dialog-only).
|
||
|
|
887bdc73f5 |
notes: replace native confirm() with styled AlertDialog (task #423)
Original task: deleting a note (and a label from the manage-labels menu)
showed the browser's native confirm() with the raw Repl hostname,
clashing with the rest of the app. Switch both call sites to the
project's existing AlertDialog component, matching the pattern used in
FoldersRail and SendNoteDialog.
Changes
- artifacts/tx-os/src/pages/notes.tsx
- NoteCard: add `confirmDeleteOpen` state; render AlertDialog as a
Fragment sibling of the clickable card (not a child) so React's
portal event bubbling cannot trigger the card's onEdit handler when
the user interacts with the dialog. Also stopPropagation on the
trash button itself.
- LabelsDialog: add `pendingDeleteLabel` state and a sibling
AlertDialog inside the Dialog root. Both dialogs use existing
locale keys (notes.deleteConfirm, notes.deleteLabelConfirm,
notes.delete, common.cancel) — no new strings needed.
- Add data-testids: note-card-delete-{id}, note-delete-dialog-{id},
note-delete-confirm-{id}, label-delete-{id},
label-delete-dialog-{id}, label-delete-confirm-{id},
notes-manage-labels-open.
Tests
- artifacts/tx-os/tests/notes-delete-confirm.spec.mjs (new)
- Note delete: cancel keeps the card, confirm fires DELETE and
removes the card. Deliberately does NOT register a `dialog` event
handler — a native confirm would hang the test.
- Label delete: open manage-labels, create a label via the UI, click
trash, confirm, assert DELETE /api/note-labels/{id}.
Verification
- pnpm --filter @workspace/tx-os exec tsc --noEmit: clean.
- pnpm --filter @workspace/tx-os test:e2e -- notes-delete-confirm:
2 passed.
- Architect code review surfaced a real bubble-through risk on the
card's onClick={onEdit}; fixed by lifting the AlertDialog to a
Fragment sibling.
Out of scope (not changed): native confirm()/alert() outside the Notes
page. (executive-meetings.tsx already uses an in-app `confirm({...})`
modal, not the native browser dialog.)
|
||
|
|
bd8a4adbe9 |
notes: add per-note checklist (to-do list) option
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. |
||
|
|
bc8493ba5c |
notes: persist chosen color (and labels) when composer auto-saves
When creating a new note, picking a color from the palette had no effect — the note saved with the default white background. Editing an existing note worked because that path lives in a Dialog with no auto-save guard. Root cause The inline Composer in artifacts/tx-os/src/pages/notes.tsx attaches a document-level mousedown listener that auto-saves whenever the click falls outside its container ref. The color and label menus are built on Radix Popover, which renders content in a portal outside the composer's DOM tree. So clicking a swatch fired: 1. document mousedown -> save() with the OLD color 2. swatch click -> setColor(new) (state already discarded) Fix The mousedown handler now ignores clicks whose target is inside any Radix popper wrapper, Radix portal, or role="dialog" element. The guard is narrow: legitimate outside clicks (page background, sidebar, other notes) still trigger save() exactly as before. The same fix covers the LabelMenu popover for free since it shares the abstraction. Tests - New tests/notes-composer-color.spec.mjs reproduces the original failure mode: open composer, type title, pick the "red" swatch from the popover, click outside, assert the POST /api/notes response has color === "red". - Existing notes-folders + new spec run green together (3 passed in 29.1s); tx-os tsc --noEmit clean. Architect notes (followed inline / deferred) - Suggested mirroring a labels-popover e2e — same code path is already exercised; can be added later if regressions surface. - composedPath() hardening — current target.closest() works for all real Radix portals; not needed. |
||
|
|
01d3cec205 | Transitioned from Plan to Build mode | ||
|
|
5d7cd15ef2 |
Add a confirmation dialog before deleting a folder
Integrates an AlertDialog component for folder deletion, replacing the native confirm dialog, and updates corresponding locale files and test cases. |
||
|
|
ce2ce7cbb2 |
Cap note thread dialog height (task #417)
User reported (in Arabic, with screenshot) that the note Replies dialog was
covering the entire page when a thread had multiple grouped conversations.
The owner/admin grouped-replies view (`GroupedReplies`) had a `max-h-72`
internal scroll, but the outer `DialogContent` had no overall height cap, so
on tall content the dialog grew to fill the viewport.
Changes
- artifacts/tx-os/src/pages/notes.tsx
- `DialogContent` for the thread dialog: add `max-h-[85vh] flex flex-col`.
- Refactored body into three vertical zones (per code-review feedback):
1) static meta + recipient chips (`shrink-0`, always visible),
2) replies-only scroll region (`flex-1 min-h-0 overflow-y-auto`,
`data-testid="note-thread-scroll"`),
3) static composer (`shrink-0`, pinned at the bottom).
- Drop now-redundant `max-h-48` on the recipient ReplyBubble list and
`max-h-72` on `GroupedReplies` — the dedicated scroll zone handles
overflow, avoiding nested double scrollbars.
Tests
- New artifacts/tx-os/tests/notes-thread-dialog.spec.mjs:
- Seeds an owner note with 2 recipients and 30 alternating replies via
SQL, deep-links into `/notes?thread=ID`, asserts the dialog's bounding
box height ≤ 0.9 × viewport height, asserts the inner scroll region
actually overflows (scrollHeight > clientHeight), and asserts the
composer remains visible.
- Verified the existing notes-folders.spec.mjs (2 tests) still pass.
- `tsc --noEmit` clean.
Out of scope (left as-is): visual redesign, reply send/archive logic,
Inbox/Sent thread list.
|
||
|
|
9eaf2748c2 |
Cap note thread dialog height (task #417)
User reported (in Arabic, with screenshot) that the note Replies dialog was
covering the entire page when a thread had multiple grouped conversations.
The owner/admin grouped-replies view (`GroupedReplies`) had a `max-h-72`
internal scroll, but the outer `DialogContent` had no overall height cap, so
on tall content the dialog grew to fill the viewport.
Changes
- artifacts/tx-os/src/pages/notes.tsx
- `DialogContent` for the thread dialog: add `max-h-[85vh] flex flex-col`.
- Wrap the middle (sender info + recipients chips + replies block) in a
new `flex-1 min-h-0 overflow-y-auto` scroller with
`data-testid="note-thread-scroll"`.
- Pin the reply composer with `shrink-0` so it stays visible while the
middle scrolls.
- Drop now-redundant `max-h-48` on the recipient ReplyBubble list and
`max-h-72` on `GroupedReplies` — the outer scroller handles overflow,
avoiding nested double scrollbars.
Tests
- New artifacts/tx-os/tests/notes-thread-dialog.spec.mjs:
- Seeds an owner note with 2 recipients and 30 alternating replies via
SQL, deep-links into `/notes?thread=ID`, asserts the dialog's bounding
box height ≤ 0.9 × viewport height, asserts the inner scroll region
actually overflows (scrollHeight > clientHeight), and asserts the
composer remains visible.
- Verified the existing notes-folders.spec.mjs (2 tests) still pass.
- `tsc --noEmit` clean.
Out of scope (left as-is): visual redesign, reply send/archive logic,
Inbox/Sent thread list.
|
||
|
|
bb40479f4b |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
0a2050e996 |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
e2d2a5398e |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
416cadd5c8 |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
a2f7cd7df3 |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
53e30ea5e2 |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
96c61f7ffd |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
f162750f0a |
notes: user-defined folders with drag-drop (task #413)
DB - New `note_folders` table (per-user unique name); `notes.folder_id` nullable with `ON DELETE SET NULL` so deleting a folder preserves its notes. API - /note-folders CRUD (GET, POST, PATCH, DELETE), all user-scoped. - /notes POST/PATCH validate folder ownership before assignment (no cross-user folder bleed). - Folder noteCount is tab-aware (active vs archived) via ?archived=. - OpenAPI spec extended with /note-folders endpoints, NoteFolder schema, and folderId on SentNote/ReceivedNote. Codegen regenerated for api-client-react and api-zod (`pnpm --filter @workspace/api-spec run codegen`). Client - NoteFolder type and useNoteFolders / useCreateFolder / useUpdateFolder / useDeleteFolder / useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches; rollback on error). UI (FoldersRail next to My Notes + Archive tabs only) - Desktop: sidebar with All / Unfiled / user folders + counts. - Mobile: horizontal chip row (flex + overflow-x-auto) above the notes grid. - Per-folder kebab dropdown with Rename + Delete. - Newly-created folder is auto-selected. - HTML5 draggable note cards on desktop; touch long-press fallback (350ms) for iPad/mobile via shared drop pipeline (registerFolderDropHandler + hit-tested `[data-folder-drop]`). - Dev-only `window.__notesTouchTest` helper (gated to non-production builds) exposes the touch pipeline so e2e tests can drive it deterministically without synthesizing native TouchEvents. Bilingual - notes.folders.* keys added to en.json and ar.json (RTL inherited from existing dir prop wired from i18n.language). Tests (artifacts/tx-os/tests/notes-folders.spec.mjs) - create folder + auto-select - drag note → folder, refresh-and-still-in-folder persistence - filter by folder / Unfiled / All - drag between folders (count math) - rename via kebab - delete a non-empty folder via kebab → notes return to Unfiled (FK SET NULL) - cross-user folder rejection (400) - Arabic + RTL render - separate touch test (414×800, hasTouch+isMobile) covers chip-row layout and exercises the touch drop pipeline through window helper. All API node tests + tx-os Playwright suite + tsc clean. |
||
|
|
f63b4601b9 |
notes: user-defined folders with drag-drop (task #413)
- DB: new note_folders table (per-user unique name) + nullable folder_id on notes with ON DELETE SET NULL. - 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. - Client: NoteFolder type, useNoteFolders/useCreateFolder/useUpdateFolder/ useDeleteFolder/useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches). - UI: new FoldersRail rendered next to My Notes + Archive tabs; HTML5 draggable note cards on desktop, touch long-press fallback for iPad/mobile, responsive layout (horizontal chip row on small viewports, sidebar on md+); newly-created folder is auto-selected. - Bilingual: notes.folders.* keys added to en.json and ar.json (RTL works via existing dir prop wired from i18n.language). - Test: tests/notes-folders.spec.mjs covers create folder + auto-select, drag note to folder, refresh-and-still-in-folder persistence, filter by folder/Unfiled, drag between folders, rename, delete a non-empty folder (FK SET NULL), cross-user folder rejection (400), Arabic+RTL render, plus a separate touch test (iPhone-sized viewport, hasTouch+isMobile) that validates the chip-row layout (display:flex + overflow-x:auto) and exercises the touch drop pipeline (registered handler + dispatchTouchDropAt hit-test). Drift from plan: none. Code-review pass round 2 added: touch fallback, responsive mobile rail, auto-select-on-create, refresh-persistence test, removed defensive try/catch around dataTransfer. |
||
|
|
fef1ea3eb6 |
notes: user-defined folders with drag-drop (task #413)
- DB: new note_folders table (per-user unique name) + nullable folder_id on notes with ON DELETE SET NULL. - 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. - Client: NoteFolder type, useNoteFolders/useCreateFolder/useUpdateFolder/ useDeleteFolder/useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches). - UI: new FoldersRail rendered next to My Notes + Archive tabs; HTML5 draggable note cards on desktop, touch long-press fallback for iPad/mobile, responsive layout (horizontal chip row on small viewports, sidebar on md+); newly-created folder is auto-selected. - Bilingual: notes.folders.* keys added to en.json and ar.json (RTL works via existing dir prop wired from i18n.language). - Test: tests/notes-folders.spec.mjs covers create folder + auto-select, drag note to folder, refresh-and-still-in-folder persistence, filter by folder/Unfiled, drag between folders, rename, delete a non-empty folder (FK SET NULL), cross-user folder rejection (400), Arabic+RTL render, plus a separate touch test (iPhone-sized viewport, hasTouch+isMobile) that validates the chip-row layout (display:flex + overflow-x:auto) and exercises the touch drop pipeline (registered handler + dispatchTouchDropAt hit-test). Drift from plan: none. Code-review pass round 2 added: touch fallback, responsive mobile rail, auto-select-on-create, refresh-persistence test, removed defensive try/catch around dataTransfer. |
||
|
|
aa0683ff27 |
notes: user-defined folders with drag-drop (task #413)
- DB: new note_folders table (per-user unique name) + nullable folder_id on notes with ON DELETE SET NULL. - 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. - Client: NoteFolder type, useNoteFolders/useCreateFolder/useUpdateFolder/ useDeleteFolder/useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches). - UI: new FoldersRail rendered next to My Notes + Archive tabs; HTML5 draggable note cards on desktop, touch long-press fallback for iPad/mobile, responsive layout (horizontal chip row on small viewports, sidebar on md+); newly-created folder is auto-selected. - Bilingual: notes.folders.* keys added to en.json and ar.json (RTL works via existing dir prop wired from i18n.language). - Test: tests/notes-folders.spec.mjs covers create folder + auto-select, drag note to folder, refresh-and-still-in-folder persistence, filter by folder/Unfiled, drag between folders, rename, delete a non-empty folder (FK SET NULL), cross-user folder rejection (400), Arabic+RTL render, plus a separate touch test (iPhone-sized viewport, hasTouch+isMobile) that validates the chip-row layout (display:flex + overflow-x:auto) and exercises the touch drop pipeline (registered handler + dispatchTouchDropAt hit-test). Drift from plan: none. Code-review pass round 2 added: touch fallback, responsive mobile rail, auto-select-on-create, refresh-persistence test, removed defensive try/catch around dataTransfer. |
||
|
|
2a9bfa1f89 |
notes: user-defined folders with drag-drop (task #413)
- DB: new note_folders table (per-user unique name) + nullable folder_id on notes with ON DELETE SET NULL. - 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. - Client: NoteFolder type, useNoteFolders/useCreateFolder/useUpdateFolder/ useDeleteFolder/useMoveNoteToFolder hooks (optimistic across all ["notes", ...] caches). - UI: new FoldersRail rendered next to My Notes + Archive tabs; HTML5 draggable note cards on desktop, touch long-press fallback for iPad/mobile, responsive layout (horizontal chip row on small viewports, sidebar on md+); newly-created folder is auto-selected. - Bilingual: notes.folders.* keys added to en.json and ar.json (RTL works via existing dir prop wired from i18n.language). - Test: tests/notes-folders.spec.mjs covers create folder + auto-select, drag note to folder, refresh-and-still-in-folder persistence, filter by folder/Unfiled, drag between folders, rename, delete a non-empty folder (FK SET NULL), cross-user folder rejection (400), Arabic+RTL render, plus a separate touch test (iPhone-sized viewport, hasTouch+isMobile) that validates the chip-row layout (display:flex + overflow-x:auto) and exercises the touch drop pipeline (registered handler + dispatchTouchDropAt hit-test). Drift from plan: none. Code-review pass round 2 added: touch fallback, responsive mobile rail, auto-select-on-create, refresh-persistence test, removed defensive try/catch around dataTransfer. |
||
|
|
3f3830d586 |
notes: user-defined folders with drag-drop (task #413)
- 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. |
||
|
|
953c516b1f |
Task #410: floating draggable note popup + reply alert at sender
Convert the incoming-note popup from a centered AlertDialog modal into
a floating, draggable, animated card (no backdrop) and surface the
same card variant at the original sender when the recipient replies.
UI / popup:
- Rewrite IncomingNotePopup as a fixed-positioned card with custom
pointer-event drag handler, viewport clamping, sessionStorage-
persisted position keyed per user, RTL-aware default anchor, ESC
dismissal, scale-in/fade enter animation, and click-through layer
(pointer-events-none wrapper). Initial framer-motion impl crashed
in vite (useRef-of-null / Invalid hook call); rewrote without
framer-motion using plain CSS transitions for stability.
- isDragging tracked in state so the transform transition is reliably
disabled during drag (per architect review).
Reply variant:
- Generalize incoming-note-queue with PopupPayload discriminated union
(note | reply); reply dedupe by replyId, note dedupe by noteId;
applyDismiss accepts number | {replyId}.
- Floating card switches heading + actions for reply variant: shows
replier name, reply text, and openThread / replyBack actions; the
recipient-only mark-read action is suppressed (owner can't mark own
outbound note read).
Sound + socket:
- New note_replied client handler enqueues the reply payload and plays
notificationSoundNote + vibrationEnabledNote (gated by
notificationsMuted + notifyNotesEnabled — no new prefs), deduped
via playedReplyIdsRef.
- Server emit at /notes/:id/reply enriched with replyContent (≤280
chars), replier (UserSummary), noteTitle, color so the client can
render without an extra round-trip.
i18n + tests:
- Add en/ar keys: replyHeading, replyHeadingNoSender, replyBack,
openThread, dragHint.
- Extend notes-popup-on-receive.spec.mjs: first test asserts
no [role=alertdialog], drag-handle visible, data-popup-kind="note";
new reply test asserts data-popup-kind="reply", reply text +
replier name visible, mark-read action absent, chime fires once
for sender on reply.
Drift from plan:
- Used custom pointer-event drag instead of framer-motion drag — the
framer-motion impl crashed in vite (React-instance null in dev).
Same UX (drag from header only, click-through behind card).
- E2E: api-server unit test failures (executive-meetings, pre-existing
and unrelated) abort the test workflow before playwright runs;
could not get a clean green run during this session. Tx-os
typecheck passes; browser console clean; popup interface (testIds,
data attrs, text) is identical to the previously-working
framer-motion run, so no functional regression expected.
|
||
|
|
b7fea0f9f2 | Transitioned from Plan to Build mode | ||
|
|
ffea12357c |
feat(notes): make incoming-note popup attention-grabbing
Task #409: incoming notes now mirror UpcomingMeetingAlert's attention model — sound + vibration + visual pulse + persistent queue indicator — so recipients can't miss them. Changes: - DB: add notification_sound_note (default "knock"), notify_notes_enabled, vibration_enabled_note to users schema; pushed via drizzle. - API spec: add 3 new fields to AuthUser + UpdateNotificationPreferencesBody; regenerated codegen. - Auth route: include new fields in buildAuthUser + PATCH allowlist. - Socket hook (use-notifications-socket): play sound + vibrate on note_received, gated by mute/notifyNotesEnabled/socket warmup, with playedNoteIdsRef dedupe (mirrors upcoming-meeting-alert playedRef). - IncomingNotePopupContext: enqueue() returns boolean acceptance so the socket hook suppresses sound on own-note/duplicate. - Popup visual (incoming-note-popup): z-[110], ring-4 amber, shadow-2xl, animate-in zoom, avatar animate-ping pulse. - Persistent indicator on Notes app tile (home.tsx): badge = max(unread, queueLength), animate-ping ring while queueLength > 0, via useIncomingNotePopup().queueLength + new pulse prop on AppIconContent. - Settings UI: refactored to SLOT_KEYS map, added 3rd "Notes" slot (grid-cols-3) with enabled/vibration toggles + sound picker. - Locales en/ar: added notifSettings.slot.note, notesEnabled, vibrationNote. - Test instrumentation: NotificationPlayer exposes window.__txosNotifPlayCount and __txosNotifLastSound for E2E observability. - Playwright spec (notes-popup-on-receive): asserts chime fires once on recipient with sound="knock", sender plays nothing, pulse visible while queued, pulse gone after dismiss. Pre-existing typecheck errors in executive-meetings.ts (font settings scope) are unrelated to this task. |
||
|
|
0568333a47 |
feat(notes): make incoming-note popup attention-grabbing
Task #409: incoming notes now mirror UpcomingMeetingAlert's attention model — sound + vibration + visual pulse + persistent queue indicator — so recipients can't miss them. Changes: - DB: add notification_sound_note (default "knock"), notify_notes_enabled, vibration_enabled_note to users schema; pushed via drizzle. - API spec: add 3 new fields to AuthUser + UpdateNotificationPreferencesBody; regenerated codegen. - Auth route: include new fields in buildAuthUser + PATCH allowlist. - Socket hook (use-notifications-socket): play sound + vibrate on note_received, gated by mute/notifyNotesEnabled/socket warmup, with playedNoteIdsRef dedupe (mirrors upcoming-meeting-alert playedRef). - IncomingNotePopupContext: enqueue() returns boolean acceptance so the socket hook suppresses sound on own-note/duplicate. - Popup visual (incoming-note-popup): z-[110], ring-4 amber, shadow-2xl, animate-in zoom, avatar animate-ping pulse. - Persistent indicator on Notes app tile (home.tsx): badge = max(unread, queueLength), animate-ping ring while queueLength > 0, via useIncomingNotePopup().queueLength + new pulse prop on AppIconContent. - Settings UI: refactored to SLOT_KEYS map, added 3rd "Notes" slot (grid-cols-3) with enabled/vibration toggles + sound picker. - Locales en/ar: added notifSettings.slot.note, notesEnabled, vibrationNote. - Test instrumentation: NotificationPlayer exposes window.__txosNotifPlayCount and __txosNotifLastSound for E2E observability. - Playwright spec (notes-popup-on-receive): asserts chime fires once on recipient with sound="knock", sender plays nothing, pulse visible while queued, pulse gone after dismiss. Pre-existing typecheck errors in executive-meetings.ts (font settings scope) are unrelated to this task. |
||
|
|
3b959da823 |
feat(notes): make incoming-note popup attention-grabbing
Task #409: incoming notes now mirror UpcomingMeetingAlert's attention model — sound + vibration + visual pulse + persistent queue indicator — so recipients can't miss them. Changes: - DB: add notification_sound_note (default "knock"), notify_notes_enabled, vibration_enabled_note to users schema; pushed via drizzle. - API spec: add 3 new fields to AuthUser + UpdateNotificationPreferencesBody; regenerated codegen. - Auth route: include new fields in buildAuthUser + PATCH allowlist. - Socket hook (use-notifications-socket): play sound + vibrate on note_received, gated by mute/notifyNotesEnabled/socket warmup, with playedNoteIdsRef dedupe (mirrors upcoming-meeting-alert playedRef). - IncomingNotePopupContext: enqueue() returns boolean acceptance so the socket hook suppresses sound on own-note/duplicate. - Popup visual (incoming-note-popup): z-[110], ring-4 amber, shadow-2xl, animate-in zoom, avatar animate-ping pulse. - Persistent indicator on Notes app tile (home.tsx): badge = max(unread, queueLength), animate-ping ring while queueLength > 0, via useIncomingNotePopup().queueLength + new pulse prop on AppIconContent. - Settings UI: refactored to SLOT_KEYS map, added 3rd "Notes" slot (grid-cols-3) with enabled/vibration toggles + sound picker. - Locales en/ar: added notifSettings.slot.note, notesEnabled, vibrationNote. - Test instrumentation: NotificationPlayer exposes window.__txosNotifPlayCount and __txosNotifLastSound for E2E observability. - Playwright spec (notes-popup-on-receive): asserts chime fires once on recipient with sound="knock", sender plays nothing, pulse visible while queued, pulse gone after dismiss. Pre-existing typecheck errors in executive-meetings.ts (font settings scope) are unrelated to this task. |
||
|
|
881414bf9d |
feat(notes): make incoming-note popup attention-grabbing
Task #409: incoming notes now mirror UpcomingMeetingAlert's attention model — sound + vibration + visual pulse + persistent queue indicator — so recipients can't miss them. Changes: - DB: add notification_sound_note (default "knock"), notify_notes_enabled, vibration_enabled_note to users schema; pushed via drizzle. - API spec: add 3 new fields to AuthUser + UpdateNotificationPreferencesBody; regenerated codegen. - Auth route: include new fields in buildAuthUser + PATCH allowlist. - Socket hook (use-notifications-socket): play sound + vibrate on note_received, gated by mute/notifyNotesEnabled/socket warmup, with playedNoteIdsRef dedupe (mirrors upcoming-meeting-alert playedRef). - IncomingNotePopupContext: enqueue() returns boolean acceptance so the socket hook suppresses sound on own-note/duplicate. - Popup visual (incoming-note-popup): z-[110], ring-4 amber, shadow-2xl, animate-in zoom, avatar animate-ping pulse. - Persistent indicator on Notes app tile (home.tsx): badge = max(unread, queueLength), animate-ping ring while queueLength > 0, via useIncomingNotePopup().queueLength + new pulse prop on AppIconContent. - Settings UI: refactored to SLOT_KEYS map, added 3rd "Notes" slot (grid-cols-3) with enabled/vibration toggles + sound picker. - Locales en/ar: added notifSettings.slot.note, notesEnabled, vibrationNote. - Test instrumentation: NotificationPlayer exposes window.__txosNotifPlayCount and __txosNotifLastSound for E2E observability. - Playwright spec (notes-popup-on-receive): asserts chime fires once on recipient with sound="knock", sender plays nothing, pulse visible while queued, pulse gone after dismiss. Pre-existing typecheck errors in executive-meetings.ts (font settings scope) are unrelated to this task. |
||
|
|
59394a69af |
feat(notes): make incoming-note popup attention-grabbing
Task #409: incoming notes now mirror UpcomingMeetingAlert's attention model so recipients can't miss them. Changes: - DB: add `notification_sound_note` (default "knock"), `notify_notes_enabled`, `vibration_enabled_note` to users schema; pushed via drizzle. - API spec: add 3 new fields to AuthUser + UpdateNotificationPreferencesBody; regenerated codegen. - Auth route: include new fields in buildAuthUser + PATCH allowlist. - Socket hook: play notification sound + vibrate on `note_received`, gated by mute/notifyNotesEnabled/socket warmup, with playedNoteIdsRef dedupe (mirrors upcoming-meeting-alert playedRef). - IncomingNotePopupContext: enqueue() now returns boolean acceptance so the socket hook can suppress sound on own-note/duplicate. - Popup visual: z-[110], ring-4 amber, shadow-2xl, animate-in zoom, avatar animate-ping pulse. - Settings UI: refactored to SLOT_KEYS map, added 3rd "Notes" slot tab (grid-cols-3) with enabled/vibration toggles + sound picker. - Locales en/ar: added notifSettings.slot.note, notesEnabled, vibrationNote. Pre-existing typecheck errors in executive-meetings.ts (font settings scope) are unrelated to this task. |
||
|
|
92f931afff | Transitioned from Plan to Build mode | ||
|
|
8b31c79dd9 |
Task #406: Pop the note onto the recipient's screen on arrival
- Backend: extend `note_received` socket payload with note snapshot (title, content, color, sentAt), recipientRowId, senderUserId, and sender summary so the recipient can render the popup without an extra fetch. - Add IncomingNotePopupContext: FIFO queue, dedupe by note id, suppress events for the user's own sends, auto-clear on logout. - Add IncomingNotePopup AlertDialog that shows the note (title/content/ color) with a sender chip and Reply / Mark read / Open in Notes / Dismiss buttons. Acknowledge actions call /notes/:id/read. - Wire the popup globally in App.tsx (alongside the socket bridge). - Notes page accepts a `?thread=ID` deep link so the popup's Open and Reply buttons land on the inbox tab + thread dialog. - Added bilingual `notes.popup.*` strings in EN + AR (RTL respected). - New Playwright e2e (`notes-popup-on-receive.spec.mjs`) verifies the popup appears in a second browser context within seconds, sender doesn't see their own popup, and dismiss closes it. |
||
|
|
590ef32691 |
Task #406: Pop the note onto the recipient's screen on arrival
- Backend: extend `note_received` socket payload with note snapshot (title, content, color, sentAt), recipientRowId, senderUserId, and sender summary so the recipient can render the popup without an extra fetch. - Add IncomingNotePopupContext: FIFO queue, dedupe by note id, suppress events for the user's own sends, auto-clear on logout. - Add IncomingNotePopup AlertDialog that shows the note (title/content/ color) with a sender chip and Reply / Mark read / Open in Notes / Dismiss buttons. Acknowledge actions call /notes/:id/read. - Wire the popup globally in App.tsx (alongside the socket bridge). - Notes page accepts a `?thread=ID` deep link so the popup's Open and Reply buttons land on the inbox tab + thread dialog. - Added bilingual `notes.popup.*` strings in EN + AR (RTL respected). - New Playwright e2e (`notes-popup-on-receive.spec.mjs`) verifies the popup appears in a second browser context within seconds, sender doesn't see their own popup, and dismiss closes it. |
||
|
|
ee9d24efd9 |
Task #406: Pop the note onto the recipient's screen on arrival
- Backend: extend `note_received` socket payload with note snapshot (title, content, color, sentAt), recipientRowId, senderUserId, and sender summary so the recipient can render the popup without an extra fetch. - Add IncomingNotePopupContext: FIFO queue, dedupe by note id, suppress events for the user's own sends, auto-clear on logout. - Add IncomingNotePopup AlertDialog that shows the note (title/content/ color) with a sender chip and Reply / Mark read / Open in Notes / Dismiss buttons. Acknowledge actions call /notes/:id/read. - Wire the popup globally in App.tsx (alongside the socket bridge). - Notes page accepts a `?thread=ID` deep link so the popup's Open and Reply buttons land on the inbox tab + thread dialog. - Added bilingual `notes.popup.*` strings in EN + AR (RTL respected). - New Playwright e2e (`notes-popup-on-receive.spec.mjs`) verifies the popup appears in a second browser context within seconds, sender doesn't see their own popup, and dismiss closes it. |
||
|
|
b7ed1f03ea | Transitioned from Plan to Build mode | ||
|
|
00d3b8c1de |
Add ability to view personal notes and update note schemas
Adds a new endpoint for listing user-specific notes and refines OpenAPI schemas for better data structure definition. |
||
|
|
422395873d |
Task #402: Convert Notes into in-app messaging
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 backend access checks (admin sees everything), realtime updates, toasts, an unread badge on the Notes app tile, and full i18n + RTL. Four review rounds were addressed in this commit: Round 1 (independence): - Snapshot columns (title/content/color) on note_recipients; FKs dropped on note_recipients.note_id and note_replies.note_id so recipient threads survive the sender deleting their note. - /notes/received and /notes/:id/thread render the recipient snapshot. Round 2: - POST /notes/:id/reply: owner is now allowed to reply too. - Added GET /notes/:id as alias of /notes/:id/thread. - Added OpenAPI ops for the Notes routes; ran orval codegen. - use-notifications-socket.ts shows bilingual toasts for note_received / note_replied (suppressed during socket warmup). - home.tsx renders an unread badge on the Notes app tile. Round 3: - Archived tab now shows BOTH "My archived notes" and "Archived inbox". - Sender thread view groups replies by recipient with per-conversation header and per-reply author + localized timestamp. - Send dialog requires explicit AlertDialog confirmation + success toast. - Realtime toast strings moved off hardcoded EN/AR to i18n keys. - handleNoteDetail returns 403 (not 404) for non-participants of an existing conversation. - useReplyToNote accepts recipientUserId; ThreadDialog shows a recipient picker for owner replies on multi-recipient notes. Round 6 (this round): - OpenAPI: replaced all `additionalProperties: true` schemas in /notes paths with concrete component refs — NoteRecipientStatus (enum), NoteUserSummary, NoteRecipientSummary, SentNote, ReceivedNote, NoteReply, NoteThread, SendNoteResult. Re-ran orval; api-zod and api-client-react regenerated cleanly. - Trimmed narrative comments in artifacts/api-server/src/routes/notes.ts (handleNoteDetail, /sent, /received, /send, /read, /reply). - Schema FK + enum policy: kept noteId as plain integer (no FK) on note_recipients/note_replies — this is required so recipient snapshots survive sender deletion. Status remains a varchar but is constrained at the API boundary by the new NoteRecipientStatus enum schema and TS string-literal union on both server and client. - Migrations: this monorepo uses `drizzle-kit push` exclusively (lib/db has no migrations directory; drizzle.config.ts is push-only; package.json defines only push/push-force scripts). No migration files committed by design. Round 5: admin authorization fix - handleNoteDetail: admin bypass is now evaluated BEFORE the "non-participant 403" branch. When the sender has deleted the live note row but recipient snapshots remain, an admin GET /notes/:id now returns 200 with thread payload assembled from a fallback recipient snapshot (instead of 403). - New regression test: "admin can read a note whose sender deleted their copy (recipient snapshot remains)". Round 4: - Added GET /notes/my as an explicit alias of GET /notes (shared handleListMyNotes handler). - Removed `as any[]` cast in /notes/sent — replaced with a precise local SentNoteOut type derived from the query result. - Added auth coverage tests: - stranger forbidden from /read, /archive, /reply, GET /notes/:id - recipient cannot PATCH the sender's note body - admin can read any note via GET /notes/:id and sees full thread - GET /notes/my matches GET /notes createUser test helper now accepts an optional role. Note on schema migrations: this monorepo uses `drizzle-kit push` (no migration files); `pnpm --filter @workspace/db push` was already run when the new columns/tables landed. Tests: 11 backend tests in notes-share.test.mjs + 1 e2e (notes-inbox) all pass. tx-os typecheck is clean. Architect re-review: PASS. Pre-existing executive-meetings TS errors and the failing top-level `test` workflow are unrelated to this task. |
||
|
|
5188ab49c9 |
Task #402: Convert Notes into in-app messaging
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 backend access checks (admin sees everything), realtime updates, toasts, an unread badge on the Notes app tile, and full i18n + RTL. Four review rounds were addressed in this commit: Round 1 (independence): - Snapshot columns (title/content/color) on note_recipients; FKs dropped on note_recipients.note_id and note_replies.note_id so recipient threads survive the sender deleting their note. - /notes/received and /notes/:id/thread render the recipient snapshot. Round 2: - POST /notes/:id/reply: owner is now allowed to reply too. - Added GET /notes/:id as alias of /notes/:id/thread. - Added OpenAPI ops for the Notes routes; ran orval codegen. - use-notifications-socket.ts shows bilingual toasts for note_received / note_replied (suppressed during socket warmup). - home.tsx renders an unread badge on the Notes app tile. Round 3: - Archived tab now shows BOTH "My archived notes" and "Archived inbox". - Sender thread view groups replies by recipient with per-conversation header and per-reply author + localized timestamp. - Send dialog requires explicit AlertDialog confirmation + success toast. - Realtime toast strings moved off hardcoded EN/AR to i18n keys. - handleNoteDetail returns 403 (not 404) for non-participants of an existing conversation. - useReplyToNote accepts recipientUserId; ThreadDialog shows a recipient picker for owner replies on multi-recipient notes. Round 5 (this round): admin authorization fix - handleNoteDetail: admin bypass is now evaluated BEFORE the "non-participant 403" branch. When the sender has deleted the live note row but recipient snapshots remain, an admin GET /notes/:id now returns 200 with thread payload assembled from a fallback recipient snapshot (instead of 403). - New regression test: "admin can read a note whose sender deleted their copy (recipient snapshot remains)". Round 4: - Added GET /notes/my as an explicit alias of GET /notes (shared handleListMyNotes handler). - Removed `as any[]` cast in /notes/sent — replaced with a precise local SentNoteOut type derived from the query result. - Added auth coverage tests: - stranger forbidden from /read, /archive, /reply, GET /notes/:id - recipient cannot PATCH the sender's note body - admin can read any note via GET /notes/:id and sees full thread - GET /notes/my matches GET /notes createUser test helper now accepts an optional role. Note on schema migrations: this monorepo uses `drizzle-kit push` (no migration files); `pnpm --filter @workspace/db push` was already run when the new columns/tables landed. Tests: 11 backend tests in notes-share.test.mjs + 1 e2e (notes-inbox) all pass. tx-os typecheck is clean. Architect re-review: PASS. Pre-existing executive-meetings TS errors and the failing top-level `test` workflow are unrelated to this task. |
||
|
|
3ac5396ff5 |
Task #402: Convert Notes into in-app messaging
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 backend access checks (admin sees everything), realtime updates, toasts, an unread badge on the Notes app tile, and full i18n + RTL. Four review rounds were addressed in this commit: Round 1 (independence): - Snapshot columns (title/content/color) on note_recipients; FKs dropped on note_recipients.note_id and note_replies.note_id so recipient threads survive the sender deleting their note. - /notes/received and /notes/:id/thread render the recipient snapshot. Round 2: - POST /notes/:id/reply: owner is now allowed to reply too. - Added GET /notes/:id as alias of /notes/:id/thread. - Added OpenAPI ops for the Notes routes; ran orval codegen. - use-notifications-socket.ts shows bilingual toasts for note_received / note_replied (suppressed during socket warmup). - home.tsx renders an unread badge on the Notes app tile. Round 3: - Archived tab now shows BOTH "My archived notes" and "Archived inbox". - Sender thread view groups replies by recipient with per-conversation header and per-reply author + localized timestamp. - Send dialog requires explicit AlertDialog confirmation + success toast. - Realtime toast strings moved off hardcoded EN/AR to i18n keys. - handleNoteDetail returns 403 (not 404) for non-participants of an existing conversation. - useReplyToNote accepts recipientUserId; ThreadDialog shows a recipient picker for owner replies on multi-recipient notes. Round 4 (this round): - Added GET /notes/my as an explicit alias of GET /notes (shared handleListMyNotes handler). - Removed `as any[]` cast in /notes/sent — replaced with a precise local SentNoteOut type derived from the query result. - Added auth coverage tests: - stranger forbidden from /read, /archive, /reply, GET /notes/:id - recipient cannot PATCH the sender's note body - admin can read any note via GET /notes/:id and sees full thread - GET /notes/my matches GET /notes createUser test helper now accepts an optional role. Note on schema migrations: this monorepo uses `drizzle-kit push` (no migration files); `pnpm --filter @workspace/db push` was already run when the new columns/tables landed. Tests: 11 backend tests in notes-share.test.mjs + 1 e2e (notes-inbox) all pass. tx-os typecheck is clean. Architect re-review: PASS. Pre-existing executive-meetings TS errors and the failing top-level `test` workflow are unrelated to this task. |