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 ? (
span_p]:inline [&>span_p]:m-0` flattens the block
- // `` 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-"]',
+ ),
+ );
}
}
});