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).
Task #175. Follow-up to #173. The first fix added `whitespace-nowrap`
on each attendee `<li>`, but users still saw the index span (`1-`,
`2-`) stacked above the name — even with very short names like
"رياض" / "محمد" that obviously fit on one line. Two screenshots
(before and after the merge) showed the same stacked layout, ruling
out narrow-column wrapping.
Root cause: attendee names are saved as tiptap HTML such as
`<p>محمد</p>`. Inside the inline-block EditableCell shell (and even
inside the plain view-mode `<span>`), the default block-level `<p>`
with its 1em top/bottom margins forced the name onto its own visual
row beneath the index span. `whitespace-nowrap` cannot pull a block
child back onto the parent line.
Fix (artifacts/tx-os/src/pages/executive-meetings.tsx):
- Each attendee `<li>` is now `inline-flex items-baseline
whitespace-nowrap` so the index span and the name wrapper become
flex children that structurally cannot break apart.
- The view-mode `<span>` (plain dangerouslySetInnerHTML) gets
`[&_p]:inline [&_p]:m-0` — tiptap `<p>` renders as inline with no
margins.
- The editable EditableCell wrapper gets the more-scoped
`[&>span_p]:inline [&>span_p]:m-0`, which matches only the
view-mode shell `<div> > <span> > <p>` and deliberately does NOT
match the editing shell `<div> > <div(border)> > EditorContent`,
so pressing Enter inside the editor still creates a real new
paragraph.
Tests (artifacts/tx-os/tests/executive-meetings-edit-toggle.spec.mjs):
- New regression spec asserts that the index span and the name
wrapper share the same vertical center (within 8px) for the first
attendee in BOTH view mode and edit mode. Without the fix the
centers differ by a full line height (~20px+).
- All 3 specs in the file pass.
Out of scope (unchanged): grouping/sorting, index format, multi-
group Virtual/Internal/External rows, pending +Add ghost row,
other EditableCell call sites (title, time, notes, manage tab).