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+).
- The new spec is parameterised over `tx-lang` so it runs once for
English (LTR) and once for Arabic (RTL) — the bug originally
surfaced on the Arabic schedule, so RTL coverage matters.
- The new spec self-skips (rather than fails) if the schedule has
no attendees, so an empty environment doesn't masquerade as a
layout regression.
- All passing.
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).
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).
Adds a new test case to `executive-meetings-edit-toggle.spec.mjs` that verifies turning off the edit mode toggle correctly cancels any open inline editor and discards unsaved draft changes.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ecc0f121-1bcf-46f5-a729-28d8af363bab
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/m6nH0ju
Replit-Helium-Checkpoint-Created: true
Add a single global "تحرير/Edit" toggle button to the schedule
toolbar that hides every editing affordance by default and reveals
them only when the user (with edit permission) explicitly opts in.
Affordances now gated behind `effectiveCanMutate = canMutate &&
editMode`:
- "+ Add row" button
- Per-row delete, color swatch, merge trigger, drag grip
- Inline cell editors (EditableCell, TimeRangeCell)
- Column drag-reorder (SortableHeader.dragEnabled)
- Column resize handles
- "+ Add attendee" button AND its pending ghost row
Persistence:
- Toggle state is stored in localStorage under a per-user key
`em-schedule-edit-mode-v1:<userId>`, so a shared browser cannot
leak one editor's last toggle into another account that signs
in. Falls back to view mode when userId is unavailable.
- Always starts in view mode for users without edit permission.
Toggle-off safety:
- EditableCell + TimeRangeCell discard any in-progress draft and
exit edit mode when their `disabled` / `canMutate` prop flips.
- ScheduleSection clears `pendingAttendee` in a useEffect when
effectiveCanMutate becomes false, so the ghost "+ Add attendee"
row unmounts immediately.
- AttendeeFlow also gates the pending render on `canMutate` as
defense in depth.
i18n: 4 new keys under `executiveMeetings.schedule`
(editToggle / editToggleAria / editToggleOn / editToggleOff)
in both en.json and ar.json.
Tests: tests/executive-meetings-edit-toggle.spec.mjs covers
default-hidden affordances, toggle-on reveal, reload persistence,
and toggle-off re-hide. Cleanup wipes all `em-schedule-edit-mode-v1*`
keys to handle the user-namespaced storage. Full e2e suite (12
tests) passes.
Code review: PASS on the second pass after the per-user key + ghost
row fixes. Pre-existing TS errors in admin.tsx and
use-notifications-socket.ts are codegen drift from earlier tasks
and are not touched by this change.