After #322 trimmed the dialog, the Save/Cancel footer slid below
the visible 90vh sheet on phones because the whole DialogContent
was a single overflow-y-auto block.
Changes in artifacts/tx-os/src/pages/executive-meetings.tsx:
MeetingFormDialog
- Rebuilt DialogContent as a flex column: shrink-0 header,
flex-1 min-h-0 overflow-y-auto middle (the only scroller),
shrink-0 footer. Save/Cancel now stay pinned at the bottom
even after many attendees are added.
- Reduced mobile padding/gaps (`p-4 sm:p-6`,
`gap-3 sm:gap-4`, grid `gap-2 sm:gap-3`). Desktop spacing
unchanged.
- Switched the field grid from `grid-cols-1 sm:grid-cols-2`
to a flat `grid-cols-2` so the two short fields (التاريخ,
الرقم اليومي) share a row even on mobile. Title spans both
columns; time pickers span both on mobile and one each on
sm+ via the new mobileFull variant.
- Inner scroll wrapper uses negative margins + matching padding
so scroll content can extend edge-to-edge without losing the
dialog's gutter.
FormRow helper
- Broadened `full` from `sm:col-span-2` to `col-span-2`
(works in both 1-col and 2-col grids; only callers using
`full` are in this file).
- Added `mobileFull` prop → `col-span-2 sm:col-span-1` for
the time pickers, so AM/PM controls don't get squeezed on
phones.
Out of scope (untouched): validation, save logic, attendee
model, removed-field defaults from #322, duplicate dialog,
audit, schedule view, translations.
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
Follow-up to task #315. The 3-control picker shipped with desktop-
sized inline controls (~22px tall) and a single-row layout that
pushed the end picker out of view on a 375px iPhone, leaving the
user with no obvious way to fill the second time field.
What changed
- src/components/time-picker-12h.tsx: bumped the "inline" size
from h<22px text-[10px] to h-8 (32px) text-sm/text-xs across
hour input, minute input, and AM/PM toggle so a finger tap on
mobile/iPad lands cleanly. Added `gap-1.5` between the
[hour:minute] group and the AM/PM toggle so a thumb-tap on the
toggle doesn't accidentally land on the minute input. Added a
visible focus ring color and lightened the placeholder so the
empty fields read as "fill me in" rather than already-active.
- src/pages/executive-meetings.tsx (TimeRangeCell wrapper):
switched from `inline-flex` (single-row, ellipsis on overflow)
to `flex flex-wrap items-end gap-x-1.5 gap-y-1`. The end picker
now drops onto a second line when the cell can't fit both
side-by-side, which is the iPhone case. The "–" separator is
hidden on narrow widths (`hidden sm:inline`) since wrapping
makes it visually wrong.
- Bumped Start/End labels from 10px to 11px font-medium for
better at-a-glance scanability of which time field is which —
this was the user-reported clarity issue.
- Save/cancel icon buttons grew to 32x32 hit areas (was tiny
p-0.5 around a 14px icon ≈ 16px clickable).
- tests/executive-meetings-keyboard-editing.spec.mjs: added a
new test.describe block "Schedule mobile viewport (iPhone 375)"
with `test.use({ viewport: { width: 375, height: 667 } })`. The
test asserts: (a) all 8 picker sub-controls + save button are
visible on iPhone, (b) each interactive control is at least
28px tall (regression guard against shrinking touch targets),
(c) a complete tap-driven hour+minute+AM/PM entry on both
start and end persists 21:30–22:45 to the DB.
Validation
- Unit tests: 22/22 pass (no logic changes — only CSS + layout).
- E2E: Tab walks (desktop) + compact+PM toggle (desktop) +
new iPhone-375 mobile test all pass.
- TypeScript: clean.
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).
- NEW UI test "Cascade prompt UI: no followers => prompt skipped,
direct submit with cascadeFollowing=false" — locks in the no-
followers branch from the second review pass: mocks cascade-
preview to {followers:[], blockedBy:null} and asserts the prompt
never renders and the postpone-minutes payload carries
cascadeFollowing=false. Both UI tests use wildcard regex routes so
same-day pollution from prior tests cannot misroute requests.
- 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.
User report: "the dropdown for changing the site font does not work."
Root cause: FontSettingsSection listed Cairo / Tajawal / Noto Naskh
Arabic / Amiri, but only Tajawal had been registered with @font-face.
Picking the others was a no-op. Site default body font was also not
DIN Next LT Arabic as designed.
Changes:
- artifacts/tx-os/src/index.css: --app-font-sans now starts with
"DIN Next LT Arabic"; dropped IBM Plex Mono.
- artifacts/tx-os/index.html: removed Google Fonts <link> + preconnect.
- artifacts/tx-os/src/pages/executive-meetings.tsx: FontSettingsSection
dropdown replaced with the 5 actually-bundled families + system.
- artifacts/api-server/src/routes/executive-meetings.ts: FONT_FAMILIES
Zod allowlist updated to match.
- artifacts/api-server/src/lib/sanitize.ts: FONT_NAME_PART regex now
allows the new families (kept IBM Plex Sans Arabic for backward
compat). Fixes a latent bug from #301 where the rich-text sanitizer
silently stripped attendee font picks on save.
- artifacts/api-server/src/lib/pdf-renderer.ts: FAMILY_MAP and
ARABIC_FONT_NAMES updated. Sans-style families map to the bundled
NotoSansArabic; Naskh-style families to NotoNaskhArabic.
- artifacts/api-server/tests/executive-meetings.test.mjs: PDF
sans-vs-naskh assertion updated from Cairo/Noto Naskh Arabic to
DIN Next LT Arabic/Majalla, preserving its semantics. Other
Cairo/Noto Naskh Arabic occurrences swapped to the new names.
- replit.md: documented site default font + lockstep allowlist rule.
Deviations from plan: kept DEFAULT_FONT.fontFamily = "system" on the
frontend (and the backend Zod default) instead of changing it to
"DIN Next LT Arabic". "system" semantically means "no override → use
the CSS default", which is now DIN Next LT Arabic — same end result
without forcing a migration on existing rows. The sanitizer change
was not in the original plan but was required to make the picker
actually persist.
Verification:
- Frontend tsc clean.
- Backend tsc shows the same pre-existing unrelated errors as before
(isHighlighted boolean/number; Drizzle scope typing).
- E2E browser test verified all 7 assertions: site default font,
no Google Fonts requests, exactly 6 dropdown options, font picker
applies + reverts correctly, attendee font picks persist after save.
- Code review verdict: PASS.
Implement shared row highlighting for executive meetings by adding a `rowColor` field to the database schema and API, and migrating existing per-device colors to the new shared field.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 273accfc-a301-41b9-bd20-c121cb4e79c7
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
Introduce real-time event listeners and emitters to synchronize meeting alert status changes, such as "Done" or "Dismissed", across a user's multiple open tabs and devices. This update refactors the `executive-meetings.ts` route to trigger a per-user socket event upon state transitions, which is then handled by `use-notifications-socket.ts` to invalidate the alert state query, ensuring near-instantaneous UI updates. New tests in `executive-meetings-upcoming-alert.spec.mjs` verify the cross-tab synchronization for both "Done" and "Dismiss" actions, confirming that only the acting user's alerts are affected.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b81561d1-3ba4-46fc-96c0-77d50130c061
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
The substantive code for this task was already merged earlier in the
session as commit 1df6ba4 (and re-merged as 43a5491 due to a
re-assignment after task #130 went through). When the system handed
the task back to me on the post-#130 base, the working tree already
contained the desired final state — there was nothing further to
change.
What is already in HEAD that satisfies the task:
- artifacts/tx-os/src/pages/executive-meetings.tsx:
- Add-row button (~line 1533): renders only the <Plus> icon in the
idle state; "Loading..." stays during creatingRow. Button has
aria-label={t("executiveMeetings.schedule.addRow")} and
aria-busy={creatingRow}; <Plus> is aria-hidden.
- Add-attendee inline button (~line 3008): visible content is the
literal "+"; aria-label={addLabel} preserved for assistive tech.
- artifacts/tx-os/src/locales/ar.json:
- executiveMeetings.schedule.addRow : "أضف اجتماع"
- executiveMeetings.schedule.addAttendee : "أضف حاضر"
- artifacts/tx-os/src/locales/en.json:
- executiveMeetings.schedule.addAttendee : "Add attendee"
- executiveMeetings.schedule.addRow : unchanged ("Add meeting")
Verification (already performed during the original implementation):
- All 4 cases of executive-meetings-edit-toggle.spec.mjs pass; tests
reference the buttons via data-testid (em-add-row-button,
em-add-attendee-*), so no test changes were required.
No deviations from the task brief.
Task #190.
User wanted both add affordances in the Executive Meetings schedule
to show only the "+" symbol — no accompanying text in either
language.
Changes:
- artifacts/tx-os/src/pages/executive-meetings.tsx:
- Add-row button: drops the visible label in idle state, renders
only the <Plus> icon (aria-hidden). Loading state still shows
"Loading..." for feedback. Added aria-label and aria-busy on
the <button> so screen readers still announce the action and
the loading state.
- Add-attendee inline button: visible content is now the literal
"+" character. aria-label preserved for assistive tech.
- artifacts/tx-os/src/locales/ar.json:
- executiveMeetings.schedule.addRow: "أضف اجتماع جديد" → "أضف اجتماع"
- executiveMeetings.schedule.addAttendee: "+ أضف حاضرًا" → "أضف حاضر"
- artifacts/tx-os/src/locales/en.json:
- executiveMeetings.schedule.addAttendee: "+ Add attendee" → "Add attendee"
Both locale strings are now used solely as accessible names since
the visible glyph is hard-coded.
Verification: All 4 tests in
executive-meetings-edit-toggle.spec.mjs pass. Tests use
data-testid (em-add-row-button, em-add-attendee-*), not text, so
no test changes were required. Pre-existing TS errors in admin.tsx
and use-notifications-socket.ts are unrelated (api-client-react
codegen out of sync; tracked by other in-flight tasks).