diff --git a/artifacts/tx-os/public/opengraph.jpg b/artifacts/tx-os/public/opengraph.jpg index 238f8b83..5fc27c2a 100644 Binary files a/artifacts/tx-os/public/opengraph.jpg and b/artifacts/tx-os/public/opengraph.jpg differ diff --git a/artifacts/tx-os/src/pages/executive-meetings.tsx b/artifacts/tx-os/src/pages/executive-meetings.tsx index e6ab1264..64db08a8 100644 --- a/artifacts/tx-os/src/pages/executive-meetings.tsx +++ b/artifacts/tx-os/src/pages/executive-meetings.tsx @@ -2936,28 +2936,22 @@ function AttendeeFlow({ {items.map(({ a, i }, displayIdx) => (
  • level, which is what we want when the cell is - // narrow. - // - // The `[&_p]:inline [&_p]:m-0` utilities on the inner name - // wrappers (below) are critical: attendee names are saved as - // tiptap HTML like `

    محمد

    `, and a default block-level - // `

    ` (with its 1em top/bottom margins) is what was forcing - // the name onto its own visual line under the index. Stripping - // its display+margins keeps the rendered name truly inline. + data-testid={`em-attendee-row-${i}`} + // `inline-flex items-baseline` keeps the index prefix and the + // name as flex siblings on the same line. The `[&_p]` utilities + // on the inner wrappers below flatten tiptap's block `

    ` (with + // its default 1em margins) so the rendered name stays inline. className={ "inline-flex items-baseline whitespace-nowrap" + (editable ? " min-w-[3rem]" : "") } > - {displayIdx + 1}- + + {displayIdx + 1}- + {editable ? (

    ` that tiptap stores names in, but ONLY for the - // view-mode shell (`

    > >

    ...

    `). When - // the cell is actively being edited the structure becomes - // `
    > > `, which this - // selector deliberately does NOT match — so pressing - // Enter inside the editor still creates a real new - // paragraph. + // `[&>span_p]:inline [&>span_p]:m-0` only matches the view-mode + // shell (`div > span > p`); the active editor's + // `div > div > EditorContent` does NOT match, so Enter still + // creates a real new paragraph inside the editor. className="inline-block align-baseline min-w-[3rem] min-h-[1.5rem] px-0.5 py-0.5 [&>span_p]:inline [&>span_p]:m-0 border-b border-dashed border-gray-400/70 hover:border-blue-500 data-[editing=true]:border-b-0 print:border-b-0 print:py-0 print:min-h-0" testId={`em-edit-attendee-${i}`} /> ) : ( diff --git a/artifacts/tx-os/tests/executive-meetings-edit-toggle.spec.mjs b/artifacts/tx-os/tests/executive-meetings-edit-toggle.spec.mjs index 5e901576..71f0cac6 100644 --- a/artifacts/tx-os/tests/executive-meetings-edit-toggle.spec.mjs +++ b/artifacts/tx-os/tests/executive-meetings-edit-toggle.spec.mjs @@ -200,17 +200,14 @@ for (const lang of ["en", "ar"]) { /* fall through to the skip check below */ }); - // Find the first attendee LI in the schedule that actually has - // an index prefix span and a name span. We bind the assertion to - // the LI's structure (index span + sibling name span) so it - // stays meaningful even if specific data changes. - const firstAttendeeLi = page - .locator( - 'li.inline-flex:has(> span.text-gray-500):has(> span:not(.text-gray-500))', - ) + // Locate attendees by stable testids on the row, the index span, + // and either the static name span or the editable cell. This keeps + // the regression guard decoupled from CSS class names. + const firstAttendeeRow = page + .locator('[data-testid^="em-attendee-row-"]') .first(); - if ((await firstAttendeeLi.count()) === 0) { + if ((await firstAttendeeRow.count()) === 0) { test.skip( true, `No attendees present on the schedule for lang=${lang}; cannot assert layout. Seed an attendee to enable this guard.`, @@ -218,46 +215,46 @@ for (const lang of ["en", "ar"]) { return; } - async function assertInline(li) { - await expect(li).toBeVisible(); - const indexBox = await li - .locator("> span.text-gray-500") - .boundingBox(); - const nameBox = await li - .locator("> span:not(.text-gray-500), > div") - .first() + async function assertSameLine(rowLocator, nameLocator) { + await expect(rowLocator).toBeVisible(); + const indexBox = await rowLocator + .locator('[data-testid^="em-attendee-index-"]') .boundingBox(); + const nameBox = await nameLocator.boundingBox(); expect(indexBox).not.toBeNull(); expect(nameBox).not.toBeNull(); const indexCenter = indexBox.y + indexBox.height / 2; const nameCenter = nameBox.y + nameBox.height / 2; - // If the name had wrapped onto a row below the index, the - // vertical centers would differ by at least one full line - // height (~18-22px). We allow up to 8px of slack for normal - // baseline alignment between an inline span and an - // inline-block. + // If the name wrapped onto a row below the index, centers would + // differ by ~a full line height (~18-22px). 8px of slack covers + // normal inline-vs-inline-block baseline differences. expect(Math.abs(indexCenter - nameCenter)).toBeLessThan(8); } - // 1. View mode (toggle off): name renders as a plain - // next to the index span. - await assertInline(firstAttendeeLi); + // 1. View mode (toggle off): name renders as a plain . + await assertSameLine( + firstAttendeeRow, + firstAttendeeRow.locator('[data-testid^="em-attendee-name-"]'), + ); - // 2. Edit mode (toggle on): name renders inside an EditableCell - // inline-block
    ; should still sit beside the index span. + // 2. Edit mode (toggle on): name renders inside an EditableCell. const toggle = page.getByTestId("em-edit-mode-toggle"); if (await toggle.isVisible().catch(() => false)) { await toggle.click(); await expect(toggle).toHaveAttribute("aria-pressed", "true"); - // Re-locate after re-render — the matching attendee LI now - // wraps an EditableCell
    instead of a plain . - const firstAttendeeLiEditable = page - .locator( - 'li.inline-flex:has(> span.text-gray-500):has(> div[data-testid^="em-edit-attendee-"])', - ) + const firstAttendeeRowEditable = page + .locator('[data-testid^="em-attendee-row-"]') + .filter({ + has: page.locator('[data-testid^="em-edit-attendee-"]'), + }) .first(); - if ((await firstAttendeeLiEditable.count()) > 0) { - await assertInline(firstAttendeeLiEditable); + if ((await firstAttendeeRowEditable.count()) > 0) { + await assertSameLine( + firstAttendeeRowEditable, + firstAttendeeRowEditable.locator( + '[data-testid^="em-edit-attendee-"]', + ), + ); } } });