Update project documentation and code comments to remove platform-specific references
Refactor documentation files and code comments to remove references to Replit, specific task numbers, and other platform-specific identifiers. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: fa18e5d4-a810-4bd5-8cde-2a60d64d9e3f Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Unit tests for the incoming-note popup queue (Task #406).
|
||||
// Unit tests for the incoming-note popup queue.
|
||||
//
|
||||
// The queue is the data structure behind the centered modal that
|
||||
// appears on the recipient's screen the moment a `note_received` socket
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Unit tests for parseTypedTime — the human-input time parser used by
|
||||
// the executive-meetings inline time editor. Covers every shape listed
|
||||
// in task #308's "Done looks like" plus the obvious invalid edges.
|
||||
// in's "Done looks like" plus the obvious invalid edges.
|
||||
//
|
||||
// Runs with Node 24's built-in test runner + native TypeScript support.
|
||||
import { test } from "node:test";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Unit tests for the 12-hour-picker helpers (task #315). Covers the
|
||||
// Unit tests for the 12-hour-picker helpers. Covers the
|
||||
// AM/PM disambiguation rules that fix the "1:15 silently saved as AM"
|
||||
// bug — the picker MUST require an explicit toggle for any 1–12 hour
|
||||
// without a typed AM/PM marker, and MUST accept any 13–23 hour or
|
||||
|
||||
@@ -31,7 +31,7 @@ type Props = {
|
||||
|
||||
/**
|
||||
* Inner content for the clock-style picker, extracted so the unified
|
||||
* Settings panel (Task #527) can render the same controls without
|
||||
* Settings panel can render the same controls without
|
||||
* re-implementing the visibility toggles, hour-format switch, and
|
||||
* style list. `ClockStylePicker` below remains as a standalone
|
||||
* popover wrapper for any caller that wants a single icon-button.
|
||||
|
||||
@@ -441,7 +441,7 @@ export function UpcomingMeetingAlert() {
|
||||
setExpanded(false);
|
||||
}, [eligibleId]);
|
||||
|
||||
// Task #438: scale-in + fade entrance that mirrors the incoming-note
|
||||
// scale-in + fade entrance that mirrors the incoming-note
|
||||
// popup, so the alert feels like the same family of "important
|
||||
// floating thing arrived". Toggled false → true on the next paint of
|
||||
// each new eligible meeting so the CSS transition has a starting
|
||||
|
||||
@@ -126,7 +126,7 @@ interface Props {
|
||||
mode?: "rail" | "chips-only";
|
||||
// Extra classes appended to the root <aside>. Used by the Notes page
|
||||
// to control flex `order-*` so the composer stacks above the rail on
|
||||
// narrow widths (Task #514).
|
||||
// narrow widths.
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ export function FoldersRail({
|
||||
</span>
|
||||
{/* Permission badge so the recipient can tell at a
|
||||
glance whether they can edit this folder's notes
|
||||
(Task #454). */}
|
||||
. */}
|
||||
<span
|
||||
className={`shrink-0 inline-flex items-center justify-center rounded px-1 py-0.5 text-[9px] font-medium uppercase tracking-wide ${
|
||||
sf.myPermission === "edit"
|
||||
|
||||
@@ -112,7 +112,7 @@ function clampToViewport(pos: StoredPos): StoredPos {
|
||||
* the checkboxes are not interactive (toggling is reserved for the
|
||||
* full Notes page after "Open note").
|
||||
*/
|
||||
// Task #438: collaborative checklist render. Owners + active recipients
|
||||
// collaborative checklist render. Owners + active recipients
|
||||
// can toggle items right from the popup; the change persists server-
|
||||
// side and fans out to other audience members via socket. We stop
|
||||
// pointer/click propagation so toggling a checkbox doesn't bubble up to
|
||||
@@ -246,7 +246,7 @@ export function IncomingNotePopup() {
|
||||
|
||||
// Inline reply composer state. When `composerOpen` is true the action
|
||||
// row is replaced with a textarea + Send/Cancel so the user can reply
|
||||
// without navigating away from the current page (Task #515).
|
||||
// without navigating away from the current page.
|
||||
const [composerOpen, setComposerOpen] = useState(false);
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [justSent, setJustSent] = useState(false);
|
||||
@@ -388,7 +388,7 @@ export function IncomingNotePopup() {
|
||||
|
||||
if (!current) return null;
|
||||
|
||||
// Task #468/#470: derive accent classes from the note's own color so
|
||||
///#470: derive accent classes from the note's own color so
|
||||
// the popup looks like an extension of the note (pink note → pink
|
||||
// ping + pink avatar tint, etc.) instead of the previous fixed amber
|
||||
// chrome. The outer ring was removed in #470 — only the avatar bg,
|
||||
@@ -540,7 +540,7 @@ export function IncomingNotePopup() {
|
||||
: current.content;
|
||||
// Render the inner sticky-note as faithfully as the NoteCard does:
|
||||
// text body for "text" notes, interactive collaborative checklist for
|
||||
// "checklist" (Task #438).
|
||||
// "checklist".
|
||||
// Reply-variant popups always render the reply text as plain content.
|
||||
const isChecklistNote =
|
||||
!!notePayload &&
|
||||
@@ -663,7 +663,7 @@ export function IncomingNotePopup() {
|
||||
|
||||
{/* Action row — collapses into the inline reply composer
|
||||
when the user clicks "Reply" so they can respond without
|
||||
leaving the current page (Task #515). */}
|
||||
leaving the current page. */}
|
||||
<div className="px-4 py-3">
|
||||
{composerOpen ? (
|
||||
<div
|
||||
|
||||
@@ -244,7 +244,7 @@ export function QuickMuteButton() {
|
||||
|
||||
/**
|
||||
* Inner content of the notification settings popover, extracted so the
|
||||
* Settings panel (Task #527) can render the same form without
|
||||
* Settings panel can render the same form without
|
||||
* re-implementing the toggles, slot tabs, and sound picker. The
|
||||
* standalone `NotificationSettingsPicker` below wraps it in a popover
|
||||
* trigger + content; the unified Settings panel mounts this content
|
||||
|
||||
@@ -197,7 +197,7 @@ function GroupItem({
|
||||
* persistence + behaviour stays identical to the topbar buttons that
|
||||
* used to expose them.
|
||||
*
|
||||
* Task #529: groups are collapsible. Open/closed state is lifted up
|
||||
* groups are collapsible. Open/closed state is lifted up
|
||||
* to the SettingsPanel so it persists while the panel is closed and
|
||||
* re-opened in the same session. Default = all collapsed (compact
|
||||
* panel on first open).
|
||||
@@ -249,13 +249,13 @@ function SettingsPanelBody({
|
||||
* behaviour and accessibility stay consistent.
|
||||
* - Body composes the existing form components rather than
|
||||
* re-implementing them, preserving DB + localStorage persistence.
|
||||
* - Task #529: groups are collapsible (accordion); logout stays
|
||||
* -: groups are collapsible (accordion); logout stays
|
||||
* in the topbar so the gear panel only hosts preferences.
|
||||
*/
|
||||
export function SettingsPanel() {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
// Default = all collapsed so the panel opens compact (Task #529).
|
||||
// Default = all collapsed so the panel opens compact.
|
||||
// State is lifted here so toggles persist while the panel is
|
||||
// dismissed and re-opened in the same session.
|
||||
const [openGroups, setOpenGroups] = useState<GroupId[]>([]);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// 12-hour time picker for the executive-meetings editor (task #315).
|
||||
// 12-hour time picker for the executive-meetings editor.
|
||||
//
|
||||
// Renders THREE controls per field, per the task spec:
|
||||
// 1. Hour input (1–12, but also accepts a full time as a power-user
|
||||
@@ -23,7 +23,7 @@
|
||||
// parent reads the latest draft via the imperative `commit()`
|
||||
// handle when the user presses Enter or blurs the wrapper, so
|
||||
// a save that races React state batching still sees the right
|
||||
// value (matches the belt-and-braces pattern task #308 set up
|
||||
// value (matches the belt-and-braces pattern set up
|
||||
// against the prior text input).
|
||||
|
||||
import {
|
||||
|
||||
@@ -38,7 +38,7 @@ interface IncomingNotePopupContextValue {
|
||||
) => boolean;
|
||||
dismiss: (target: number | { replyId: number }) => void;
|
||||
/**
|
||||
* Task #438: patch the checklist items of any queued popup payload
|
||||
* patch the checklist items of any queued popup payload
|
||||
* for `noteId` so an open popup re-renders against the freshest
|
||||
* shared state when another collaborator toggles an item.
|
||||
*/
|
||||
|
||||
@@ -301,7 +301,7 @@ export function useNotificationsSocket() {
|
||||
invalidate(["notes"]);
|
||||
});
|
||||
|
||||
// Task #438: a collaborator (owner or another recipient) toggled a
|
||||
// a collaborator (owner or another recipient) toggled a
|
||||
// checklist item — refetch so this user's open thread / popup /
|
||||
// cards reflect the shared state. Server already updates the
|
||||
// emitter optimistically, so it never receives its own echo. We
|
||||
@@ -335,7 +335,7 @@ export function useNotificationsSocket() {
|
||||
if (typeof payload?.folderId === "number") {
|
||||
invalidate(["notes", "shared-folder", payload.folderId]);
|
||||
}
|
||||
// Task #454: this event also fanouts to the OWNER + every other
|
||||
// this event also fanouts to the OWNER + every other
|
||||
// recipient when an editor mutates the folder's notes (create /
|
||||
// patch / delete / checklist toggle), so refresh the owner's
|
||||
// personal notes list and the folders rail badge counts too.
|
||||
@@ -343,7 +343,7 @@ export function useNotificationsSocket() {
|
||||
invalidate(["note-folders"]);
|
||||
},
|
||||
);
|
||||
// Task #454: dedicated event for permission-only flips on an
|
||||
// dedicated event for permission-only flips on an
|
||||
// existing share (view ↔ edit). Same invalidation as `shared`,
|
||||
// but kept distinct so the contract is explicit and so future UI
|
||||
// can react with a toast like "Your access changed to Edit".
|
||||
|
||||
@@ -421,7 +421,7 @@
|
||||
}
|
||||
|
||||
/*
|
||||
* Arabic dot/diacritic clipping fix for truncated text (Task #503 follow-up).
|
||||
* Arabic dot/diacritic clipping fix for truncated text ( follow-up).
|
||||
*
|
||||
* `.truncate` uses `overflow: hidden` + `white-space: nowrap`. With Tailwind's
|
||||
* default tight line-heights on small text (text-xs / text-sm / text-[11px]),
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface NoteFolder {
|
||||
// so the rail can render "by <name>" without an extra fetch.
|
||||
// Permission a recipient has on a shared folder. "view" is read-only
|
||||
// (default); "edit" lets them create / update / delete notes inside the
|
||||
// folder owner's namespace. Added in Task #454.
|
||||
// folder owner's namespace. Added in.
|
||||
export type FolderSharePermission = "view" | "edit";
|
||||
|
||||
export interface SharedFolder {
|
||||
@@ -223,7 +223,7 @@ export function useNoteLabels() {
|
||||
return useQuery({ queryKey: labelsKey, queryFn: () => req<NoteLabel[]>("/note-labels") });
|
||||
}
|
||||
|
||||
// Task #454: shared-folder editors mutate notes that live in someone
|
||||
// shared-folder editors mutate notes that live in someone
|
||||
// else's namespace. The server emits `note-folder-shared` to every
|
||||
// other viewer, but the actor is excluded from that fanout, so we
|
||||
// invalidate the shared-with-me list and folders rail locally too —
|
||||
@@ -260,7 +260,7 @@ export function useDeleteNote() {
|
||||
});
|
||||
}
|
||||
|
||||
// Task #472: bulk-delete the caller's own (sent) notes by id. Reuses the
|
||||
// bulk-delete the caller's own (sent) notes by id. Reuses the
|
||||
// existing per-note DELETE endpoint and fans out with bounded
|
||||
// concurrency (worker pool of `BULK_DELETE_CONCURRENCY`) so we never
|
||||
// open hundreds of sockets at once on large selections — each worker
|
||||
@@ -325,7 +325,7 @@ export function useMarkNoteRead() {
|
||||
});
|
||||
}
|
||||
|
||||
// Task #438: collaborative checklist item toggle. Anyone in the audience
|
||||
// collaborative checklist item toggle. Anyone in the audience
|
||||
// (owner + active recipients) can flip an item; we optimistically patch
|
||||
// every cached `Note`/`SentNote`/`ReceivedNote`/`NoteThread` entry that
|
||||
// references this noteId so all surfaces (cards, sent grid, inbox,
|
||||
@@ -432,7 +432,7 @@ export function useArchiveReceivedNote() {
|
||||
});
|
||||
}
|
||||
|
||||
// Task #510: bulk archive for the Notes Inbox. Mirrors
|
||||
// bulk archive for the Notes Inbox. Mirrors
|
||||
// `useBulkDeleteSentNotes` — fans `BULK_DELETE_CONCURRENCY` workers over
|
||||
// POST /notes/:id/archive so the request count is bounded even on large
|
||||
// selections, and reports per-id success/failure so the caller can show
|
||||
@@ -471,7 +471,7 @@ export function useBulkArchiveReceivedNotes() {
|
||||
});
|
||||
}
|
||||
|
||||
// Task #512: per-recipient delete. Removes only the caller's own
|
||||
// per-recipient delete. Removes only the caller's own
|
||||
// recipient row — sender, note, and other recipients are untouched.
|
||||
// Invalidates `["notes"]` so both the inbox and the archived view
|
||||
// refresh immediately.
|
||||
@@ -484,7 +484,7 @@ export function useDeleteReceivedNote() {
|
||||
});
|
||||
}
|
||||
|
||||
// Task #512: bulk variant. One POST handles the whole batch on the
|
||||
// bulk variant. One POST handles the whole batch on the
|
||||
// server (single SQL DELETE) and reports per-id ok/notFound so the
|
||||
// UI can render a precise toast even on partial misses. Cache is
|
||||
// invalidated once at the end so all surfaces (inbox, archived,
|
||||
@@ -771,7 +771,7 @@ export function useFolderShares(folderId: number | null) {
|
||||
|
||||
// Replace the recipient set on a folder. Server diffs idempotently so this
|
||||
// can be called repeatedly with the desired final set. Each recipient
|
||||
// carries an explicit "view" | "edit" permission (Task #454).
|
||||
// carries an explicit "view" | "edit" permission.
|
||||
export function useUpdateFolderShares() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
@@ -805,7 +805,7 @@ export function useSharedWithMeFolders() {
|
||||
// Note returned inside a shared-folder view. Carries the readOnly flag the
|
||||
// server stamps on every record so the UI doesn't have to recompute access.
|
||||
// `readOnly` is `false` for notes inside an editor-permission folder
|
||||
// (Task #454).
|
||||
//.
|
||||
export interface SharedFolderNote extends Note {
|
||||
readOnly: boolean;
|
||||
}
|
||||
@@ -845,7 +845,7 @@ export const NOTE_COLORS: {
|
||||
bg: string;
|
||||
ring: string;
|
||||
accent: string;
|
||||
// Task #468: each color also carries the literal class strings used by
|
||||
// each color also carries the literal class strings used by
|
||||
// the incoming-note popup so the popup's outer ring, the avatar's
|
||||
// background + ring, and the animated ping pulse all match the note's
|
||||
// own color family instead of being a fixed amber tint. Keeping these
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Helpers for the 12-hour time picker used by the executive-meetings
|
||||
// editor (task #315). The picker keeps storage / wire format unchanged
|
||||
// editor. The picker keeps storage / wire format unchanged
|
||||
// (canonical "HH:mm" 24h) — these helpers only translate between that
|
||||
// canonical form and the 3-control 12-hour shape the user sees, plus
|
||||
// combine an hour + minute + AM/PM toggle draft into a single
|
||||
@@ -13,7 +13,7 @@
|
||||
// choice, so the same misinput is now surfaced as a clear "pick AM
|
||||
// or PM" parse error rather than silently saved as AM.
|
||||
//
|
||||
// Per task #315 the picker exposes THREE separate controls per
|
||||
// the picker exposes THREE separate controls per
|
||||
// field (hour input 1–12, minute input 00–59, AM/PM toggle). The
|
||||
// hour input is intentionally permissive: it also accepts a full
|
||||
// time string ("13:30", "9:30 PM", "0930", "٠٩:٣٠") so power users
|
||||
@@ -189,7 +189,7 @@ export function combineTextWithPeriod(
|
||||
/**
|
||||
* Combine the 3-control picker's hour input + minute input + AM/PM
|
||||
* toggle into a single canonical "HH:mm" value. This is the primary
|
||||
* combine path for the picker rebuilt against the task #315 spec
|
||||
* combine path for the picker rebuilt against the spec
|
||||
* ("three controls per field: hour input, minute input, AM/PM
|
||||
* toggle").
|
||||
*
|
||||
|
||||
@@ -1232,7 +1232,7 @@ function ScheduleSection({
|
||||
// what makes title / attendee / merge / row-color edits repaint the
|
||||
// cell within a single React commit instead of waiting for the GET
|
||||
// refetch — the same pattern saveTimes already uses for the schedule
|
||||
// time editor (task #316).
|
||||
// time editor.
|
||||
const applyMeetingPatch = useCallback(
|
||||
(meetingId: number, patch: (m: Meeting) => Meeting) => {
|
||||
const queryKey = ["/api/executive-meetings", date] as const;
|
||||
@@ -4549,14 +4549,14 @@ function TimeRangeCell({
|
||||
// Imperative handles to the start/end pickers. The inline editor
|
||||
// reads the live draft (typed text + AM/PM toggle) via commit()
|
||||
// when saving, instead of relying on React state — same
|
||||
// belt-and-braces pattern task #308 used to dodge "Enter pressed
|
||||
// belt-and-braces pattern used to dodge "Enter pressed
|
||||
// before React's batched onChange flush" races.
|
||||
const startPickerRef = useRef<TimePicker12hHandle | null>(null);
|
||||
const endPickerRef = useRef<TimePicker12hHandle | null>(null);
|
||||
|
||||
// Track the last `startSaved`/`endSaved` we've observed so the sync
|
||||
// effect below only fires when the *saved props themselves change*,
|
||||
// not on every `editing` flip. This is the fix for task #316's
|
||||
// not on every `editing` flip. This is the fix for's
|
||||
// snap-back race: `save()` writes the optimistic times, then
|
||||
// `setEditing(false)` triggered a re-run of the prior
|
||||
// `[editing, startSaved, endSaved]` effect that overwrote the
|
||||
@@ -5027,7 +5027,7 @@ function AttendeesCell({
|
||||
|
||||
if (hasSplit) {
|
||||
// Split-mode cells render ONE cell-level "+ عنوان فرعي" chip below
|
||||
// all groups (see Task #234), so each per-group AttendeeFlow must
|
||||
// all groups (see), so each per-group AttendeeFlow must
|
||||
// suppress its own trailing subheading chip. Per-group "+" person
|
||||
// chips and the after-section chips are unaffected.
|
||||
return (
|
||||
@@ -5092,7 +5092,7 @@ function AttendeesCell({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Cell-level "+ عنوان فرعي" chip (Task #234). One per cell,
|
||||
{/* Cell-level "+ عنوان فرعي" chip. One per cell,
|
||||
not one per group. Clicking it routes the new subheading to
|
||||
the LAST visible group in render order (virtual → internal
|
||||
→ external). The chip mirrors the per-group hide rules:
|
||||
@@ -6127,7 +6127,7 @@ function ManageSection({
|
||||
// state — the picker's onChange suppresses ambiguous/invalid
|
||||
// drafts so `editing.startTime` / `editing.endTime` may still
|
||||
// hold the previous saved value when the user typed but never
|
||||
// resolved the AM/PM. Task #315: prevent silent fallback.
|
||||
// resolved the AM/PM.: prevent silent fallback.
|
||||
const startTime = timeOverrides
|
||||
? timeOverrides.startTime || null
|
||||
: editing.startTime || null;
|
||||
@@ -6496,7 +6496,7 @@ function ManageSection({
|
||||
onChange={setEditing}
|
||||
// Pass the committed (canonical) start/end through so the
|
||||
// dialog's Enter/click-Save path validates the time pickers
|
||||
// synchronously (per task #315: typing "1:15" without an
|
||||
// synchronously (: typing "1:15" without an
|
||||
// AM/PM pick must surface as an error, NOT silently fall
|
||||
// back to the previous saved value or AM). The values
|
||||
// arriving here are already canonical "HH:mm" or "" — we
|
||||
|
||||
@@ -103,7 +103,7 @@ function AppIconContent({
|
||||
const name = lang === "ar" ? app.nameAr : app.nameEn;
|
||||
|
||||
const Icon = resolveIcon(app.iconName);
|
||||
// Task #517: when an admin uploads a custom image, render it in place
|
||||
// when an admin uploads a custom image, render it in place
|
||||
// of the Lucide icon. The image fills the tile (object-cover) so the
|
||||
// gradient background still shows through transparent PNGs.
|
||||
const resolvedImage = resolveServiceImageUrl(app.imageUrl ?? null);
|
||||
@@ -479,7 +479,7 @@ export default function HomePage() {
|
||||
logAppOpen(app.id, { keepalive: true }).catch(() => {
|
||||
// Best-effort tracking; ignore failures so navigation isn't blocked.
|
||||
});
|
||||
// Task #517: branch on the admin-configured open mode.
|
||||
// branch on the admin-configured open mode.
|
||||
// external_tab — open the externalUrl in a new tab
|
||||
// external_iframe — navigate to /embedded/:id which renders externalUrl
|
||||
// inside an iframe shell with a back button
|
||||
@@ -516,7 +516,7 @@ export default function HomePage() {
|
||||
const initials = (displayName || "?").trim().slice(0, 1).toUpperCase();
|
||||
|
||||
return (
|
||||
// Task #459: removed `overflow-hidden` and switched to `min-h-[100dvh]`
|
||||
// removed `overflow-hidden` and switched to `min-h-[100dvh]`
|
||||
// so iOS Safari can collapse its URL bar on scroll, matching the
|
||||
// executive-meetings page behavior. `100dvh` (dynamic viewport
|
||||
// height) sizes against the visible area, so the layout reflows
|
||||
@@ -566,11 +566,11 @@ export default function HomePage() {
|
||||
itself can scale at the `md:` breakpoint.
|
||||
*/}
|
||||
{/*
|
||||
Task #527 + #529: per-user UI preferences (language,
|
||||
: per-user UI preferences (language,
|
||||
notification sound + vibration, master sound, clock
|
||||
style/visibility/12-24h) live behind a single Settings
|
||||
(gear) entry that opens the unified panel. Logout stays
|
||||
on the topbar as a single-tap action (Task #529 reverted
|
||||
on the topbar as a single-tap action ( reverted
|
||||
the brief move into the panel). The cluster now shows
|
||||
exactly Bell -> Inbox -> Settings -> Logout (Inbox is
|
||||
conditional on the user being able to receive incoming
|
||||
|
||||
@@ -194,7 +194,7 @@ export default function NotesPage() {
|
||||
}, []);
|
||||
const clearSelected = useCallback(() => setSelectedIds(new Set()), []);
|
||||
|
||||
// Task #472: bulk-select + bulk-delete for the Sent tab. Lives at the
|
||||
// bulk-select + bulk-delete for the Sent tab. Lives at the
|
||||
// page level (not inside SentList) so the toggle button in the header
|
||||
// and the AlertDialog rendered next to the other dialogs can share
|
||||
// state with the list. Selection is kept as a `Set<number>` for O(1)
|
||||
@@ -225,7 +225,7 @@ export default function NotesPage() {
|
||||
}
|
||||
}, [view, sentSelectionMode, exitSentSelection]);
|
||||
|
||||
// Task #510: bulk-select + bulk-archive for the Inbox tab. Identical
|
||||
// bulk-select + bulk-archive for the Inbox tab. Identical
|
||||
// shape to the Sent tab's selection state above, but the destructive
|
||||
// action is "archive" (the recipient doesn't own the underlying note,
|
||||
// so we hide it from their inbox via POST /notes/:id/archive instead
|
||||
@@ -234,7 +234,7 @@ export default function NotesPage() {
|
||||
const [selectedInboxIds, setSelectedInboxIds] = useState<Set<number>>(new Set());
|
||||
const [bulkArchiveOpen, setBulkArchiveOpen] = useState(false);
|
||||
const bulkArchiveInbox = useBulkArchiveReceivedNotes();
|
||||
// Task #512: bulk-delete for the Notes Inbox. Distinct from archive
|
||||
// bulk-delete for the Notes Inbox. Distinct from archive
|
||||
// (which moves to the Archived tab) — this permanently removes the
|
||||
// caller's own recipient rows so the notes disappear from both
|
||||
// Inbox and Archived. The sender's copy is untouched.
|
||||
@@ -464,7 +464,7 @@ export default function NotesPage() {
|
||||
);
|
||||
}, [sentNotes, search]);
|
||||
|
||||
// Task #472 (architect fix): when the visible Sent set changes
|
||||
// (architect fix): when the visible Sent set changes
|
||||
// (search/filter, server refresh, or successful delete), prune the
|
||||
// selection to currently visible ids so we never delete a hidden
|
||||
// note. If the prune empties the visible list while in selection
|
||||
@@ -517,7 +517,7 @@ export default function NotesPage() {
|
||||
|
||||
// Composer is rendered inside the main content column of the
|
||||
// folders+content row so its top edge aligns with the FoldersRail
|
||||
// (Task #514). On narrow widths the row stacks (`flex-col`) and the
|
||||
//. On narrow widths the row stacks (`flex-col`) and the
|
||||
// composer is forced above the rail via `order-*` classes so the
|
||||
// user can still capture a quick note without scrolling past the
|
||||
// rail. It is hidden on read-only / non-owner views (Inbox, Sent,
|
||||
@@ -526,7 +526,7 @@ export default function NotesPage() {
|
||||
view === "active" && folderSelection.kind !== "shared-folder";
|
||||
|
||||
return (
|
||||
// Task #459: switched from a fixed-viewport `h-screen overflow-hidden`
|
||||
// switched from a fixed-viewport `h-screen overflow-hidden`
|
||||
// shell to a document-scrolling layout (`min-h-[100dvh]`) so iOS
|
||||
// Safari can collapse its URL bar on scroll, matching the executive
|
||||
// meetings page. The header below is `sticky top-0` so it stays
|
||||
@@ -543,7 +543,7 @@ export default function NotesPage() {
|
||||
dir={isRtl ? "rtl" : "ltr"}
|
||||
data-testid="notes-page"
|
||||
>
|
||||
{/* Task #457: enlarged header — taller padding, bigger title /
|
||||
{/* enlarged header — taller padding, bigger title /
|
||||
icons / search / tabs so the bar is easier to read on big
|
||||
screens. Sizing kept responsive (flex-wrap) so it still folds
|
||||
nicely on narrow widths. */}
|
||||
@@ -595,7 +595,7 @@ export default function NotesPage() {
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center gap-3 flex-wrap">
|
||||
{/* Task #466: replaced the four-tab email-style bar with a unified
|
||||
{/* replaced the four-tab email-style bar with a unified
|
||||
feed. The page opens directly on "My Notes"; Inbox / Sent /
|
||||
Archived now live in the overflow menu (⋯) on the end of the
|
||||
row. A small back button appears when the user has navigated
|
||||
@@ -639,7 +639,7 @@ export default function NotesPage() {
|
||||
</div>
|
||||
)}
|
||||
<div className="ms-auto flex items-center gap-2">
|
||||
{/* Task #472: bulk-select toggle for the Sent tab. Only shown
|
||||
{/* bulk-select toggle for the Sent tab. Only shown
|
||||
when there are sent notes to act on. Entering selection
|
||||
mode swaps the cards' click behavior from "open thread"
|
||||
to "toggle checkbox" and exposes the bulk action bar
|
||||
@@ -771,13 +771,13 @@ export default function NotesPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Task #459: inner scroll container removed (was
|
||||
{/* inner scroll container removed (was
|
||||
`flex-1 min-h-0 overflow-y-auto`) — content now scrolls with
|
||||
the document so iOS Safari collapses the URL bar on scroll.
|
||||
The sticky header above keeps the controls pinned. */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
|
||||
{/* Task #514: switched the folders+content row from flex to CSS
|
||||
{/* switched the folders+content row from flex to CSS
|
||||
grid so the top composer can sit at the SAME vertical
|
||||
position as the FoldersRail on desktop, while still
|
||||
preserving the original mobile reading order
|
||||
@@ -978,7 +978,7 @@ export default function NotesPage() {
|
||||
<Archive size={14} />
|
||||
{t("notes.bulkArchive", "Archive")}
|
||||
</button>
|
||||
{/* Task #512: bulk Delete sits next to Archive. It's
|
||||
{/* bulk Delete sits next to Archive. It's
|
||||
destructive and user-scoped — copy in the confirm
|
||||
dialog clarifies the sender keeps their copy. */}
|
||||
<button
|
||||
@@ -1095,7 +1095,7 @@ export default function NotesPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Task #472: confirmation for bulk-deleting selected sent notes.
|
||||
{/* confirmation for bulk-deleting selected sent notes.
|
||||
Fires N parallel DELETE /notes/:id calls via Promise.allSettled
|
||||
so partial failures still report a precise count. On success
|
||||
the React Query "notes" key is invalidated by the mutation so
|
||||
@@ -1185,7 +1185,7 @@ export default function NotesPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Task #510: confirmation for bulk-archiving selected inbox notes.
|
||||
{/* confirmation for bulk-archiving selected inbox notes.
|
||||
Mirrors the Sent bulk-delete dialog: parallel POST /notes/:id/archive
|
||||
via worker pool, partial-failure aware toasts. */}
|
||||
<AlertDialog
|
||||
@@ -1273,7 +1273,7 @@ export default function NotesPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Task #512: confirm dialog for bulk-deleting inbox notes. The
|
||||
{/* confirm dialog for bulk-deleting inbox notes. The
|
||||
copy makes clear it removes them from BOTH inbox + archive
|
||||
and that the sender keeps their copy. Single POST to the
|
||||
bulk endpoint; reports per-id ok/notFound. */}
|
||||
@@ -1362,7 +1362,7 @@ export default function NotesPage() {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Task #512: per-row inbox delete confirm. Driven by the page-level
|
||||
{/* per-row inbox delete confirm. Driven by the page-level
|
||||
singleDeleteInboxId state set from each ReceivedList card so the
|
||||
dialog can mount once instead of per card. */}
|
||||
<AlertDialog
|
||||
@@ -1529,7 +1529,7 @@ function NoteCard({
|
||||
labels: NoteLabel[];
|
||||
onEdit: (n: Note) => void;
|
||||
onSend: (n: Note) => void;
|
||||
// Task #454: shared-folder editors don't own the note, and the
|
||||
// shared-folder editors don't own the note, and the
|
||||
// server-side /send route is owner-only, so the Send button is
|
||||
// suppressed for them to avoid a UI affordance that would 403.
|
||||
hideSend?: boolean;
|
||||
@@ -2205,7 +2205,7 @@ function ReceivedList({
|
||||
selectionMode?: boolean;
|
||||
selectedIds?: Set<number>;
|
||||
onToggle?: (id: number) => void;
|
||||
// Task #512: optional per-row delete affordance. Only the live
|
||||
// optional per-row delete affordance. Only the live
|
||||
// Inbox passes it; the Archived section omits it because deletes
|
||||
// are surfaced at the page level there.
|
||||
onRequestDelete?: (id: number) => void;
|
||||
@@ -2332,7 +2332,7 @@ function SentList({
|
||||
loading: boolean;
|
||||
notes: SentNote[];
|
||||
onOpen: (id: number) => void;
|
||||
// Task #472: when `selectionMode` is on the cards become checkbox
|
||||
// when `selectionMode` is on the cards become checkbox
|
||||
// toggles instead of thread openers. The page-level state is passed
|
||||
// in so the bulk action bar (rendered above this list) can mirror
|
||||
// the same Set without prop-drilling state setters.
|
||||
@@ -2708,12 +2708,12 @@ function ThreadDialog({
|
||||
const markRead = useMarkNoteRead();
|
||||
const reply = useReplyToNote();
|
||||
const archive = useArchiveReceivedNote();
|
||||
// Task #512: per-row delete from the open thread. Lives next to the
|
||||
// per-row delete from the open thread. Lives next to the
|
||||
// existing Archive button so single-note delete doesn't require
|
||||
// entering bulk-selection mode.
|
||||
const deleteReceived = useDeleteReceivedNote();
|
||||
const [singleDeleteOpen, setSingleDeleteOpen] = useState(false);
|
||||
// Task #438: collaborative checklist toggle scoped to the open thread.
|
||||
// collaborative checklist toggle scoped to the open thread.
|
||||
const toggleChecklistItem = useToggleChecklistItem();
|
||||
const [replyText, setReplyText] = useState("");
|
||||
const [ownerReplyTarget, setOwnerReplyTarget] = useState<number | null>(null);
|
||||
@@ -2805,7 +2805,7 @@ function ThreadDialog({
|
||||
items={thread.items ?? []}
|
||||
testIdPrefix={`thread-checklist-${thread.id}`}
|
||||
noteColor={thread.color}
|
||||
// Task #438: gate toggles to match server auth — only
|
||||
// gate toggles to match server auth — only
|
||||
// the owner or an active (non-archived) recipient can
|
||||
// collaboratively check items. Admins viewing a note
|
||||
// they neither own nor were sent get a read-only
|
||||
@@ -2943,7 +2943,7 @@ function ThreadDialog({
|
||||
<Archive size={14} className="me-1" />
|
||||
{t("notes.archive", "Archive")}
|
||||
</Button>
|
||||
{/* Task #512: per-row Delete sits next to Archive
|
||||
{/* per-row Delete sits next to Archive
|
||||
in the thread footer. Confirms inline before
|
||||
calling DELETE /notes/received/:id so users
|
||||
don't lose a note by accident. */}
|
||||
@@ -2983,7 +2983,7 @@ function ThreadDialog({
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{/* Task #512: single-note delete confirm rendered as a sibling so
|
||||
{/* single-note delete confirm rendered as a sibling so
|
||||
it overlays cleanly above the thread dialog. */}
|
||||
<AlertDialog
|
||||
open={singleDeleteOpen}
|
||||
@@ -3452,7 +3452,7 @@ function Composer({
|
||||
labels: NoteLabel[];
|
||||
folderSelection: FolderSelection;
|
||||
onSend: (id: number) => void;
|
||||
// Task #454: shared-folder editors create notes that get stamped to
|
||||
// shared-folder editors create notes that get stamped to
|
||||
// the folder owner; the owner-only `/notes/:id/send` route would 403
|
||||
// for them, so the composer hides its Send button in that context.
|
||||
hideSend?: boolean;
|
||||
@@ -3555,7 +3555,7 @@ function Composer({
|
||||
create.mutate(payload, { onSuccess: reset });
|
||||
};
|
||||
|
||||
// Task #444: Send button inside the composer. The plan agreed with
|
||||
// Send button inside the composer. The plan agreed with
|
||||
// the user is "save first, then open the send dialog for the new
|
||||
// note". On success we reset the composer (mirroring save()) and
|
||||
// hand the freshly created note's id up to the page so it can mount
|
||||
@@ -3632,7 +3632,7 @@ function Composer({
|
||||
<Button size="sm" variant="ghost" onClick={save} data-testid="notes-composer-save">
|
||||
{t("notes.save", "Save")}
|
||||
</Button>
|
||||
{/* Task #444: Send action inside the composer. Saves first
|
||||
{/* Send action inside the composer. Saves first
|
||||
(auto-save on send was explicitly OK'd by the user),
|
||||
then opens <SendNoteDialog> for the new note. Disabled
|
||||
while empty or while the create request is in flight to
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Task #517 E2E:
|
||||
// E2E:
|
||||
// 1) An app with an `image_url` renders that image (not a Lucide icon)
|
||||
// on the launcher tile.
|
||||
// 2) An app with `open_mode='external_tab'` opens its `external_url` in
|
||||
|
||||
@@ -23,7 +23,7 @@ function attendanceLabel(lang, kind) {
|
||||
return val;
|
||||
}
|
||||
|
||||
// E2E coverage for Task #204: the per-group attendee headers
|
||||
// E2E coverage for: the per-group attendee headers
|
||||
// ("Virtual Attendance / Internal Attendance / External Attendance")
|
||||
// rendered above each attendee group in the schedule's attendee cell.
|
||||
//
|
||||
|
||||
@@ -174,7 +174,7 @@ for (const lang of ["en", "ar"]) {
|
||||
test(`Schedule [${lang}]: top "+ subheading" chip is NEVER rendered (empty / person-only / subheading-only / mixed)`, async ({
|
||||
page,
|
||||
}) => {
|
||||
// Task #230: the top chip is gone in every state. We seed two
|
||||
// the top chip is gone in every state. We seed two
|
||||
// meetings that, between them, exercise all four cell states:
|
||||
// meetingEmptyId: a brand-new meeting with zero attendees ⇒
|
||||
// the cell is EMPTY.
|
||||
@@ -272,7 +272,7 @@ for (const lang of ["en", "ar"]) {
|
||||
// must be absent in every group. We prove all three groups
|
||||
// mounted by checking each one's per-group trailing "+" person
|
||||
// button is present (testid em-add-attendee-{addType}). NOTE:
|
||||
// since Task #234, the trailing "+ subheading" chip is suppressed
|
||||
// since, the trailing "+ subheading" chip is suppressed
|
||||
// per group in split-mode cells and replaced by ONE cell-level
|
||||
// chip (testid em-add-subheading-cell-{meetingId}) — so the
|
||||
// per-group em-add-subheading-{addType} testids must NOT exist
|
||||
@@ -773,7 +773,7 @@ for (const lang of ["en", "ar"]) {
|
||||
test(`Schedule [${lang}]: split-mode cell shows ONE cell-level "+ subheading" chip and routes to the LAST visible group`, async ({
|
||||
page,
|
||||
}) => {
|
||||
// Task #234: in a split-mode cell (≥2 attendance groups visible),
|
||||
// in a split-mode cell (≥2 attendance groups visible),
|
||||
// the trailing "+ عنوان فرعي" chip must NOT be rendered per group;
|
||||
// a single cell-level chip below all groups is rendered instead.
|
||||
// Clicking it adds the new subheading to the LAST visible group in
|
||||
@@ -935,7 +935,7 @@ for (const lang of ["en", "ar"]) {
|
||||
test(`Schedule [${lang}]: split-mode WITHOUT external — cell-level "+ subheading" chip routes to INTERNAL (last visible)`, async ({
|
||||
page,
|
||||
}) => {
|
||||
// Task #234 fallback path. The "last visible group" rule resolves
|
||||
// fallback path. The "last visible group" rule resolves
|
||||
// the cell-level chip's target at click-time as
|
||||
// external > internal > virtual. The previous test exercises the
|
||||
// external-wins path; this one locks the internal-wins fallback by
|
||||
|
||||
@@ -14,7 +14,7 @@ function loadLocale(lang) {
|
||||
}
|
||||
const localeBundles = { en: loadLocale("en"), ar: loadLocale("ar") };
|
||||
|
||||
// E2E coverage for Task #207 — custom subheadings inside the attendees
|
||||
// E2E coverage for — custom subheadings inside the attendees
|
||||
// cell. Subheadings are user-defined section labels that live alongside
|
||||
// person rows in a meeting's attendee list, but are NOT counted as
|
||||
// attendees and do NOT advance the running "1-, 2-, 3-…" person index.
|
||||
|
||||
@@ -189,7 +189,7 @@ async function gotoSchedule(page, lang) {
|
||||
// LTR (English) and RTL (Arabic) locales — since the bug originally
|
||||
// surfaced on the Arabic schedule.
|
||||
//
|
||||
// Per Task #203 the index prefix is intentionally hidden when the
|
||||
// the index prefix is intentionally hidden when the
|
||||
// attendee group has only one entry, so this test scopes itself to
|
||||
// rows that actually carry an `em-attendee-index-*` span.
|
||||
for (const lang of ["en", "ar"]) {
|
||||
@@ -260,7 +260,7 @@ for (const lang of ["en", "ar"]) {
|
||||
}
|
||||
});
|
||||
|
||||
// Task #203: when an attendee group has exactly one entry the
|
||||
// when an attendee group has exactly one entry the
|
||||
// leading `1-` index prefix is hidden because there is nothing to
|
||||
// enumerate. Assert that for at least one solo-attendee row, no
|
||||
// `em-attendee-index-*` span exists inside it, in BOTH locales.
|
||||
|
||||
@@ -708,7 +708,7 @@ test("Schedule keyboard: Tab from the title cell reaches the time cell in the sa
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Task #308: typing on the keyboard + Enter must save.
|
||||
// typing on the keyboard + Enter must save.
|
||||
//
|
||||
// The historical bug: the time cell used <input type="time">, which
|
||||
// only fires onChange when the value is a complete, canonical "HH:MM"
|
||||
@@ -729,7 +729,7 @@ test("Schedule keyboard: Tab from the title cell reaches the time cell in the sa
|
||||
// the same for end, press Enter, and wait for the PATCH + day GET
|
||||
// refetch.
|
||||
//
|
||||
// Per task #315 the time cell is now a 12-hour picker — typing a
|
||||
// the time cell is now a 12-hour picker — typing a
|
||||
// 1–12 hour without first picking AM or PM is intentionally an
|
||||
// ambiguous parse error (the fix for the "1:15 silently saved as
|
||||
// AM" bug). For typed shapes that the picker can resolve on its
|
||||
@@ -802,7 +802,7 @@ async function typeTimesAndSave(
|
||||
// hour/minute so a test that accidentally passes by reading stale
|
||||
// state (rather than the new value) would fail.
|
||||
//
|
||||
// `startPeriod` / `endPeriod` mirror task #315's "the picker has no
|
||||
// `startPeriod` / `endPeriod` mirror's "the picker has no
|
||||
// implicit default" rule: ambiguous typed shapes (1–12 hour, no
|
||||
// marker) require the helper to click the AM/PM toggle, while
|
||||
// shapes with an explicit marker or a 24h-only hour are resolved by
|
||||
@@ -884,7 +884,7 @@ test("Schedule keyboard (AR): typing Arabic-Indic digits + Enter saves canonical
|
||||
await gotoMeetingRow(page, meetingId, date);
|
||||
|
||||
// ٠٩:٣٠ -> 09:30, ١٠:٤٥ -> 10:45. Both are 1–12 hours so the
|
||||
// picker requires an explicit AM/PM choice (task #315) — pass the
|
||||
// picker requires an explicit AM/PM choice — pass the
|
||||
// Arabic morning marker via the toggle so the helper clicks ص.
|
||||
await typeTimesAndSave(page, meetingId, date, "٠٩:٣٠", "١٠:٤٥", "am", "am");
|
||||
|
||||
@@ -897,7 +897,7 @@ test("Schedule keyboard (AR): typing Arabic-Indic digits + Enter saves canonical
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Task #311: dragging a visible meeting must produce a chronologically-
|
||||
// dragging a visible meeting must produce a chronologically-
|
||||
// sorted visible list, even when the same day contains hidden cancelled
|
||||
// meetings. Before the fix, cancelled meetings (filtered out of
|
||||
// `orderedMeetings` for display) were still included in the reorder
|
||||
@@ -1239,7 +1239,7 @@ test("Schedule keyboard: Tab walks start hour → start minute → start AM →
|
||||
const endPm = page.getByTestId(`em-time-end-period-pm-${meetingId}`);
|
||||
await expect(startInput).toBeFocused();
|
||||
|
||||
// Per task #315 each side has THREE controls: hour input, minute
|
||||
// each side has THREE controls: hour input, minute
|
||||
// input, AM/PM toggle. Tab walks through start hour → start minute
|
||||
// → start AM → start PM → end hour → end minute → end AM → end PM.
|
||||
// "7" + "15" is a 1–12 hour with no marker, so the picker requires
|
||||
@@ -1429,7 +1429,7 @@ test.describe("Schedule mobile viewport (iPhone 375)", () => {
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Task #316: stop the snap-back race + collapse the editor instantly.
|
||||
// stop the snap-back race + collapse the editor instantly.
|
||||
// Three guards:
|
||||
// 1. Save → immediate re-open of the editor must show the SAVED
|
||||
// values pre-filled, not blank fields. (The bug the user
|
||||
@@ -1727,8 +1727,8 @@ test("Schedule time editor: a failed PATCH rolls back the optimistic times and r
|
||||
|
||||
// ─── #317: optimistic-cache repaint for non-time inline editors ─────
|
||||
//
|
||||
// Task #316 made the time cell repaint instantly via an optimistic
|
||||
// React Query cache write. Task #317 generalises that pattern to the
|
||||
// made the time cell repaint instantly via an optimistic
|
||||
// React Query cache write. generalises that pattern to the
|
||||
// title, attendee-name, and merge-text editors. The three tests below
|
||||
// mirror the time-cell tests for the title cell:
|
||||
//
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// E2E + UI coverage for the cell-merge feature added by task #152.
|
||||
// E2E + UI coverage for the cell-merge feature added by.
|
||||
//
|
||||
// Two scenarios live here:
|
||||
//
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Verifies the consolidated row-actions kebab menu introduced as part
|
||||
// of task #191 — the three previously-separate row overlays (delete,
|
||||
// of — the three previously-separate row overlays (delete,
|
||||
// row color, merge cells) are now collapsed into a single menu so they
|
||||
// no longer collide visually inside narrow cells. Each sub-action
|
||||
// keeps its original testid, so the rest of the suite + any external
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Coverage for task #192 — quick-preview indicators on the row-actions
|
||||
// Coverage for — quick-preview indicators on the row-actions
|
||||
// kebab menu's main view:
|
||||
//
|
||||
// 1. The "Row color" item shows a tiny swatch reflecting the row's
|
||||
|
||||
@@ -595,7 +595,7 @@ test("Schedule: editing an attendee name with bold + color via the Tiptap toolba
|
||||
|
||||
// Seed a meeting with one attendee whose name is plain text. The
|
||||
// attendee row uses the SAME EditableCell + Tiptap toolbar as titles
|
||||
// (parent task #122), so applying bold + a color swatch and saving
|
||||
// (parent), so applying bold + a color swatch and saving
|
||||
// via the toolbar's check button must round-trip through the PUT
|
||||
// attendees endpoint and survive a full reload, just like titles do.
|
||||
const date = uniqueFutureDate(4);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Task #512 e2e: per-recipient delete on the Inbox tab.
|
||||
// e2e: per-recipient delete on the Inbox tab.
|
||||
// Seeds three received notes for a recipient, opens Notes (Inbox is
|
||||
// the default view), enters bulk-select mode, picks two cards,
|
||||
// confirms the bulk delete, and verifies that the deleted recipient
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Task #438 e2e: a recipient toggles a checklist item directly from the
|
||||
// e2e: a recipient toggles a checklist item directly from the
|
||||
// incoming-note popup, and the change is visible to the sender (and
|
||||
// persists server-side).
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// E2E test for Task #406: when a sender sends a note, the recipient (in
|
||||
// E2E test for: when a sender sends a note, the recipient (in
|
||||
// another browser context) sees the note pop up as a centered modal on
|
||||
// their screen — wherever they are in the app — within ~seconds.
|
||||
import { test, expect } from "@playwright/test";
|
||||
@@ -225,7 +225,7 @@ test("recipient sees a popup modal when a note arrives in real time", async ({
|
||||
}
|
||||
});
|
||||
|
||||
// Task #410: when the recipient replies on a note, the *original sender*
|
||||
// when the recipient replies on a note, the *original sender*
|
||||
// (note owner) should see the same floating-card popup variant — pre-
|
||||
// filled with the reply text + replier name — wherever they are in the
|
||||
// app, with the same chime/vibration profile as the new-note alert.
|
||||
@@ -373,7 +373,7 @@ test("sender sees a floating reply card when the recipient replies", async ({
|
||||
}
|
||||
});
|
||||
|
||||
// Task #433: a checklist note must render the inner sticky-note "as a
|
||||
// a checklist note must render the inner sticky-note "as a
|
||||
// real note" — i.e. the popup body shows a read-only checklist preview
|
||||
// (not the plain text body), proving `noteKind` + `items` are forwarded
|
||||
// from the backend through the socket payload into the popup.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Task #427: on touch devices (iPad / phone), tapping a button on the
|
||||
// on touch devices (iPad / phone), tapping a button on the
|
||||
// floating note popup MUST work — even when other floating UI is on
|
||||
// screen at the same time. The previous root cause was a full-viewport
|
||||
// `fixed inset-0` wrapper at z-[110] that intermittently swallowed
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Task #472 e2e: multi-select + bulk-delete on the Sent notes tab.
|
||||
// e2e: multi-select + bulk-delete on the Sent notes tab.
|
||||
// Seeds three sent notes for a user, opens the Sent view, enters
|
||||
// selection mode, picks two cards, confirms the bulk delete, and
|
||||
// verifies that the count drops by two while the unselected note
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// E2E test: the note thread dialog must cap its height instead of growing
|
||||
// to fill the entire viewport when the conversation is long. Regression
|
||||
// guard for task #417 (user reported the dialog covering the whole page).
|
||||
// guard for (user reported the dialog covering the whole page).
|
||||
import { test, expect } from "@playwright/test";
|
||||
import pg from "pg";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// E2E test for Task #441: notification sounds use Web Audio so they
|
||||
// E2E test for: notification sounds use Web Audio so they
|
||||
// reliably play on iPad Safari after a single user gesture. Verifies
|
||||
// (1) the AudioContext reaches `running` state after the user clicks
|
||||
// the per-sound preview button in notification settings, (2) the play
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Playwright e2e for the per-card and bulk delete affordances on
|
||||
// /orders/incoming added in task #397.
|
||||
// /orders/incoming added in.
|
||||
//
|
||||
// Covers:
|
||||
// - Bulk delete: receiver selects 2 unclaimed orders via per-card
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Task #438: lightweight render test for the upcoming-meeting alert
|
||||
// lightweight render test for the upcoming-meeting alert
|
||||
// entrance animation. Asserts the alert panel renders with the
|
||||
// matching scale-in/opacity transition, an accent ring (boxShadow with
|
||||
// the user's accent color), and a pulsing animate-ping halo around the
|
||||
|
||||
Reference in New Issue
Block a user