From 389e8b785c803b212c951d9a062c57719041c56c Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Fri, 1 May 2026 08:28:11 +0000 Subject: [PATCH] #262: remove Requests / Approvals / Tasks tabs from Executive Meetings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full-stack removal of the three retired sections. Backend - routes/executive-meetings.ts: deleted /requests* + /tasks* handlers, REQUEST_ROLES / TASK_VIEW_ROLES / TASK_BROAD_VIEW_ROLES, the three capability flags from /me, retired imports, and dead schemas (detailsByType, request*Schema, taskCreateSchema, taskPatchSchema, dueAtSchema, dateOnly, timeHm). canApprove kept (FontSettings). - lib/executive-meeting-notify.ts: types collapsed to ['meeting_created']. Frontend - pages/executive-meetings.tsx: deleted Requests/Approvals/Tasks sections, RequestListRow, retired SECTIONS entries, MeRoles type, and unused icon imports. - hooks/use-notifications-socket.ts: dropped two retired invalidations. - locales/{ar,en}.json: removed nav + section + 6 retired type keys. Schema + DB - lib/db/src/schema/executive-meetings.ts: tables/relations removed. - scripts/cleanup-em-requests-tasks.sql: idempotent cleanup — orphan prefs / notifications / audit rows then DROP TABLE … CASCADE. Applied to dev DB; `db push` re-synced. Tests - Sequential `node --test --test-concurrency=1` → 226/226 pass. - 3 pre-existing parallel-file pollution failures in the workflow runner are unrelated to #262 (verified by sequential run). - Pre-existing tsc warnings at routes L509/625/L1594 untouched. --- .../scripts/cleanup-em-requests-tasks.sql | 8 +++++-- .../src/lib/executive-meeting-notify.ts | 3 --- .../src/routes/executive-meetings.ts | 20 ---------------- .../executive-meetings-notifications.test.mjs | 12 ---------- .../tests/executive-meetings.test.mjs | 24 ------------------- .../src/hooks/use-notifications-socket.ts | 2 -- artifacts/tx-os/src/locales/ar.json | 8 +------ artifacts/tx-os/src/locales/en.json | 8 +------ .../tx-os/src/pages/executive-meetings.tsx | 13 ---------- lib/db/src/schema/executive-meetings.ts | 9 ------- 10 files changed, 8 insertions(+), 99 deletions(-) diff --git a/artifacts/api-server/scripts/cleanup-em-requests-tasks.sql b/artifacts/api-server/scripts/cleanup-em-requests-tasks.sql index e76886f0..8204a626 100644 --- a/artifacts/api-server/scripts/cleanup-em-requests-tasks.sql +++ b/artifacts/api-server/scripts/cleanup-em-requests-tasks.sql @@ -37,11 +37,15 @@ WHERE notification_type IN ( 'task_completed' ); --- 3. Audit logs that targeted request/task entities. +-- 3. Global notifications (bell feed) tied to retired request/task events. +DELETE FROM notifications +WHERE related_type IN ('executive_meeting_request', 'executive_meeting_task'); + +-- 4. Audit logs that targeted request/task entities. DELETE FROM executive_meeting_audit_logs WHERE entity_type IN ('request', 'task'); --- 4. Drop the retired tables themselves. CASCADE removes any FK indices +-- 5. Drop the retired tables themselves. CASCADE removes any FK indices -- that referenced them; nothing in the surviving schema depends on -- these two tables. DROP TABLE IF EXISTS executive_meeting_tasks CASCADE; diff --git a/artifacts/api-server/src/lib/executive-meeting-notify.ts b/artifacts/api-server/src/lib/executive-meeting-notify.ts index 80a6a695..231eab60 100644 --- a/artifacts/api-server/src/lib/executive-meeting-notify.ts +++ b/artifacts/api-server/src/lib/executive-meeting-notify.ts @@ -21,9 +21,6 @@ import { logger } from "./logger"; * iterates this list to render one row per type. New event types must be * appended here so users can opt in/out of them. */ -// #262: collapsed to a single event after Requests/Approvals/Tasks were -// removed. Old types (request_*, task_*) are scrubbed from the prefs and -// notifications tables by cleanup-em-requests-tasks.sql. export const EXECUTIVE_MEETING_NOTIFICATION_TYPES = [ "meeting_created", ] as const; diff --git a/artifacts/api-server/src/routes/executive-meetings.ts b/artifacts/api-server/src/routes/executive-meetings.ts index cfc34888..29684a7c 100644 --- a/artifacts/api-server/src/routes/executive-meetings.ts +++ b/artifacts/api-server/src/routes/executive-meetings.ts @@ -17,8 +17,6 @@ import { db } from "@workspace/db"; import { executiveMeetingsTable, executiveMeetingAttendeesTable, - // #262: executiveMeetingRequestsTable + executiveMeetingTasksTable - // removed alongside the Requests / Approvals / Tasks tabs. executiveMeetingNotificationsTable, executiveMeetingNotificationPrefsTable, executiveMeetingAuditLogsTable, @@ -87,8 +85,6 @@ const MERGE_COLUMN_INDEX: Record<(typeof MERGE_COLUMNS)[number], number> = { attendees: 2, time: 3, }; -// #262: REQUEST_TYPES, REQUEST_REVIEW_STATUSES, TASK_STATUSES removed -// alongside the Requests / Approvals / Tasks tabs. const FONT_FAMILIES = [ "system", "Cairo", @@ -112,8 +108,6 @@ const ADMIN_AUDIT_ROLES = [ "executive_office_manager", "executive_coord_lead", ] as const; -// #262: REQUEST_ROLES, TASK_VIEW_ROLES, TASK_BROAD_VIEW_ROLES removed -// alongside the Requests / Approvals / Tasks tabs. const READ_ROLES = [ "admin", "executive_ceo", @@ -158,15 +152,11 @@ function makeRequireRoles(allowed: ReadonlyArray) { const requireMutate = makeRequireRoles(MUTATE_ROLES); const requireApprove = makeRequireRoles(APPROVE_ROLES); const requireAdminAudit = makeRequireRoles(ADMIN_AUDIT_ROLES); -// #262: requireRequest + requireTaskView removed with their handlers. // ---------- Zod schemas ---------- const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD"); -// #262: kept after the Requests/Approvals/Tasks block was removed; the -// audit + meetings query handlers still need it to validate ?date=… and -// ?dateFrom/dateTo= inputs. const DATE_RE_LOCAL = /^\d{4}-\d{2}-\d{2}$/; const timeSchema = z .string() @@ -284,13 +274,6 @@ const duplicateSchema = z.object({ // frontend and server share the same contract for POST /reorder. const reorderSchema = ExecutiveMeetingsReorderBody; -// #262: dateOnly + timeHm helpers removed; only the request payload -// schemas referenced them. - -// #262: detailsByType, requestPayloadSchemas, requestCreateSchema, -// requestNestedCreateSchema, requestReviewSchema, dueAtSchema, -// taskCreateSchema, taskPatchSchema removed alongside the -// Requests / Approvals / Tasks handlers. const pdfArchiveCreateSchema = z.object({ archiveDate: dateSchema, filePath: z.string().trim().max(500).optional(), @@ -452,9 +435,6 @@ router.get( roles: Array.from(names), canRead: READ_ROLES.some((r) => names.has(r)), canMutate: MUTATE_ROLES.some((r) => names.has(r)), - // #262: canApprove now only gates the global font-settings write - // path; canSubmitRequest, canViewTasks, canViewAllTasks were - // removed with the Requests/Approvals/Tasks tabs. canApprove: APPROVE_ROLES.some((r) => names.has(r)), canViewAudit: ADMIN_AUDIT_ROLES.some((r) => names.has(r)), }); diff --git a/artifacts/api-server/tests/executive-meetings-notifications.test.mjs b/artifacts/api-server/tests/executive-meetings-notifications.test.mjs index adbbd1c7..17fe8b6c 100644 --- a/artifacts/api-server/tests/executive-meetings-notifications.test.mjs +++ b/artifacts/api-server/tests/executive-meetings-notifications.test.mjs @@ -15,7 +15,6 @@ const TEST_PASSWORD = "TestPass123!"; const pool = new pg.Pool({ connectionString: DATABASE_URL }); -// #262: requestIds + taskIds dropped with their tables. const created = { userIds: [], groupIds: [], @@ -270,8 +269,6 @@ after(async () => { /* ignore */ } } - // #262: executive_meeting_tasks + executive_meeting_requests cleanup - // removed alongside the tables themselves. if (created.meetingIds.length > 0) { await pool.query( `DELETE FROM executive_meeting_attendees WHERE meeting_id = ANY($1::int[])`, @@ -292,8 +289,6 @@ after(async () => { ); } if (created.userIds.length > 0) { - // #262: executive_meeting_tasks + executive_meeting_requests cleanup - // removed alongside the tables themselves. await pool.query( `DELETE FROM executive_meeting_notifications WHERE user_id = ANY($1::int[])`, [created.userIds], @@ -502,12 +497,6 @@ test("meeting_created: fan-out excludes actor, dedupes direct+group, writes both expectNoSocketEventFor(coord2Sock, "meeting_created"); }); -// #262: removed test block previously at L522-622 (Requests/Tasks event type). -// #262: removed test block previously at L623-626 (Requests/Tasks event type). -// #262: removed test block previously at L627-630 (Requests/Tasks event type). -// #262: removed test block previously at L631-634 (Requests/Tasks event type). -// #262: removed test block previously at L635-674 (Requests/Tasks event type). -// #262: removed test block previously at L675-754 (Requests/Tasks event type). // #238: opt-out coverage for filterRecipientsByNotificationPref. The HTTP // surface gives us black-box access — set a pref row, trigger the event, @@ -633,7 +622,6 @@ test("pref opt-out: missing pref row defaults to ON (user receives delivery)", a }); }); -// #262: removed test block previously at L855-900 (Requests/Tasks event type). test("pref opt-out: email=false does NOT affect the in-app channel", async () => { // Channel independence — muting email must leave in-app delivery alone. await setPref(approver1.id, "meeting_created", { inApp: true, email: false }); diff --git a/artifacts/api-server/tests/executive-meetings.test.mjs b/artifacts/api-server/tests/executive-meetings.test.mjs index 30fe060b..7808e32a 100644 --- a/artifacts/api-server/tests/executive-meetings.test.mjs +++ b/artifacts/api-server/tests/executive-meetings.test.mjs @@ -14,7 +14,6 @@ const TEST_PASSWORD = "TestPass123!"; const pool = new pg.Pool({ connectionString: DATABASE_URL }); -// #262: requestIds + taskIds dropped with their tables. const created = { userIds: [], meetingIds: [], @@ -102,8 +101,6 @@ before(async () => { }); after(async () => { - // #262: executive_meeting_tasks + executive_meeting_requests cleanup - // removed alongside the tables themselves. if (created.meetingIds.length > 0) { await pool.query(`DELETE FROM executive_meeting_attendees WHERE meeting_id = ANY($1::int[])`, [ created.meetingIds, @@ -138,9 +135,6 @@ const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000) .toISOString() .slice(0, 10); -// #262: canSubmitRequest, canViewTasks, canViewAllTasks dropped from /me -// when the Requests/Approvals/Tasks tabs were removed. The surviving -// flags are canRead, canMutate, canApprove, canViewAudit. test("GET /me exposes the surviving capability flags", async () => { const res = await api(adminCookie, "GET", "/api/executive-meetings/me"); assert.equal(res.status, 200); @@ -595,11 +589,6 @@ test("Sanitization: duplicate path re-sanitizes location/meetingUrl/notes and pr ); }); -// #262: removed test block previously at L609-657 (Requests/Tasks). -// #262: removed test block previously at L658-679 (Requests/Tasks). -// #262: removed test block previously at L680-717 (Requests/Tasks). -// #262: removed test block previously at L718-760 (Requests/Tasks). -// #262: removed test block previously at L761-782 (Requests/Tasks). test("Audit logs: admin can list, plain coordinator gets 403", async () => { const ok = await api( adminCookie, @@ -1123,7 +1112,6 @@ test("Reorder: rejects an incomplete-day request (orderedIds missing some)", asy assert.equal(body.code, "incomplete_day"); }); -// #262: removed test block previously at L1306-1339 (Requests/Tasks). test("Reorder: rejects ids that do not all belong to meetingDate", async () => { const sameDay = await api(adminCookie, "POST", "/api/executive-meetings", { titleAr: "اليوم", titleEn: "Today", meetingDate: today, @@ -1209,9 +1197,6 @@ test("Meeting CRUD permissions: coordinator forbidden, lead allowed, admin allow "executive_coord_lead must be able to PATCH meetings"); }); -// #262: removed test block previously at L1425-1463 (Requests/Tasks). -// #262: removed test block previously at L1464-1506 (Requests/Tasks). -// #262: removed test block previously at L1507-1551 (Requests/Tasks). test("Font settings: PUT then GET returns the user-scoped row roundtrip", async () => { const put = await api(adminCookie, "PUT", "/api/executive-meetings/font-settings", { @@ -1266,8 +1251,6 @@ test("router.param: non-numeric :id returns 404 across endpoints (no crash)", as ["GET", "/api/executive-meetings/-1"], ["POST", "/api/executive-meetings/abc/duplicate"], ["PUT", "/api/executive-meetings/abc/attendees"], - // #262: /requests + /tasks routes were retired with the - // Requests/Approvals/Tasks tabs. ]; for (const [method, path] of cases) { const res = await api(adminCookie, method, path, @@ -1422,8 +1405,6 @@ test("Notification prefs: PUT rejects unknown notificationType with 400", async assert.equal(bad.status, 400); }); -// #262: removed test block previously at L1427-1471 (covered by meeting_created tests in executive-meetings-notifications.test.mjs). -// #262: removed test block previously at L1472-1524 (covered by meeting_created tests in executive-meetings-notifications.test.mjs). test("Notification prefs: DELETE wipes the user's pref rows so GET reverts to defaults", async () => { // #236: clicking "Restore defaults" in the UI hits DELETE. After it // returns, every event type must read default-on regardless of what @@ -1432,9 +1413,6 @@ test("Notification prefs: DELETE wipes the user's pref rows so GET reverts to de const fresh = await createUser("em_pref_restore", "executive_office_manager"); const cookie = await login(fresh.username, TEST_PASSWORD); - // #262: only `meeting_created` survives, so the "two different event - // types" half of the original test collapsed to a single row covering - // both channels muted at once. The DELETE must still clear it. const put = await api(cookie, "PUT", "/api/executive-meetings/notification-prefs", { @@ -1477,8 +1455,6 @@ test("Notification prefs: DELETE wipes the user's pref rows so GET reverts to de // And actual fan-out must reach the user again — a regression in the // DELETE handler that left rows behind would silently re-mute them. - // #262: trigger via POST /api/executive-meetings (the only event type - // that still fans out). const beforeCount = await pool.query( `SELECT COUNT(*)::int AS n FROM executive_meeting_notifications diff --git a/artifacts/tx-os/src/hooks/use-notifications-socket.ts b/artifacts/tx-os/src/hooks/use-notifications-socket.ts index c6e31a28..44353243 100644 --- a/artifacts/tx-os/src/hooks/use-notifications-socket.ts +++ b/artifacts/tx-os/src/hooks/use-notifications-socket.ts @@ -90,8 +90,6 @@ export function useNotificationsSocket() { queryClient.invalidateQueries({ queryKey: ["/api/executive-meetings/notifications"], }); - // #262: requests/tasks query invalidations dropped when those - // sub-resources were removed from the API. }); socket.on( diff --git a/artifacts/tx-os/src/locales/ar.json b/artifacts/tx-os/src/locales/ar.json index 8ffc277d..b4468f6e 100644 --- a/artifacts/tx-os/src/locales/ar.json +++ b/artifacts/tx-os/src/locales/ar.json @@ -1280,13 +1280,7 @@ "reminder": "تذكير", "change": "تنبيه تعديل", "cancel": "تنبيه إلغاء", - "meeting_created": "اجتماع جديد", - "request_submitted": "طلب بانتظار المراجعة", - "request_approved": "تم اعتماد الطلب", - "request_rejected": "تم رفض الطلب", - "request_needs_edit": "الطلب يحتاج تعديلاً", - "task_assigned": "إسناد مهمة", - "task_completed": "اكتمال مهمة" + "meeting_created": "اجتماع جديد" }, "status": { "pending": "بالانتظار", diff --git a/artifacts/tx-os/src/locales/en.json b/artifacts/tx-os/src/locales/en.json index 5f3cd4ba..ca4d3791 100644 --- a/artifacts/tx-os/src/locales/en.json +++ b/artifacts/tx-os/src/locales/en.json @@ -1181,13 +1181,7 @@ "reminder": "Reminder", "change": "Change notice", "cancel": "Cancellation notice", - "meeting_created": "Meeting created", - "request_submitted": "Request awaiting review", - "request_approved": "Request approved", - "request_rejected": "Request rejected", - "request_needs_edit": "Request needs edits", - "task_assigned": "Task assigned", - "task_completed": "Task completed" + "meeting_created": "Meeting created" }, "status": { "pending": "Pending", diff --git a/artifacts/tx-os/src/pages/executive-meetings.tsx b/artifacts/tx-os/src/pages/executive-meetings.tsx index a14da40c..dd89e4a6 100644 --- a/artifacts/tx-os/src/pages/executive-meetings.tsx +++ b/artifacts/tx-os/src/pages/executive-meetings.tsx @@ -18,8 +18,6 @@ import { Type, Languages, ListChecks, - // #262: ClipboardList, CheckSquare, ListTodo removed with the - // Requests / Approvals / Tasks tabs. Bell, ScrollText, FileText, @@ -146,11 +144,6 @@ type Meeting = { type DayResponse = { date: string; meetings: Meeting[] }; -// #262: MeRoles collapsed to the same shape as MeCapabilities (only one -// type definition is needed now that the Requests/Approvals/Tasks tabs -// are gone). RequestRow / TaskRow types removed alongside the sections -// that consumed them. - type AuditEntry = { id: number; action: string; @@ -224,8 +217,6 @@ function formatBytesForDisplay(bytes: number): string { return `${value < 10 && unitIdx > 0 ? value.toFixed(1) : Math.round(value)} ${units[unitIdx]}`; } -// #262: removed `requests`, `approvals`, `tasks` entries (and their -// icons) when the corresponding sections were retired. const SECTIONS = [ { key: "schedule", icon: CalendarClock }, { key: "manage", icon: ListChecks }, @@ -237,9 +228,6 @@ const SECTIONS = [ type SectionKey = (typeof SECTIONS)[number]["key"]; -// #262: canSubmitRequest, canViewTasks, canViewAllTasks dropped from /me; -// canApprove is kept because the global font-settings write path still -// gates on it. type MeCapabilities = { userId: number; roles: string[]; @@ -656,7 +644,6 @@ export default function ExecutiveMeetingsPage() { {section === "manage" && me && ( )} - {/* #262: Requests / Approvals / Tasks section blocks removed. */} {section === "notifications" && me && (