409b2c6fd51dff64168c61347c0971fe7e98721c
198 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f810afe0cd |
Task #349 follow-up: brand-logo embed test + PDF label module
Original task: Executive Meetings PDF improvements — respect user font prefs, render saved per-meeting rowColor, drop legacy isHighlighted baking, brand logo on left of PDF header with title "قائمة بأسماء حضور الاجتماعات", and a logo upload + font color picker in the font-settings page. Prior mark_task_complete was rejected for three issues. The first (stray backup files) was a false positive. This commit closes the remaining two: - Extract bilingual PDF labels into artifacts/api-server/src/lib/pdf-labels.ts and import it from the PDF route so the strings are no longer inlined inside the route handler. Mirror the same keys under executiveMeetings.pdf.* in the tx-os ar.json / en.json locales so the frontend stays in sync. - Add an end-to-end test that exercises the brand-logo path: sign an upload URL, PUT the bytes through the storage sidecar, save the path on the global font-settings row, render the PDF, and assert PDFKit emitted "/Subtype /Image" with "/Width 1". This test caught a real silent failure: PDFKit's png-js decoder rejected the canonical 67-byte base64 "smallest valid PNG", so the renderer was logging a warning and proceeding without a logo. The fixture now constructs a real 1x1 RGB PNG inline using zlib + a CRC32 routine (no new dependencies) and the assertion passes. Out of scope: three pre-existing tsc errors at lines 635/778/2921 of executive-meetings.ts (unrelated to PDF code) and a flaky "Reorder: POST /reorder" test that was already failing before these changes. |
||
|
|
57e8297464 |
Task #349: Executive Meetings PDF improvements
- Schema: add `font_color` (hex, default #000000) and `logo_object_path` to `executive_meeting_font_settings`. Pushed via drizzle-kit. - PDF renderer: - Add `fontColor` to PdfFontPrefs (body cells only; header chrome and logo intentionally ignore it to preserve branding). - Add `rowColor` to PdfMeeting and a ROW_COLOR_FILL palette kept in lockstep with the on-screen swatches; deliberately stop painting the legacy `isHighlighted` overlay so the archived PDF reflects editorial state instead of the viewer's transient cursor. - Add optional `logo: Buffer`. Header now reserves a left-anchored logo box and centers the title in the remaining width; bad image bytes log + fall back to the no-logo layout instead of crashing. - API route: - Extend fontSettingsSchema with strict #RRGGBB regex and /^/objects/<id>$/ regex for logoObjectPath. - resolveFontPrefsForUser now returns { font, logoObjectPath }. - loadLogoBytes downloads the brand asset via ObjectStorageService. - Logo only writable on the global-scope row. - PDF labels switched to "قائمة بأسماء حضور الاجتماعات" / "Meeting Attendance List" per the user's printed sample. - Frontend (executive-meetings.tsx): - FontPrefs gains fontColor; FontSettingsResponse.global gains logoObjectPath; DEFAULT_FONT and effectiveFont updated. - buildFontStyle applies fontColor to on-screen rows. - FontSettingsSection: native color picker + hex text input; logo upload (PNG/JPEG) via @workspace/object-storage-web's useUpload, visible only at the global scope for admins. - Locales: AR/EN keys for fontColor + logo.{label,upload,replace, remove,uploading,uploadFailed,globalOnly}. - Tests: existing font-settings roundtrip extended with fontColor; new test rejects malformed fontColor and non-/objects logo paths. Type-check clean for api-server and tx-os; new font-settings tests pass. Other test failures in the suite pre-date this change. |
||
|
|
3d2d80c250 |
Task #348: Arabic-friendly Executive Meetings audit log
- Reorder audit table columns to RTL order: المستخدم، الوقت، الإجراء،
النوع، التغييرات. Drop the entityId ("المعرف") column entirely. Update
loading/empty colSpan from 6 to 5 to match the new column count.
- Rename audit.col.actor copy: "المنفّذ" → "المستخدم" (ar.json) and
"Actor" → "User" (en.json). Underlying data field name unchanged.
- Localize the Changes cell in AuditDiffSummary:
- Field-name labels (titleAr, titleEn, meetingDate, dailyNumber,
startTime, endTime, location, meetingUrl, platform, status,
isHighlighted, notes, assignedTo, taskType, dueAt, reviewDecision,
reviewNotes) now resolve via executiveMeetings.audit.field.* with a
raw-key fallback so unknown future fields don't crash the row.
- Enum values render via existing label namespaces:
`platform` → executiveMeetings.platform.*, `status` →
executiveMeetings.status.*, `isHighlighted` → executiveMeetings.common.yes/no.
- Both the diff view and the single-side created/removed view use the
same labelForKey/fmtValue helpers.
- Added matching audit.field.* keys to ar.json and en.json with sensible
Arabic and English labels.
- No test changes needed: no existing test asserts on the old column
count, the "Actor"/"المنفّذ" header, the entityId column, or raw
English field names in Changes.
- tsc passes for @workspace/tx-os.
|
||
|
|
e695f5b4cb |
Improve attendee management by refining text handling and button labels
Introduce new utility functions for HTML to plain text conversion and paragraph wrapping in `safe-html.ts`. Update locale files (`ar.json`, `en.json`) to change "attendee" to "member" in the add button label. Modify `executive-meetings.tsx` to swap the functionality and labels of the "Add attendee" and "Add subheading" buttons. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: edd35722-74e8-4f8d-a3cc-bac6c5dccc51 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/Ow4s0aa Replit-Helium-Checkpoint-Created: true |
||
|
|
41cf24533e |
EM upcoming alert: Arabic copy tweaks (#346)
Two small Arabic-only string changes in the Upcoming Meeting alert
popup, requested off a screenshot:
1. Header title: "اجتماع يبدأ قريبًا" → "الاجتماع التالي".
2. Countdown line: replaced "خلال" with "بعد" so the alert reads
"يبدأ بعد 5 دقائق" instead of "يبدأ خلال 5 دقائق". Applied to all
plural forms (one/two/few/many/other). minutesAway_zero ("يبدأ
الآن") and the separate startsNow key were left unchanged.
Files:
- artifacts/tx-os/src/locales/ar.json
- executiveMeetings.alert.title
- executiveMeetings.alert.minutesAway_{one,two,few,many,other}
Out of scope (untouched on purpose):
- en.json — user only asked about the Arabic copy.
- upcoming-meeting-alert.tsx — no logic/layout change needed; it
already reads these keys via i18next.
- All other behavior (blink, drag, buttons, etc.) carried over from
#344.
Verification:
- ar.json still valid JSON; key set unchanged.
- Pre-existing red workflows (api-server, combined `test`) are
unrelated upstream failures and not caused by this change.
|
||
|
|
de9b5954ce |
EM Notifications tab: remove header + intro + per-user prefs card (#345)
The user pointed out that the "بريد إلكتروني" channel they could see in
the per-user "تفضيلات التنبيهات الخاصة بي" card is not part of the
product (no email delivery wired up), so exposing the toggle was
misleading. They sent a screenshot and asked to delete everything in
it: the page heading "التنبيهات", the intro paragraph, and the entire
prefs card (event row + in-app/email switches + Save / Reset / Restore
defaults buttons).
Changes:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- Removed the <h2> heading, the <p> intro, and the
<NotificationPrefsCard /> render call from the notifications page
component. The audit-log table below stays untouched.
- Deleted the NotificationPrefsCard component definition and the
NotificationPref / NotificationPrefsResponse types (only consumers
were inside the deleted card).
- artifacts/tx-os/src/locales/{ar,en}.json
- Removed executiveMeetings.notificationsPage.heading,
executiveMeetings.notificationsPage.intro, and the entire
executiveMeetings.notificationsPage.prefs.* subtree.
- Kept notificationsPage.col.*, type.*, status.*, empty — still used
by the audit-log table.
Out of scope (intentionally not touched):
- Backend /api/executive-meetings/notification-prefs routes and the
email channel in fan-out helpers. The backend defaults are left
intact so notification fan-out keeps working; only the UI surface
is removed.
- Tests: ripgrep showed no references to the removed test-ids
(em-notification-prefs, em-pref-inapp-*, em-pref-email-*,
em-pref-save, em-pref-reset, em-pref-restore-defaults) in the
Playwright/unit suites, so nothing to update.
Verification:
- pnpm -C artifacts/tx-os exec tsc --noEmit clean.
- Switch import retained: still used by row-color popover and another
call site in the same file.
- The api-server workflow and combined `test` workflow are red with
pre-existing upstream failures unrelated to this UI-only change.
|
||
|
|
db6b835f75 |
feat(executive-meetings): replace native window.confirm with in-app AlertDialog (#342)
Native window.confirm exposes the Repl hostname and ignores the app's
RTL/branding. The user (Arabic-speaking) flagged it as visually jarring
inside Executive Meetings.
Changes (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Add a small ConfirmProvider + useConfirm() backed by the existing
shadcn AlertDialog (`@/components/ui/alert-dialog`). The hook returns
Promise<boolean> so call sites stay one-liners:
if (!(await confirm({ message, destructive: true }))) return;
- Single-flight guard: if a second confirm() arrives while the first is
still pending, the previous Promise resolves to false (no orphaned
resolvers).
- Mount the provider once around the page (split the default export
into a thin wrapper + ExecutiveMeetingsPageInner so the inner tree
can call useConfirm()).
- Centered title + description, destructive-styled confirm button,
mobile-safe sizing (max-w-md, w-[calc(100%-2rem)], max-h-[90vh],
overflow-y-auto). Explicit `dir` on AlertDialogContent so the
portal-rendered dialog always inherits the page's RTL/LTR.
- Footer renders Action first then Cancel: in LTR Confirm sits on the
left and Cancel on the right; RTL flips automatically (Confirm right,
Cancel left). Mobile stacks Cancel on top, destructive at the bottom.
- Replace all 5 window.confirm sites:
1. Schedule single-row delete (deleteMeeting)
2. Schedule bulk delete
3. Manage bulk delete
4. Manage single delete (confirmDelete)
5. MeetingFormDialog "remove all attendees" (function turned async)
- Add `confirm` to the relevant useCallback deps (deleteMeeting,
bulk delete) and inject useConfirm() inside ScheduleSection,
ManageSection, MeetingFormDialog (all rendered inside the provider).
- New i18n keys: executiveMeetings.common.confirm and .confirmTitle in
both ar.json and en.json (used as default title / non-destructive
confirm label).
`pnpm -C artifacts/tx-os exec tsc --noEmit` clean. Pre-existing
api-server / test workflow failures are unrelated to this UI change
(also failing before the task started).
|
||
|
|
f479fa5498 |
Remove unnecessary confirmation message after acknowledging alerts
Remove the `doneToast` locale keys and the toast notification call in `upcoming-meeting-alert.tsx`. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 320c7da3-19c9-48af-ab28-f9af7aad21dd Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/vKbTeqB Replit-Helium-Checkpoint-Created: true |
||
|
|
104ae5526d |
Add functionality to duplicate multiple selected meetings to a future date
Adds a bulk duplicate option to the toolbar, including a dialog for selecting the target date, and implements the API calls and state management for this feature. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 13d92594-1814-45b7-83b2-862ed06fe2dc Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/vKbTeqB Replit-Helium-Checkpoint-Created: true |
||
|
|
0fda08169a |
#329 Add a meeting search box to the schedule toolbar
Lets users find a meeting in the day's schedule by typing part of its
title, an attendee name, or other text — instead of scrolling.
artifacts/tx-os/src/pages/executive-meetings.tsx
- Added two top-level helpers near translateSaveError:
- normalizeForSearch(input): strips HTML tags + entities, drops
Arabic diacritics + tatweel, folds common letter variants
(آأإٱ→ا, ى→ي, ة→ه, ؤ→و, ئ→ي), lowercases.
- meetingMatchesSearch(meeting, needle): tests title (AR/EN),
attendee names + titles, location, notes, and merge text via
normalized substring containment.
- ScheduleSection: added searchQuery state, useEffect that resets it
whenever the active `date` changes, normalizedSearch memo, and a
filteredMeetings memo derived from orderedMeetings.
- Switched the row render path to filteredMeetings: SortableContext
items, the .map render, and the select-all overlay's "anything to
select" check now read from the filtered list. The
setAllMeetingsSelected and allSelectedState helpers also operate
on filteredMeetings so the header checkbox + select-all reflect
what's actually visible.
- Added a new "no matches" empty-state row that appears only when
the day has meetings but the active search filters them all out
(the original "no meetings today" row is preserved for empty days).
- New search Input + clear-X button placed in the schedule toolbar,
next to the existing date picker. Hidden in print so PDFs export
the full schedule.
- MeetingRow gained an optional `displayNumber` prop. The "#" cell
now renders displayNumber when provided, falling back to the
server's dailyNumber. The schedule passes idx+1, so visible rows
always show 1, 2, 3… without gaps when the list is filtered.
artifacts/tx-os/src/locales/ar.json + en.json
- New keys under executiveMeetings.schedule: searchPlaceholder,
searchAria, searchClear, searchNoMatches.
- Bulk delete now intersects the selection with filteredMeetings at
action time. The selection set is intentionally NOT auto-pruned
when the search narrows (so clearing the search restores the
user's earlier picks), but destructive actions only ever target
rows the user can currently see.
Out of scope: cross-day search, prev/next-day chevron buttons,
search history, fuzzy matching.
Verified: `tsc --noEmit` clean.
|
||
|
|
41c0c30cb8 |
#326 Show meeting save errors in Arabic, centered at the bottom
Two small UX fixes for the Add/Edit Meeting save toast. artifacts/tx-os/src/pages/executive-meetings.tsx - Added a `translateSaveError(rawMsg, t)` helper near the other top-level helpers (~line 425). It substring-matches the API's Zod-generated message "startTime must be <= endTime" and returns the translated copy from `executiveMeetings.manage.errors.endTimeBeforeStart`. Anything unknown falls through to the raw server message so unexpected errors aren't silently hidden. - The save catch block now passes the message through this helper before showing the toast. Title still uses `errors.saveFailed`. artifacts/tx-os/src/locales/ar.json + en.json - New translation key `executiveMeetings.manage.errors.endTimeBeforeStart`: - ar: "وقت الانتهاء يجب أن يكون بعد وقت البدء" - en: "End time must be after start time" artifacts/tx-os/src/components/ui/toast.tsx - ToastViewport: replaced the mobile-top + desktop-bottom-right positioning with a single bottom-center placement on every viewport (`fixed bottom-0 left-1/2 -translate-x-1/2`). Width is still capped at `md:max-w-[420px]`. The flex direction is `flex-col` (no longer `flex-col-reverse`) since there is now only one slide-in direction. - toastVariants: dropped `data-[state=open]:slide-in-from-top-full` so toasts slide in from the bottom on every breakpoint, matching the new viewport position. Out of scope (untouched): API validation, other dialogs/pages, preventive client-side validation for invalid time ranges. |
||
|
|
5cb1644300 |
Update meeting form layout and Arabic title label
Refactor executive meetings form layout in `executive-meetings.tsx` to use a responsive grid, ensuring the Arabic title spans full width and other fields stack appropriately on smaller screens. Update Arabic and English locale files (`ar.json`, `en.json`) to change the Arabic title field label from "العنوان (عربي)" to "العنوان". Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 57375b7d-57d9-4fe3-a745-00a2cad59df8 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/2VzZgdn Replit-Helium-Checkpoint-Created: true |
||
|
|
e63a7f8193 |
Task #315: 12h time picker with explicit AM/PM (executive meetings)
Replaces native <input type="time"> in the executive-meetings editor with a TimePicker12h that always requires an explicit AM/PM choice for any 1–12 hour, fixing the "1:15 silently saved as 01:15 (AM) when user meant 13:15 (PM)" bug. Display stays compact 12h with no AM/PM marker (per #292); wire format remains canonical HH:mm 24h. What ships - src/lib/time-12h.ts: periodFromCanonical, formatCanonicalAs12h, combineTextWithPeriod (returns "ambiguous" for any unmarked 1–12 hour without a toggle pick; accepts hours 0 and 13–23 directly because they are unambiguous 24h shapes; typed marker always wins over toggle). - src/components/time-picker-12h.tsx: forwardRef component with imperative {commit, focus, select} handle, dirtyRef to avoid prop overwrite mid-edit, dir="ltr" for stable RTL layout, and Enter-on-toggle = "set AND save". - Wired into TimeRangeCell inline editor (commit-based save handles invalid/ambiguous/TimeOrderError as toasts and refocuses). - Wired into MeetingFormDialog with picker refs + handleSaveClick validation: Save button now calls commit() on both pickers, blocks on invalid/ambiguous (toast + focus offending picker), and passes canonical values straight to the parent's save handler so a stale React state batch from onChange-suppression cannot silently persist the previous value. Tests - 12 unit tests in src/__tests__/time-12h.test.mjs (incl. new "hour 0 is unambiguous, accepted without a toggle"). - 17 e2e tests in tests/executive-meetings-keyboard-editing.spec.mjs (incl. "compact + PM toggle" 0115→13:15 and "Tab walks start input → start AM/PM toggle → end input → end AM/PM toggle"). - Existing manage-create e2e suite still green. Locale keys (en + ar) - executiveMeetings.timeEditor.{am,pm,periodGroupStart,periodGroupEnd} - executiveMeetings.schedule.timeAmbiguousError Deviations from plan - Architect review #1 caught two issues that were both fixed: (1) combineTextWithPeriod treated hour 0 as ambiguous; now accepts 00:xx unambiguously. (2) MeetingFormDialog initially used pickers via value/onChange only, which left a silent-fallback hole because onChange suppresses ambiguous drafts. Added imperative refs + handleSaveClick validation; onSave signature changed to (committedStart, committedEnd) so the parent's save() can use the freshly-committed values directly. - Reverted accidental vite.config.ts typo (@replit/... was missing the leading @). |
||
|
|
e4aa4fa280 |
Improve time input functionality and fix related test cases
Replace native time input with a custom text input and parser, enabling support for various time formats and fixing associated end-to-end tests. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 4ef80303-e96f-4ad0-ba0f-8fce49b7b340 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/HJW50TH Replit-Helium-Checkpoint-Created: true |
||
|
|
5748ec773b |
#307 Polish UpcomingMeetingAlert: AR plurals, 12h time, details table
- locales/{ar,en}.json: split minutesAway / postponeMinutesApply /
postponeConfirmPrompt / postponeConfirmYes / staleMeetingApplyAnyway
into i18next plural variants (AR uses zero/one/two/few/many/other).
Switched call sites to pass `count` so plural detection triggers.
- Added cascadeMinutesUnit_* AR/EN unit keys; cascadePromptHeader_* now
interpolates a pre-formatted {{minutesText}} so the {{minutes}} unit
inside the cascade prompt obeys the same Arabic plural rules.
- upcoming-meeting-alert.tsx: replaced the local 24h `formatTime` with
a 12-hour formatter that delegates to lib/i18n-format.formatTime with
`hour12: true`, keeping Latin digits. Applied to the popup time
window, the stale-meeting current-time line, and the new details row.
- DetailsPanel rewritten as a 2-col label/value grid (time, location,
meeting link, attendees grouped by internal/external/virtual). Empty
rows are skipped. Existing test-ids (alert-details-panel,
alert-details-attendees, alert-details-no-attendees, alert-details-url)
preserved; added alert-details-time / alert-details-location.
- Removed the postponeMinutesHint paragraph (and key from both locales);
the noTimeWindow warning still renders when a meeting has no times.
- PostponeDialog now also reads i18n.language so stale-meeting times
render in the user's locale.
tsc + the full executive-meetings-upcoming-alert.spec.mjs Playwright
suite (18 tests) pass after clearing leftover seeded test data.
|
||
|
|
446a83b5a7 |
#302 cascade-shift later meetings on postpone/reschedule (review fixes)
Backend (artifacts/api-server/src/routes/executive-meetings.ts):
- New helpers computeCascadeShift + applyCascadeShift; new schema
cascadePreviewSchema; cascadeFollowing flag on postpone-minutes
and reschedule.
- POST /executive-meetings/:id/cascade-preview returns followers +
blockedBy.
- Writer paths now SELECT followers FOR UPDATE inside the txn so
concurrent mutations cannot lose updates and audit oldStart/oldEnd
always reflect the locked-current values (architect feedback).
- Reschedule only cascades when same date AND newStart > oldStart.
- Midnight rejection rolls back the primary too; named offender is
surfaced verbatim to the UI.
- One meeting_cascade_shift audit per follower (trigger meeting +
delta in the row for replay).
Frontend (artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx):
- CascadePromptBlock with loading / blocked / normal variants and
data-testids cascade-prompt[-blocked|-loading], cascade-keep-times,
cascade-shift-following, cascade-back, cascade-follower-{id}.
- runCascadePreview helper with in-flight + busy guard against
duplicate submissions; falls through to single-meeting submit when
no followers and not blocked.
- rescheduleSubmit extracted; cascade_crosses_midnight from server
is caught and re-rendered as the blocked variant.
i18n: cascade* keys under executiveMeetings.alert in en.json + ar.json
(Arabic plural zero/one/two/few/many/other for header + shift action).
ALSO added `executiveMeetings.audit.action.meeting_cascade_shift` in
both locales so the History view renders a localized label instead of
falling back to the raw action code (review feedback).
Tests:
- 8 cascade backend tests in executive-meetings.test.mjs (preview
shape, atomic shift, skip cancelled/completed, midnight rollback,
reschedule delta + no-op cases) all pass.
- New e2e "Reschedule cascade — opting in shifts all later same-day
meetings" (API-driven backend contract).
- NEW UI dialog test "Cascade prompt UI: Shift sends
cascadeFollowing=true; Keep sends false" — opens the postpone
confirm, mocks cascade-preview to a deterministic single-follower
response, then verifies (via request interception of
/postpone-minutes) that Shift sends cascadeFollowing=true and Keep
sends cascadeFollowing=false (review feedback).
- Made two existing tests (Postpone by 10 minutes, conflict warning)
pollution-tolerant by polling for either the cascade prompt or the
meeting-postponed audit row.
Pre-existing flaky tests (notifications fan-out, postpone race, row
color realtime, reorder, status transitions, alert-done/close
realtime, reschedule-different-day visibility, cancel/renumber strict
mode) are not related to this work and were not modified.
Drift note: prior commit picked up an incidental opengraph.jpg byte
diff (25906 -> 25929 bytes) from the tx-os web workflow restart — no
content change visible to this task; left in place as removing it
requires a destructive git operation.
|
||
|
|
c64e93c146 |
#302 cascade-shift later meetings on postpone/reschedule
Backend (artifacts/api-server/src/routes/executive-meetings.ts):
- New helpers computeCascadeShift + applyCascadeShift; new schema
cascadePreviewSchema; cascadeFollowing flag on postpone-minutes
and reschedule.
- POST /executive-meetings/:id/cascade-preview returns followers +
blockedBy.
- Writer paths now SELECT followers FOR UPDATE inside the txn so
concurrent mutations cannot lose updates and audit oldStart/oldEnd
always reflect the locked-current values (architect feedback).
- Reschedule only cascades when same date AND newStart > oldStart.
- Midnight rejection rolls back the primary too; named offender is
surfaced verbatim to the UI.
- One meeting_cascade_shift audit per follower (trigger meeting +
delta in the row for replay).
Frontend (artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx):
- CascadePromptBlock with loading / blocked / normal variants and
data-testids cascade-prompt[-blocked|-loading], cascade-keep-times,
cascade-shift-following, cascade-back, cascade-follower-{id}.
- runCascadePreview helper with in-flight + busy guard against
duplicate submissions; falls through to single-meeting submit when
no followers and not blocked.
- rescheduleSubmit extracted; cascade_crosses_midnight from server
is caught and re-rendered as the blocked variant.
i18n: cascade* keys under executiveMeetings.alert in en.json + ar.json
(Arabic plural zero/one/two/few/many/other for header + shift action).
Tests:
- 8 cascade backend tests in executive-meetings.test.mjs (preview
shape, atomic shift, skip cancelled/completed, midnight rollback,
reschedule delta + no-op cases) all pass.
- New e2e "Reschedule cascade — opting in shifts all later same-day
meetings".
- Made two existing tests (Postpone by 10 minutes, conflict warning)
pollution-tolerant by polling for either the cascade prompt or the
meeting-postponed audit row.
Pre-existing flaky tests (notifications fan-out, postpone race, row
color realtime, reorder, status transitions, alert-done/close
realtime, reschedule-different-day visibility, cancel/renumber strict
mode) are not related to this work and were not modified.
|
||
|
|
0dc84d69f6 |
Shorten the Executive Meetings page header
The Executive Meetings screen used to show a two-line header next to the
calendar icon: a primary "إدارة الاجتماعات التنفيذية" / "Executive
Meetings Management" line and a smaller secondary line in the opposite
language under it. The user asked for a single short title and no
second-language line beneath it.
Changes:
- artifacts/tx-os/src/locales/ar.json + en.json
- Removed the dual-line keys executiveMeetings.titleAr and
executiveMeetings.titleEn.
- Replaced them with a single executiveMeetings.title key whose
locale-resolved value is "قائمة الاجتماعات" (Arabic) and
"Meetings list" (English).
- artifacts/tx-os/src/pages/executive-meetings.tsx
- Page header: removed the second smaller-text <div> entirely and
pointed the remaining bold line at the new key.
- Print-only daily-schedule header (around line 1960): also pointed
at the new key for consistency on the printed page.
The renamed key is more honest than the old titleAr/titleEn pair, which
no longer made sense once we collapsed to a single line.
Out of scope (untouched, per plan):
- The bilingual page subtitle / sidebar item shown in the user's
screenshot — those live elsewhere.
- Browser tab title, alert popup title, and any other Executive
Meetings surface.
- Header layout, color, or icon.
Verification:
- TypeScript: clean.
- e2e (testing skill): in Arabic locale the header shows the new
"قائمة الاجتماعات" title and neither old long string is present
anywhere on the page; after switching to English locale the header
becomes "Meetings list" and again the old strings are gone.
|
||
|
|
75272cfb51 |
Remove repeated "+ شخص هنا" chip from attendee cells
The Executive Meetings schedule used to render a small dashed "+ شخص هنا"
("+ person here") chip in the gap between every two consecutive person
rows of an attendee cell. With three attendees the user saw three chips
stacked under the names, which looked noisy and repetitive. The user
asked for the chip to be removed entirely; the existing trailing "+"
button at the end of the cell already covers the add-person flow.
Changes:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- Removed the inter-person chip render block (the `<li>` + `<button>`
that used data-testid="em-add-person-after-row-*" /
data-testid="em-add-person-after-*"). Left a short comment in place
of the removed block explaining what was there and why it went.
- Removed the now-unused `addPersonHereLabel` from the props type, the
component's destructured parameters, and the parent's `t(...)` call.
- artifacts/tx-os/src/locales/en.json + ar.json
- Removed the orphaned `executiveMeetings.schedule.addPersonHere` key.
Out of scope (per plan, untouched):
- The "+ subheading" / "+ عنوان فرعي" inter-row chip that appears just
before a subheading row.
- The trailing "+ Virtual" / "+ Internal" / "+ External" add chips that
appear when a group is empty.
- The per-row trailing "+" button and the inline add-row flow.
Verification:
- TypeScript: clean.
- e2e (testing skill): on /executive-meetings, the page contains zero
elements matching the old chip's data-testid prefixes and zero
occurrences of the literal text "+ شخص هنا" or "+ person here", while
the schedule and attendee lists render normally.
|
||
|
|
80d6267599 |
Tidy the upcoming-meeting alert Details panel
The expanded Details panel had two visible problems pointed out by the user: 1. The "Notes / الملاحظات" block always rendered — including a "No notes added." fallback for meetings without notes — adding empty noise the user did not want. 2. Attendee names were printed as plain text but the database stores them as sanitized rich-text HTML, so they showed up as literal "<p>محمد علي</p>". Changes: - Removed the entire Notes block from the DetailsPanel sub-component in artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx. Dropped the unused StickyNote icon import and the now-unused notesText constant. - Each attendee name is now rendered with the same HTML-stripping pattern the popup already uses for the meeting title: name.replace(/<[^>]+>/g, "").trim() || name. - Removed the now-unused i18n keys executiveMeetings.alert.detailsNotes and detailsNoNotes from both en.json and ar.json. Out of scope (per plan): no schema/API changes, no rich-text rendering of attendee names, no color/toggle changes. Verification: - TypeScript: clean. - e2e (testing skill): a fresh meeting with HTML-tagged attendee names was created, the popup expanded, and the test confirmed (a) no Notes section / "alert-details-notes" testid present, (b) attendee names visible without "<p>", "</p>", "<span>" tags, and (c) the meeting URL link still renders. |
||
|
|
fd4d99a97e |
Add settings to customize meeting alert appearance and behavior
Implement a settings card for upcoming meeting alerts, allowing users to enable/disable the alert, choose color presets, and reset to defaults. Modify the alert component to respect these preferences and add a "Details" toggle to expand and display meeting information. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a4e71a69-cd61-4758-aeb2-ee141845ce95 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/GMnW1ol Replit-Helium-Checkpoint-Created: true |
||
|
|
f2c031e1fe |
Task #292: Show schedule meetings in 12h format and add LTR Start/End labels to inline editor
What changed:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- formatTime helper now uses hour: "numeric", minute: "2-digit", hour12: true
(was hour12: false). All on-page time displays — schedule cells and the
Manage tab list — flow through this single helper. Latin digits remain
enforced via the shared i18nFormatTime helper, so Arabic renders as
"5:39 م" rather than mixing Arabic-Indic numerals into the table.
- Inline time editor (InlineTimeEditor):
- Wrapper locked to dir="ltr" so start-on-left / end-on-right (and
save/cancel after end) layout never mirrors under RTL.
- Each <input type="time"> wrapped in a vertical mini-column with a
small muted <label> above ("Start"/"End" or "البدء"/"الانتهاء").
- Stable per-meeting input ids (em-time-{start,end}-input-<id>) wired
via htmlFor so click-to-focus works and screen readers don't jump
across rows. Existing aria-labels preserved for accessibility.
- All existing data-testids preserved; keyboard (Enter/Escape) and
blur-to-save behavior unchanged.
- artifacts/tx-os/src/locales/en.json + ar.json
- Added executiveMeetings.schedule.timeStartShort ("Start"/"البدء")
and timeEndShort ("End"/"الانتهاء"). Existing timeStart/timeEnd
("Start time"/"End time") were too long for the compact label slot
above the time inputs and remain in use as aria-labels.
Out of scope (verified):
- API-side PDF renderer at artifacts/api-server/src/lib/pdf-renderer.ts
already uses hour12: true (line 291) — no change needed.
- No tx-os print/PDF page exists; scope confined to the schedule page.
- Audit log timestamps, home/chat clocks, clockHour12 user setting:
out of scope, untouched.
Verification:
- TypeScript clean.
- E2E (English): visual screenshot confirmed schedule shows
"5:39 PM – 5:50 PM" / "6:20 PM – 6:25 PM"; inline editor shows Start/End
labels above inputs in correct LTR order.
- E2E (Arabic): runTest passed — time cells use Latin digits with ص/م
markers; inline editor shows البدء/الانتهاء labels with LTR layout
(start-on-left, end-on-right, save/cancel on right).
- Architect code review: PASS, no critical issues.
- No existing test asserts a specific 24h display string, so no e2e
test breakage introduced.
Notes:
- Pre-existing test workflow failures on executive-meetings*.test.mjs
(500 errors on meeting create + Reorder 400 vs 200) are unrelated to
this task — they pre-date both this task and Task #205.
- Re-applies and extends the 12h direction from earlier Task #160 which
had regressed back to 24h.
|
||
|
|
49bf44f0a2 |
Add functionality to download role permission audit history as a CSV file
Introduce a new admin-only API endpoint for exporting role permission audit data to CSV, including resolved permission names and UTF-8 BOM for Excel compatibility. Frontend button and backend logic implemented to support this feature. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 0cb48020-8f0c-42bb-8fe8-d638905f7fce Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/g7BgHDL Replit-Helium-Checkpoint-Created: true |
||
|
|
6876a83bbb |
Audit log: filter by actor (#197)
Admins can now filter the audit log by who acted, not just by what was
acted on.
User-facing changes
- Replaced the flat actor <select> with an autocomplete combobox
(Popover + cmdk Command) that searches by username AND display name,
in English or Arabic.
- Added an `actorId` URL hash param that survives full reload — picks
up alongside the existing target filter on initial mount and is
cleared when the admin navigates to a different section.
- Each audit row's actor avatar + name is now clickable, mirroring the
existing target chip pivot, so admins can jump from "this row" to
"everything by this person" in one click. Rows with a null actor
(system / deleted user) render the same avatar/name without the
click affordance, since the API can only filter by a concrete id.
- Active actor pill renders in sky (target pill remains emerald) and
has a clear button.
- CSV export already accepted `actorUserId` on the backend; the
frontend export call now forwards the filter and the OpenAPI
description has been updated to document it.
Files
- artifacts/tx-os/src/pages/admin.tsx
- New AuditActorPicker component, hash helpers
(parseAuditHashActor / syncAuditHashActor), pivotToActor /
clearActorFilter handlers, sky-colored active pill, clickable
actor in AuditLogRow.
- artifacts/api-server/tests/audit-logs-actor-filter.test.mjs (new)
- 6 tests covering: filter narrows results, excludes other actors
on the same target_type, combines with target filter, invalid
/zero/negative actorUserId → 400, CSV export honors filter.
- artifacts/tx-os/src/locales/{en,ar}.json
- actorSearch / actorEmpty / actorWithName / actorWithId /
clearActorFilter / actorPivotAria.
- lib/api-spec/openapi.yaml
- Audit export endpoint description now mentions
targetType / targetId / actorUserId.
Verification
- pnpm --filter @workspace/tx-os run typecheck → clean.
- All 6 new actor-filter tests + all 7 existing target-filter tests
pass against a live API server.
- E2E run via runTest() succeeded: opened the picker, selected an
actor, verified the pill + reload-safe hash, cleared, pivoted via
a row click, and confirmed CSV export honored the filter.
Notes / drift
- URL key is `actorId` (per task spec), but the React state and API
param remain `actorUserId` to match the existing backend.
- The broader `test` workflow has pre-existing failures in
executive-meetings.test.mjs and service-orders.test.mjs unrelated
to this change (admin 401 / HTML-404 fallback). Captured as
follow-up #291.
Replit-Task-Id: 0f02b232-eda3-46db-8235-98ecce2ebdb7
|
||
|
|
2a005646f3 |
Task #196: Add inline "Recent activity" sections to admin detail panels
- Added shared `RecentActivityForTarget` component in `artifacts/tx-os/src/pages/admin.tsx` that lists the 10 most recent audit entries for a given (targetType, targetId), reusing `formatAuditSummary` for consistent wording with the audit log section, plus actor + timestamp metadata. Renders loading / empty / list states with stable data-testids.
- Added `openAuditLogForTarget(targetType, targetId)` helper inside `AdminPage`. Writes the URL hash with `section=audit-log&targetType=<t>&targetId=<n>` BEFORE calling `setSection("audit-log")` so the existing section-sync effect (which would otherwise drop section-scoped params on a section change) preserves the deep-link parameters. Also closes any open editor modals.
- Wired the component into all five entity detail panels:
* App edit modal
* Service edit modal
* `UserGroupsEditor` (user edit)
* `GroupDetailEditor` (history tab)
* Role edit dialog
GroupsPanel and RolesPanel previously took no props; both now accept and forward an `onOpenAuditLogForTarget` prop. `OpenAuditLogForTarget` type is shared.
- Added i18n keys `admin.audit.recent.{title,loading,empty,viewAll,viewAllAria}` in both `en.json` and `ar.json`.
- Verified end-to-end via Playwright runTest: deep-link hash, modal closure, and audit-log filter pre-population all work for app/service/group/role/user panels.
No backend changes required (the existing `targetType`/`targetId` filter on `GET /api/audit` was already supported).
Replit-Task-Id: c03451f9-a6bb-4dd4-b082-f34ce3b6001d
|
||
|
|
1b1697c450 |
Add conflict detection and resolution for meeting postponements
Implement optimistic locking for meeting postponements to prevent concurrent edits. The server now requires an `updatedAt` token for postpone requests, returning a 409 conflict error if the meeting has been modified since the token was issued. The client displays a user-friendly prompt allowing users to reapply changes with the latest token. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 1abdc3d2-c834-4a37-b104-397852e4732a Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/8gwn7Xm Replit-Helium-Checkpoint-Created: true |
||
|
|
b54761b166 |
Task #192: Show row color + merge range previews in row-actions kebab menu
Original task: The Executive Meetings row-actions kebab menu has three sub-views
(delete, color, merge). Users couldn't tell at a glance which colour or merge
range a row already had — they had to drill into the sub-view first. Add subtle
inline indicators to the main menu so the current state is visible without
extra clicks.
Implementation:
- artifacts/tx-os/src/pages/executive-meetings.tsx
• RowActionsMenu now renders an inline circular swatch next to the "Row
color" item, reflecting the row's saved colour. "default" shows a hatched
pattern matching the swatch picker; other keys show the saved tint.
Decorative (aria-hidden) since the swatch sub-view is the actual control.
Carries data-testid="em-row-color-indicator-{id}" plus a data-color-key
attribute for stable, language-agnostic tests.
• Renders a "Merged: <cols>" badge next to the "Merge cells" item only when
the row has a stored merge. Column names come from the canonical merge
order (number, meeting, attendees, time) and are localized via the
existing executiveMeetings.col.{id} keys.
• New mergeColumnLabels prop is computed in MeetingRow against
CANONICAL_MERGE_ORDER (not visibleColumns) so the badge still describes
the full saved range when a boundary column is hidden. Threaded into
both call sites — the first-visible-cell anchor and the merged-cell
anchor.
• Widened the local t prop type to (k, opts?) so the badge can use the
{{cols}} interpolation template.
- artifacts/tx-os/src/locales/en.json + ar.json
• Added executiveMeetings.merge.activeBadge ("Merged: {{cols}}" /
"مدموج: {{cols}}").
Tests:
- Added artifacts/tx-os/tests/executive-meetings-row-actions-previews.spec.mjs
with two specs:
1. Row-color indicator updates from "default" → "red" → "default" via the
sub-view picker, and the dot's computed background tracks the saved
colour (#fee2e2 → rgb(254,226,226)).
2. Plain rows render no merge badge; merged rows render the badge with
text "<MeetingColLabel> + <AttendeesColLabel>". Column labels are
resolved from the live header DOM so the assertion works in both
English and Arabic (admin's preferredLanguage overrides the
tx-lang=en init seed after login).
Both pass; existing executive-meetings-row-actions-menu.spec.mjs and
executive-meetings-merge.spec.mjs continue to pass (5/5 regression).
Notable subtleties:
- A first attempt at the merge spec hit a flake where Escape-then-force-click
on the next row's kebab landed on the previous popover's Delete item
(which was still mounted), triggering a stray confirm dialog. Added an
explicit waitFor on the previous popover's unmount before opening the
next one.
Follow-up proposed: #276 (next_steps) — show the same cues directly on the
row itself, not only inside the kebab popover.
Replit-Task-Id: 7ff92dcd-f4bd-421d-93c8-4d7d3dbe0077
|
||
|
|
84c2efc7a9 |
Task #275: Postpone confirm + compact 3-tab dialog
Refactor the 5-min upcoming-meeting alert's postpone sub-dialog: - Add an explicit "Are you sure?" confirm step before any postpone action (matches the existing Cancel pattern). Both minute chips (5/10/15/30/45/60) and the manual "Apply now" button now route through requestPostpone() -> amber confirm block -> postponeBy(). Switching tabs clears any armed pending-postpone state. - Replace the three stacked bordered sections with a compact 3-tab layout (Postpone / Reschedule / Cancel). Default tab is Postpone. Reschedule grid is responsive (grid-cols-1 sm:grid-cols-3) so the dialog no longer overflows on common laptop viewports. - Implement full ARIA tab pattern: role="tablist"/"tab"/"tabpanel", id + aria-controls + aria-labelledby, roving tabindex, and ArrowLeft/Right/Home/End keyboard nav (RTL-aware). Focus moves to the newly active tab. - Bilingual: added EN+AR keys (tabPostpone, tabReschedule, tabCancel, postponeConfirmPrompt, postponeConfirmYes, rescheduleHint), trimmed postponeIntro, removed obsolete *Label keys. - Spec updated: existing "Postpone by 10", "chip immediately shifts", "Reschedule", and both "Cancel" tests now click the new tabs and go through the confirm step. Added two new tests: "Back on postpone confirm leaves meeting unchanged" and "switching tabs clears armed postpone confirm". Out of scope: server route changes, realtime alert-state push, the floating alert panel itself. Architect review: PASS / mergeable. All 11 spec tests pass in isolation; intermittent batch-run flakes were traced to leftover seeded test rows from prior runs (cleaned) and pre-existing polling tightness, not regressions. |
||
|
|
0cf44b62b2 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
a72c00fe19 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
- New `executive_meeting_alert_state` table (per-user, per-meeting) tracking `dismissed`/`acknowledged`. Migration via `pnpm --filter @workspace/db run push-force`. - New API routes in artifacts/api-server/src/routes/executive-meetings.ts: GET /alert-state, POST /:id/alert-state, POST /:id/postpone-minutes, POST /:id/reschedule, POST /:id/cancel. All three mutation routes acquire a row lock with SELECT ... FOR UPDATE inside the transaction, compute oldValue from the locked snapshot, run conflict detection in the same tx, and write the audit row before commit. Cancel is idempotent. Postpone-minutes rejects ranges that would cross midnight (use reschedule for cross-day moves). - Alert-state route uses race-safe onConflictDoNothing upsert + a conditional UPDATE ... RETURNING so transition audits never duplicate. - New i18n keys `executiveMeetings.alert.*` in en.json + ar.json, including the cancel-confirm prompt. - New component artifacts/tx-os/src/components/executive-meetings/ upcoming-meeting-alert.tsx — globally mounted in App.tsx inside AuthProvider. Draggable with localStorage position persistence, RTL-aware, polls every 30s, shows start–end window, postpone-by- minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a Cancel-meeting flow that requires an explicit confirm step before the destructive call fires (gated on confirmCancel state). - Playwright spec executive-meetings-upcoming-alert.spec.mjs with 6 scenarios: appear+Done, postpone-10 shifts times, cancel-with- confirm, dismiss audit, postpone-chip+conflict-warning toast, AR/RTL render. All 6 pass. - replit.md updated with the new table, routes, and migration step. Pre-existing tsc errors at lines 489, 605, and 2039 of executive-meetings.ts are not touched by this task. |
||
|
|
310baa41ab |
#265: unify Executive Meetings header into single Settings tab
Header
- Removed the standalone Font Settings nav button and the bilingual
language toggle. The header now exposes only Export PDF.
- Renamed the SECTIONS key `fontSettings` → `settings` (Settings icon
retained) and updated visibility checks + render switch.
Settings tab
- New SettingsSection wraps the existing FontSettingsSection plus a
new ColumnsCustomizerPanel (extracted from the old popover body) so
column visibility / current-meeting highlight live with the rest of
user prefs.
- Removed the old ColumnsCustomizer popover trigger from the schedule
toolbar; the inline panel inside Settings is the single entry point.
State lifting
- columns/setColumns and highlightPrefs/setHighlightPrefs lifted from
ScheduleSection up to ExecutiveMeetingsPage so both tabs read/write
the same state. Storage keys (COLS_STORAGE_KEY, HIGHLIGHT_STORAGE_KEY)
unchanged so existing user prefs continue to load.
- Per code-reviewer follow-up: moved the columns localStorage write
effect up to the page as well so edits made in Settings persist even
when ScheduleSection is unmounted.
Locales
- ar.json + en.json: renamed nav.fontSettings → nav.settings
("الإعدادات" / "Settings"); removed the now-unused
executiveMeetings.fontSettings header label.
- Removed unused Languages and Type lucide imports.
Tests
- Updated executive-meetings-bulk-actions.spec.mjs and
executive-meetings-schedule-features.spec.mjs to navigate via
em-nav-settings instead of the removed em-customize-columns-trigger.
- All 226 sequential api tests pass; both updated playwright specs
pass.
|
||
|
|
389e8b785c |
#262: remove Requests / Approvals / Tasks tabs from Executive Meetings
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.
|
||
|
|
21c935064d |
#262: remove Requests / Approvals / Tasks tabs from Executive Meetings
Full-stack removal of the three retired sections — UI, locales, realtime
invalidations, backend routes, role lists, capability flags, schema
tables, notify lib, and tests.
Backend (artifacts/api-server)
- routes/executive-meetings.ts: deleted /requests* + /tasks* handler
block, REQUEST_ROLES / TASK_VIEW_ROLES / TASK_BROAD_VIEW_ROLES,
canSubmitRequest / canViewTasks / canViewAllTasks from /me, retired
table imports, and dead schemas (detailsByType, requestPayloadSchemas,
request*Schema, taskCreateSchema, taskPatchSchema, dueAtSchema,
dateOnly, timeHm). canApprove kept (still used by FontSettings).
- lib/executive-meeting-notify.ts: EXECUTIVE_MEETING_NOTIFICATION_TYPES
collapsed to ['meeting_created'].
Frontend (artifacts/tx-os)
- pages/executive-meetings.tsx: deleted RequestsSection /
ApprovalsSection / TasksSection / RequestListRow, pruned SECTIONS,
MeCapabilities / MeRoles types, isSectionVisible cases, icon imports.
- hooks/use-notifications-socket.ts: dropped the two retired query
invalidations.
- locales/{ar,en}.json: removed nav.{requests,approvals,tasks},
executiveMeetings.{requests,approvals,tasks} subtrees, and the 6
retired notification.type entries.
Schema + DB
- lib/db/src/schema/executive-meetings.ts: tables + relations + types
for requests/tasks removed.
- artifacts/api-server/scripts/cleanup-em-requests-tasks.sql:
idempotent BEGIN/COMMIT — deletes orphan prefs / notifications /
audit rows, then DROP TABLE … CASCADE for both retired tables.
Applied to dev DB and `db push` re-synced.
Tests
- executive-meetings.test.mjs: deleted 9 retired blocks + 2 covered
prefs duplicates, rewrote /me capability test to assert flags absent,
rewrote DELETE-wipe test to use meeting_created via POST
/api/executive-meetings, removed /requests + /tasks router.param
entries.
- executive-meetings-notifications.test.mjs: deleted 7 blocks
(request_*, task_*, cross-event-mute), updated before/after
cleanup to skip dropped tables, kept setPref/clearPref helpers
(still used by surviving meeting_created opt-out tests).
Drift / pre-existing
- 3 test failures observed under the parallel `node --test` workflow
(meeting_created fan-out count, pref opt-out daily-number conflict,
service-orders JSON-vs-HTML) are pre-existing parallel-file
pollution between executive-meetings.test.mjs and
executive-meetings-notifications.test.mjs. Verified by running
`node --test --test-concurrency=1 'tests/**/*.test.mjs'` →
226/226 pass. Out of scope for #262.
- Pre-existing tsc warnings at routes/executive-meetings.ts L509/625
(boolean/number on isHighlighted) and L1594 (font-settings scope
query) untouched.
|
||
|
|
c0e55752a3 |
Task #183: clickable dependency counts on admin Apps/Services/Users
Turned the inline dependency-count badges on each admin row into
focusable, keyboard-accessible buttons that open a drill-in modal
listing the actual rows behind the count.
Backend (artifacts/api-server/src/routes):
- apps.ts: GET /admin/apps/:id/dependents/{groups,restrictions,opens}
- services.ts: GET /admin/services/:id/dependents/orders
- users.ts: GET /admin/users/:id/dependents/{notes,orders,conversations,messages}
All paginated (limit default 50, max 200; offset) returning
{items, totalCount, limit, offset, nextOffset}. Restrictions joins
the role names that include each required permission.
OpenAPI:
- Added 8 path operations + 16 schemas (Item + Page) and re-ran
`pnpm --filter @workspace/api-spec run codegen`.
Frontend (artifacts/tx-os/src/pages/admin.tsx):
- New DependencyDrillIn component reuses DrillInShell (Escape +
backdrop close, RTL/LTR safe) and the existing LoadMoreSection
pagination pattern from AppOpensDrillIn.
- Each count part in Apps, Services, and Users panels is now a
<button> with a unique data-testid (e.g. app-counts-groups-213,
user-counts-messages-5) and an aria-label that reads
"View {count}".
- AdminPage owns dependencyTarget state for Apps/Services counts;
UsersPanel owns its own (it already encapsulates user list).
Translations:
- Added admin.dependents.* (titles, subtitles, empty/error/load
labels, conversation/order/message helper strings, order status
enum) to en.json and ar.json.
Verification: - tx-os typecheck clean; api-server has only the pre-existing
executive-meetings.ts errors (untouched).
- e2e tested via runTest: login as admin, opened Apps panel,
drilled into groups + opens (Load more grew 50→100), drilled into
Tea orders, drilled into user 5 conversations and messages,
switched to Arabic/RTL and re-opened the Groups drill-in to
confirm Arabic rendering with no raw i18n keys.
Replit-Task-Id: fe96a05b-325f-4e1f-901b-3a2235fb24b5
|
||
|
|
c53641c721 |
#245: narrow umbrella subset — toast polish, opt-out tests, Restore defaults
Picked the 3 most isolated items from the 7-item umbrella; deferred the rest as #259/#260/#261. #223 + #224 — singular toast + summary on partial failure (T001): - my-orders.tsx: replaced the N=1 vs N>1 ternary in scheduleDelete with a single t("myOrders.clearedCount", { count }) so i18next picks _one / _other automatically. Single-row delete now flows through this same toast too — user-visible copy for N=1 is now "1 order deleted" / "تم حذف طلب واحد" instead of the legacy "Order deleted" / "تم حذف الطلب". - my-orders.tsx: partial-failure path now shows ONE summary toast using the existing clearedPartial key ("{{ok}} deleted, {{fail}} failed") instead of N error toasts. Total failure (okCount===0) keeps deleteFailed. - Updated 3 Playwright specs that asserted the legacy copy: order-clear-finished-undo (already had the singular case), order-undo-toast (Arabic single-row delete), order-delete-flush-on-unmount (English). Note: the legacy "myOrders.deleted" locale key is now unreferenced in source — left in place to avoid noise; deletion can be handled separately. #238 — opt-out coverage in executive-meetings-notifications.test.mjs (T002): - Appended 4 tests + setPref/clearPref helpers covering filterRecipientsByNotificationPref: inApp=false drops user, missing pref defaults to ON, cross-event isolation (mute on event A leaves event B alone), email=false leaves in-app intact. Helpers use ON CONFLICT on the verified unique index. Some scenarios overlap existing tests in executive-meetings.test.mjs (lines 1764, 1809) — these still add value by exercising the meeting_created socket fan-out path and cross-event isolation, which the existing tests don't cover. #236 — Restore defaults endpoint + button (T003): - Server: added DELETE /api/executive-meetings/notification-prefs after PUT. Scoped strictly to req.session.userId, returns {ok, count}. Reuses requireExecutiveAccess guard. Architect confirmed no cross-user leakage. - Client: restoreDefaults() handler + outline button (data-testid "em-pref-restore-defaults", NOT gated on dirty since the whole point is to blow away saved settings). New i18n keys restoreDefaults / restored in both locales. - Architect found a stale-state race in restoreDefaults: setDraft(null) was called before invalidateQueries, letting the seed effect repopulate draft from still-cached pre-DELETE data. Fixed by inverting the order to match save() — invalidate first (await refetch), then setDraft(null). - Tests: appended 2 integration tests to executive-meetings.test.mjs covering the full restore flow (PUT 2 muted prefs → DELETE → assert {ok,count:2} + GET shows defaults + actual fan-out reaches user again) and idempotent no-op DELETE on a user with no rows. Test results: - executive-meetings.test.mjs: 47/47 pass (incl. 2 new DELETE tests) - executive-meetings-notifications.test.mjs: 11/11 pass (incl. 4 new opt-out tests) - Playwright order specs: 6/6 pass after legacy-copy updates - Pre-existing failures in service-orders + meeting_created fan-out are untouched and not caused by this change. Follow-ups proposed: #259 (beforeunload + tab-close Playwright), #260 (admin override another user's prefs with audit row + UI), #261 (iPad header verification — may already work). |
||
|
|
b3ad701ba6 |
Task #243: Admin audit log — focused readability + actor-filter subset
Landed a tight subset of the 13-item umbrella, mirroring the proven narrow-then-defer pattern from #242: - #195 — Plain DELETE /api/services/:id now writes a `service.delete` audit row carrying nameEn + nameAr (force-with-deps still uses the dedicated `service.force_delete`). Added matching `service.delete` formatter case + EN/AR i18n keys, and surfaced nameAr on the existing `service.force_delete` summary. - #197 — `actorUserId` filter for `/admin/audit-logs` and CSV export. openapi.yaml updated, codegen regenerated, server filter wired through parseFilters/buildWhere with 400-on-invalid handling, AuditLogPanel UI got an actor dropdown wired into params + export URL + reset, and a new audit-logs-actor-filter API test (4 cases) covers list narrowing, exclusion, invalid input, and CSV export. - #178 — Formatter unit tests for user.delete (id-only, EN/AR display name resolution, force flag, force + name) and the new service.delete (id-only, EN/AR), 11 new cases (33/33 pass). Skipped #194 — already implemented; users.ts DELETE persists displayName fields and audit-summary already renders user.deleteWithName/forceDeleteWithName. Deferred via follow-ups (no duplicate of existing #182/#183/#184): - F1: #196 recent-activity endpoint + 5 admin panels - F2: #205+#206+#208 permission history CSV/name resolution/timeline - F3: #209+#210 cascade/bulk audit rows + e2e UI spec for History tabs Validation: tx-os typecheck clean; pre-existing executive-meetings.ts errors not regressed; all targeted server tests pass (delete-force-warnings 10, audit-logs target-filter 7, forced-only 6, audit-log-coverage 27, new actor-filter 4, broader audit/services sweep 40); e2e test verified actor dropdown rendering, filter behavior, readable Arabic service.delete summary, and CSV export honoring the filter. |
||
|
|
a220794036 |
Task #242: Executive Meetings UX — print page removal, inter-person chip, bulk delete undo
Original umbrella covered #144, #145, #168, #169, #199, #200, #211, #217, #222. Three landed here; #200 was already implemented; remaining five proposed as follow-ups #248–#250. #169 — Removed dead /executive-meetings/print route: - Deleted artifacts/tx-os/src/pages/executive-meetings-print.tsx - Removed import + Route from App.tsx - Removed executiveMeetings.print blocks from en.json and ar.json #222 — "+ شخص هنا" chip in inter-person gaps: - Added addPersonHere i18n key (ar/en) - Threaded addPersonHereLabel through AttendeeFlowSharedProps → flowProps → AttendeeFlow - Renders chip after a person row when next item is also a person (not a subheading); reuses existing onStartAdd("person", i+1) plumbing - Test IDs em-add-person-after-row-${i} / em-add-person-after-${i} #199 — Bulk delete undo via toast action: - ToastAction wired into deleteSelectedMeetings result toast - Snapshots captured client-side before DELETE - Undo recreates each row via existing POST /api/executive-meetings (omitting dailyNumber to avoid 409s on stolen slots), then PATCH if merge fields existed - Single-fire guard prevents double-click duplicates - Updated bulkDeleteConfirm to drop the now-untrue "cannot be undone" line - New strings: bulkDeleteUndo / bulkDeleteUndone / bulkDeleteUndoPartial / bulkDeleteUndoFailed in ar+en Verification: - tsc --noEmit clean for all touched files (admin.tsx errors are pre-existing, unrelated to this diff) - Playwright executive-meetings-bulk-actions.spec.mjs: 5/5 pass - Pre-existing flake meeting_created socket fan-out test passes in isolation (unrelated to changes) |
||
|
|
eb7d15ca7e |
Show readable names in audit log for top-level deletions
Task: #177 — Make user/app/role deletion audit rows render readable names ("Deleted user @alice (Alice Smith)", "Deleted app 'Notes'", "Deleted role 'Editor'") instead of relying on whatever the route happened to capture. Backend metadata changes: - artifacts/api-server/src/routes/users.ts (user.delete): now also persists displayNameEn and displayNameAr alongside the existing username/email. - artifacts/api-server/src/routes/apps.ts (app.delete): renamed the metadata keys slug/nameAr/nameEn → appSlug/appNameAr/appNameEn so app sub-resource events and top-level deletes share one prefix. - artifacts/api-server/src/routes/roles.ts (role.delete): renamed the metadata key name → roleName, matching group.role.add/remove. Frontend formatter (artifacts/tx-os/src/pages/admin.tsx): - appName helper now reads both legacy (slug/nameEn/nameAr) and new (appSlug/appNameEn/appNameAr) keys so old rows still render. - role.delete case prefers roleName, falls back to legacy name. - user.delete case picks the user's localized display name and uses new locale strings user.deleteWithName / user.forceDeleteWithName when present; falls back to the existing username-only strings. - forceDeletedEntityName also accepts appNameEn/appNameAr/appSlug so force-deleted apps still get their inline name chip. Locales: - artifacts/tx-os/src/locales/{en,ar}.json: added admin.audit.summary.user.deleteWithName and forceDeleteWithName. Test updates: - artifacts/api-server/tests/audit-log-coverage.test.mjs: updated the role.delete and app.delete (no-deps) assertions to read the new metadata key names. The user.delete assertions kept working as-is since username/email/force are unchanged. No DB migration was required — audit_logs.metadata is already JSON. Legacy rows continue to render via the formatter fallbacks called out in the task description. Replit-Task-Id: a25d35dd-5005-4e57-96a5-580016f35e46 |
||
|
|
56524cf0de |
Friendly summaries for app.permission.add/remove audit entries
Task #174: The new `app.permission.add` and `app.permission.remove` audit log actions emitted by `artifacts/api-server/src/routes/apps.ts` were falling through `formatAuditSummary`'s default branch, so the admin audit log only displayed the raw action string instead of a human-readable sentence like the existing `role.permission.*` entries. Changes: - artifacts/tx-os/src/pages/admin.tsx: added `app.permission.add` and `app.permission.remove` cases to `formatAuditSummary`. They reuse the existing `appName(meta, lang, targetId)` helper (which already handles the nameEn/nameAr/slug fallback) and mirror the existing role.permission.* permission-name fallback (permissionName -> #permissionId -> "?"). - artifacts/tx-os/src/locales/en.json: added `admin.audit.summary.app.permissionAdd` / `admin.audit.summary.app.permissionRemove`, matching the wording of the role.* variants ("Added permission '{{permission}}' to app '{{name}}'", "Removed permission '{{permission}}' from app '{{name}}'"). - artifacts/tx-os/src/locales/ar.json: added the Arabic equivalents ("تمت إضافة الصلاحية '...' إلى التطبيق '...'", "تمت إزالة الصلاحية '...' من التطبيق '...'"). Verification: - e2e tested via runTest: created a new app, added/removed a permission via the API to emit both audit entries, opened the admin audit log, and confirmed both rows render the friendly English summary; switched the UI to Arabic and confirmed the same rows render the Arabic summary (no raw "app.permission.add" / "app.permission.remove" strings shown). - JSON files validated; no new TypeScript errors introduced near the edited lines (the pre-existing 36 codegen-related errors in admin.tsx are unrelated). No deviations from the task scope. Replit-Task-Id: eb2f3eea-089a-4afc-803c-2580cbf1cfc9 |
||
|
|
fb2d75ecd7 |
Task #164: Per-user notification preferences for Executive Meetings
Lets each user choose whether to receive in-app and/or email notifications
for each executive-meeting event type (meeting_created, request_submitted,
request_approved/rejected/needs_edit, task_assigned, task_completed).
Defaults to "everything on" when no preference row exists, preserving the
prior fan-out behavior for users who never visit the new UI.
Schema:
- New executive_meeting_notification_prefs table (user_id FK CASCADE,
notification_type varchar(64), in_app bool default true, email bool
default true, plus a unique index on (user_id, notification_type)).
- Pushed to dev DB via `pnpm --filter @workspace/db push`.
Backend:
- Exported EXECUTIVE_MEETING_NOTIFICATION_TYPES (canonical list) +
filterRecipientsByNotificationPref(ids, type, channel) helper that
returns only recipients whose row says the channel is on (default-on
semantics for missing rows).
- recordExecutiveMeetingNotifications now filters recipients by
channel="inApp" before inserting; sendExecutiveMeetingEmail filters
by channel="email" before SMTP delivery.
- New endpoints under /executive-meetings/notification-prefs:
GET → { types, prefs } merged with defaults.
PUT → upserts each supplied (type, channel) pair via
onConflictDoUpdate inside a transaction.
Frontend:
- New NotificationPrefsCard at the top of the Notifications section in
artifacts/tx-os/src/pages/executive-meetings.tsx. Renders a Switch per
(event type × channel) with batched save, dirty-state tracking, reset
button, and useToast feedback.
- Translation keys for the card added to en.json and ar.json under
executiveMeetings.notificationsPage.prefs.
Tests:
- 5 new tests in artifacts/api-server/tests/executive-meetings.test.mjs:
GET defaults, PUT roundtrip + upsert, 400 on unknown type, in-app
fan-out filtering (muted approver gets no row, control approver still
does), and channel-independence (muting only the email channel leaves
in-app delivery intact while persisting email=false in the DB row that
sendExecutiveMeetingEmail's filter reads).
- All 36 executive-meetings tests pass. Full suite shows only one
pre-existing flaky test elsewhere (groups-crud count assertion),
unrelated to these changes.
- Added e2e UI test that logs in as admin, toggles a preference, saves,
refreshes, and confirms persistence.
- After-hook cleans up new prefs rows for created users.
Follow-ups proposed: #236 (one-click reset to defaults), #237 (admin
view/override of any user's prefs).
Replit-Task-Id: 284ce15d-40d7-447e-90ca-090b44d8227b
|
||
|
|
b507b33cad |
Add app-permissions impact preview before tightening an app's gate
Mirrors the existing role-permissions impact preview UX for app
permissions. Admins now see how many currently-visible users would lose
access before they add a permission requirement to an app, plus the
groups (via group_apps) that offset the loss because their members keep
access regardless.
Backend
- New endpoint POST /api/apps/:id/permissions/impact-preview in
artifacts/api-server/src/routes/apps.ts. Implements the same OR
semantics as getVisibleAppsForUser: a user "sees" an app if they hold
ANY required permission (direct or via a group role) OR they belong
to a group granted the app via group_apps. Admins are excluded from
counts since they always see every app. Short-circuits with
noChange:true when the candidate set equals the current set.
- OpenAPI schema (lib/api-spec/openapi.yaml): adds the path,
AppPermissionsImpactBody, AppPermissionImpactGroup,
AppPermissionsImpact. Regenerated lib/api-client-react bindings.
Frontend
- AppPermissionsEditor (artifacts/tx-os/src/pages/admin.tsx): debounced
(350ms) cancel-safe preview when a pending permission is selected,
warning banner with affected/visible counts and offsetting groups,
and a confirmation dialog when affectedUserCount > 0. Add button is
disabled while the preview is loading or errored to keep the warning
trustworthy.
- i18n keys added to en.json and ar.json under
admin.appPermissions.{impactTitle, impactLoading, impactError,
impactNone, impactSummary, impactViaGroups, confirmTitle, confirmBody,
confirmAction}.
Tests
- artifacts/api-server/tests/app-permissions-impact.test.mjs: 7 tests
covering noChange short-circuit, unrestricted-app tightening,
candidate that keeps an existing permission, group_apps offset,
unknown app (404), invalid payload (400), and admin-only enforcement.
All 18 app-permissions tests pass.
- E2E flow verified via runTest: admin login → /admin → Apps → edit
app → select permission → preview banner appears → Add → confirm
dialog → cancel without writing.
Out-of-scope (filed as follow-ups #228 and #229): listing the specific
affected user IDs in the preview, and warning when REMOVING a
permission broadens access.
No deviations from the task spec.
Replit-Task-Id: 8b2ff9ea-f95e-4bdb-b268-95b5d17154ca
|
||
|
|
bd78157781 |
Task #227: Restructure attendee cell controls around section headings
User feedback on Task #220's UX (in Arabic): the per-section "+ شخص هنا" chips felt redundant; the user wanted "+ عنوان فرعي" controls placed at the TOP of each cell and after each section instead, and the "+ شخص هنا" chips removed entirely. Schedule cell (AttendeeFlow): - Added a TOP "+ عنوان فرعي" chip that renders BEFORE the first item of each AttendeeFlow group when the group is non-empty (testid em-add-subheading-top-{addType}; insertAtIndex=items[0].i). - Added an after-section "+ عنوان فرعي" chip that renders AFTER the last person of each section that is followed by another subheading (testid em-add-subheading-after-{i}; insertAtIndex=i+1). The trailing section is intentionally skipped — the existing trailing "+ subheading" button already covers that slot. - Removed both "+ شخص هنا" chips (subheading-empty branch + person-tail branch). - Added renderPendingSubheadingLi helper for inline subheading ghost rendering at the clicked slot. Generalized hasInlineInsert to cover both person and subheading pending kinds. - Removed addPersonHereLabel/addPersonHereAriaLabel from AttendeeFlowSharedProps and from the schedule-side flowProps caller. Manage dialog: - Removed the per-section "+ شخص هنا" buttons from the SortableContext flatMap and the insertAttendeeAt helper. Reverted to plain state.attendees.map. Locales (ar + en): - Dropped executiveMeetings.schedule.addPersonHere/addPersonHereAria. - Dropped executiveMeetings.manage.attendees.addPersonHere/ addPersonHereAria. Tests: - Replaced the old "+ person here" test with two new schedule tests: (a) top "+ عنوان فرعي" chip inserts a subheading at slot 0, (b) after-section chip inserts a subheading between two existing sections (BEFORE the next-section subheading). - Both new tests run in en + ar. - Pre-existing drag tests #2 and #3 are unchanged and still pass. - Pre-existing #4 (Manage [ar] drag-reorder mixed person+subheading) is reproducibly red on [ar] but green on [en]. The diff does NOT touch the manage dialog drag flow, the SortableAttendeeRow, or dnd-kit wiring — this is a pre-existing RTL keyboard-sensor flake from #220. Architect review: PASS, no blocking issues. Minor optional follow-ups (extra edge-case test coverage) intentionally not addressed to keep scope tight to the user's request. |
||
|
|
6132d2a00a |
Task #220: insert persons mid-list + drag-reorder attendees
Schedule grid (AttendeeFlow): - Per-section "+ شخص هنا" chip rendered AFTER the last PERSON of each section (a section ends right before the next subheading or at end-of-list), gated by hasAnySubheading. Clicking it opens a ghost row with insertAtIndex = lastPersonIdx + 1 so the new row lands at the TAIL of THAT section, before the next subheading. The chip belongs to the section ABOVE it, matching the user's spec. - Empty-section fallback chip kept on subheading rows: when a subheading is followed by another subheading or end-of-list, render the chip there so empty sections stay growable. Manage dialog: - Added _sid client-only field with genAttendeeSid() so dnd-kit ids stay stable across renames; stripped from save projection. - DnD via dnd-kit (DndContext / SortableContext / SortableAttendeeRow with GripVertical handle, keyboard + pointer sensors). New reorderAttendeesByDrag + sortableIds memo. - New insertAttendeeAt(idx) helper + per-section "+ شخص هنا" buttons injected as non-sortable siblings inside SortableContext after each subheading (sortableIds remains a pure _sid list so the keyboard sensor still works). Locales: addPersonHere + addPersonHereAria in both ar.json and en.json (schedule + manage scopes). Tests: - New e2e spec executive-meetings-attendee-insert-reorder.spec.mjs: (1) chip after last person of section inserts BEFORE the next subheading; asserts em-add-person-here-2 (subheading idx) does NOT exist in [A,B,SUB,C]; (2) keyboard DnD reorders 3 persons; (3) mixed person + subheading drag preserves kind. 6/6 pass in AR + EN. - Existing subheading e2e (Task #207): 6/6 still pass. Process notes: - First mark_task_complete REJECTED by code review for placing chip after subheading instead of after last person of prior section; this commit is the corrected pass. Architect re-review approved the semantic; only follow-up was the missing manage-scope addPersonHereAria locale key, which is added here. |
||
|
|
c03ea006e1 |
Add ability to insert and reorder people within meeting attendee sections
Adds functionality to insert new attendees at specific positions within sections and updates the attendee data structure and API calls to handle this. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: b9611a28-c97f-49a5-b84a-d63d07ef4426 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/piUNOmy Replit-Helium-Checkpoint-Created: true |
||
|
|
2bf2f3cd3a |
Task #147: Add structured permission-change audit (users, groups, apps)
Mirrors the existing role-permission audit pattern with a unified
`permission_audit` table capturing actor, target, prev/new id sets, and
timestamp written in the same transaction as the change.
Schema & API
- New `permission_audit` table (target_kind, target_id, change_kind,
actor_user_id, previous_ids[], new_ids[], created_at) with index on
(target_kind, target_id, created_at).
- Transactional audit writes in routes/users.ts (POST/DELETE roles,
PATCH groupIds), routes/groups.ts (PATCH + add/remove members for
users/roles/apps), routes/apps.ts (POST/DELETE permissions).
- Cross-entity mirroring: when group membership changes via a group
endpoint, a user.groups row is also written for each affected user
(and vice versa via PATCH /users), so each entity's history is
exhaustive regardless of which editor was used.
- Admin-only GET /users/:id/audit, /groups/:id/audit, /apps/:id/audit
with limit/offset/actorUserId/from/to filters and the same response
shape as role audit.
- OpenAPI types + codegen regenerated.
UI
- Reusable PermissionAuditHistory component in admin.tsx wired into
UserGroupsEditor, GroupDetailEditor (new "history" tab), and the
editing-app dialog. App history correctly resolves permission ids
(NOT roles) via useListPermissions.
- Bilingual i18n keys added under admin.{users,groups,apps}.history*
in en.json + ar.json.
Tests
- New backend tests: user-permission-audit, group-permission-audit,
app-permission-audit (14 cases — transactional capture, GET filters
& pagination, admin-only, 404 on unknown id, plus 2 new mirror
tests covering cross-entity audit visibility). All pass; 35
adjacent role/groups/users/audit-coverage tests still pass.
Notes
- replit.md updated to list `permission_audit` table.
- Restored opengraph.jpg (an unrelated stray binary diff).
- Code-review comments addressed: cross-entity asymmetry fixed via
mirroring; opengraph.jpg restored.
- Follow-ups proposed: timeline UI improvements, cascade audit on
delete/bulk paths, e2e UI test for History sections.
Replit-Task-Id: 4ba8c8de-8dfd-42c7-a3bc-964a1ed3a1a3
|
||
|
|
89df2385a8 |
Task #207: custom subheadings inside executive-meeting attendee cells
Adds a new `kind` column (`varchar(16) NOT NULL DEFAULT 'person'`) on `executive_meeting_attendees` so users can interleave free-text section labels with person rows in a meeting's attendee list. Subheadings are excluded from the running attendee number and from the per-meeting attendee count surface, but reorder and delete identically to person rows. DB - New `kind` column in `lib/db/src/schema/executive-meetings.ts`, defaulting to `"person"`. Applied via direct SQL because `drizzle-kit push` trips on a pre-existing duplicate-row issue in `app_permissions` (already documented in replit.md). API (artifacts/api-server/src/routes/executive-meetings.ts) - `attendeeSchema` accepts `kind: z.enum(["person","subheading"])` with default `"person"`. - All 4 insert paths round-trip `kind`: POST create, PATCH meeting update (attendees replacement), PUT `/attendees`, and duplicate. - `pdf-renderer` mapper forwards `kind`. `PdfMeetingAttendee.kind` typed as `string | null` to match the DB column shape. Frontend (artifacts/tx-os/src/pages/executive-meetings.tsx) - `AttendeeFlow` renders subheadings on a separate full-width row (`basis-full`, semibold, centered) and increments the running person index only for `kind === "person"`. Pending ghost row branches on `pendingKind`. - Inline "+ subheading" chip via `onStartAdd(type, "subheading")`. - Manage dialog: addSubheading button, subheading rows hide the title field, show a kind badge, and reorder/delete identically. Manage list summary count filters to person rows only (architect fix). - Print page renders subheadings as `.em-print-subheading` and skips them in the running counter. - New locale keys under `executiveMeetings.schedule` and `executiveMeetings.manage.attendees` in both `ar.json` and `en.json`. PDF - Subheadings print as `— label —` and never advance `personIdx`. Tests - New spec `executive-meetings-attendee-subheadings.spec.mjs` seeds mixed person+subheading rows and asserts (a) the subheading row renders, (b) numbering stays `1-`, `2-`, `3-` with a subheading wedged between persons, (c) zero-subheading meetings keep legacy numbering. Runs in both AR and EN. All 4 cases pass. Code review - Architect found one regression (Manage list summary count included subheadings) — fixed. |
||
|
|
fe736e3ff7 |
Task #146: Filter and paginate role permission history
Summary
- Backend: GET /api/roles/:id/audit now accepts limit (default 10, max 200),
offset, actorUserId (0 = no filter), and from/to (YYYY-MM-DD UTC). The
response is now a paginated envelope `{entries, totalCount, limit, offset,
nextOffset}` instead of a bare array.
- OpenAPI: lib/api-spec/openapi.yaml updated with the new params and a new
RolePermissionAuditList schema; client hooks regenerated via
`pnpm --filter @workspace/api-spec run codegen`.
- Frontend: RolePermissionHistory in artifacts/tx-os/src/pages/admin.tsx is
now self-contained — owns its own filter state, fetches the user list for
the actor dropdown, applies actor changes immediately, and validates date
inputs (rejecting invalid / inverted ranges).
- Pagination: switched to TRUE OFFSET PAGINATION. The first page comes from
React Query (so live cache invalidations after a save still refresh it),
and subsequent "Load more" clicks fetch `offset = nextOffset` imperatively
via getRolePermissionAudit() and append the rows to local state. There is
no client-side ceiling on how far back an admin can page; we simply stop
showing the button when nextOffset is null. A filtersKey effect resets the
appended pages whenever any filter (actor / from / to) changes so we never
serve overlapping or out-of-order rows.
- i18n: added missing keys in artifacts/tx-os/src/locales/{en,ar}.json
(historyEmptyFiltered, historyShowing, historyLoadMore, historyFilters.*,
historyErrors.*).
- Tests: artifacts/api-server/tests/role-permission-audit.test.mjs updated
to read the new envelope and now also covers offset-based pagination,
actorUserId filtering (including the "0 = no filter" semantic), and
date-range filtering (in-range / past-range / inverted / garbage). All 9
audit tests pass; tx-os typecheck clean.
- E2E: ran the testing skill end-to-end against /admin → role edit dialog →
history panel: created a fresh role through the UI, made 25 permission
writes, verified 10 → 20 → 25 pagination with no duplicates and correct
hide-on-end behaviour, filter-by-actor reset to first page, empty date
range showed empty state, inverted dates surfaced the validation error.
Drift / notes
- Pre-existing executive-meetings tests in the `test` workflow are still
failing — unchanged by this task.
- The Arabic language toggle isn't exposed in the page header in this build,
so the e2e Arabic step was skipped; locale strings are in place and the
test ids do not change with locale.
- Code review (initial pass) flagged a 200-row UI ceiling in the previous
"growing limit" approach. Replaced with true offset pagination (described
above) so admins can scroll back through arbitrarily long histories.
Code review follow-ups (round 2)
- Tightened parseRoleAuditUtcDate to reject impossible calendar days
(2024-02-31, 2025-13-01, etc.) instead of silently rolling forward.
- Aligned OpenAPI: actorUserId schema is now `minimum: 0` so the spec
matches the runtime "0 = no filter" contract; client regenerated.
- Added a targeted backend test that asserts impossible dates → 400.
Code review follow-ups (round 3)
- UX: when applied filters become invalid, the History list now hides the
stale entries and shows the "fix filters first" hint instead, and the
Load more button is hidden until filters are valid again. Avoids
presenting yesterday's results as the current view.
Code review follow-ups (round 4)
- Belt-and-braces: also reset the appended history pages when the first
page's totalCount + first-row id signature changes, so an external cache
invalidation (e.g. another save while the dialog is still open) cannot
leave appended pages out of sync with the refreshed first page.
Replit-Task-Id: cc3c9e83-921f-4f72-b443-79f95f6467b1
|
||
|
|
b1f2bab1cd |
Task #138: Show a clear message when meeting end time is before start time
Original ask
- In the executive-meetings schedule, when an admin edits a meeting's
inline time and types end < start, the change was silently discarded.
- Surface a localized toast for both the client-side guard and the
server's 400 validation, and keep the cell in edit mode with the
user's draft preserved so they can fix the typo without retyping.
What changed
- artifacts/tx-os/src/pages/executive-meetings.tsx
- TimeRangeCell.save now defers setEditing(false) until after the
server PATCH succeeds. On a thrown TimeOrderError it stays in edit
mode (preserving the typed draft) and refocuses the start input.
On any other failure it rolls back to the saved values as before.
- The parent saveTimes callback inspects the API error message; when
it matches the server's "startTime must be <= endTime" validation,
it shows the same localized timeOrderError toast (instead of the
raw English error string) and rethrows a tagged Error with
name "TimeOrderError" so TimeRangeCell can react.
- artifacts/tx-os/src/locales/en.json + ar.json
- Updated the existing executiveMeetings.schedule.timeOrderError
keys to the exact wording from the task spec:
EN: "End time is before start time"
AR: "وقت النهاية قبل وقت البداية"
Notes / non-changes
- The client-side guard at the top of TimeRangeCell.save already
emitted the toast and returned without exiting edit mode, so it only
needed the new copy. The change to defer setEditing(false) avoids a
race where the !editing useEffect would wipe the draft to startSaved
during the network round-trip on the server-side error path.
- No new i18n keys were added; existing keys were repurposed with the
spec's exact wording.
Verification
- pnpm tsc on tx-os: no new TypeScript errors in executive-meetings.tsx
(pre-existing errors in admin.tsx / use-notifications-socket.ts are
unrelated to this task).
- e2e Playwright test: created a meeting at 10:00–11:00, opened the
inline editor, set 09:00 / 08:00 and clicked save → destructive
toast "وقت النهاية قبل وقت البداية" appeared, inputs stayed visible
with the typed values intact. Corrected end to 10:00 and saved →
cell exited edit mode showing 09:00 – 10:00 and the API persisted
the new times.
Replit-Task-Id: f701faa8-02a9-4389-b130-92e522744128
|
||
|
|
0fe28fe16d |
Update toggle labels and restore image
Update locale keys for toggle functionality from 'editToggle' to 'saveToggle' and revert 'opengraph.jpg' to its previous state. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 05fe07ec-5152-4ad6-985f-c842d7501206 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/fEDT27c Replit-Helium-Checkpoint-Created: true |
||
|
|
bb87166469 |
Task #198: Edit→Save toggle + bulk-clear attendees + multi-select delete meetings
Three executive-meetings polish features on the Schedule view: 1. Edit-mode toggle now flips its label (Edit↔Save) and icon (Pencil↔Check) so a second click reads as "I'm done editing." 2. New-/edit-meeting dialog gained a "Remove all attendees" button next to "+ Add attendee" with a confirm prompt; only renders when ≥1 attendee is present. 3. Per-row select checkboxes (only in edit mode) drive a floating bulk toolbar showing "Selected N of M" with "Delete selected" + "Clear selection". Single confirm wipes all checked meetings. Implementation notes: - Bulk-select checkbox is rendered as an absolute overlay on the first visible non-merged cell of every row (and on the merged <td> for merged rows) instead of being inlined inside the # cell. This (a) preserves the # cell's grip handle so dnd-kit drag still works, (b) keeps the checkbox reachable when users hide # via the column customizer, and (c) keeps it reachable on rows whose merge swallows the leading cells. - Locale text added to ar.json/en.json for saveToggle.*, bulk*, removeAll, schedule.bulkSelectRow. Tests: - New spec executive-meetings-bulk-actions.spec.mjs (5 tests): edit toggle flip, dialog remove-all-attendees, multi-select delete with DB verify, hidden-# overlay survival, merged-row overlay survival. All pass. - Verified no regressions in executive-meetings-edit-toggle (4/4), -row-actions-menu (2/2), -schedule-features (4/4, including drag-row reorder), -manage-create (1/1), home-clock-persistence (1/1), order-undo-toast (2/2). Code review (architect, 2 rounds): - Round 1 flagged checkbox-in-#-cell breaks when # is hidden → fixed by lifting to absolute overlay. - Round 2 flagged merged rows skipped overlay → fixed by sharing the overlay JSX between renderCell (first unmerged cell) and the merged <td>, plus added the merged-row e2e regression test. Pre-existing failures unrelated to this task: 2 PDF tests in api-server (Tasks #172/#179) — untouched. |