Task #204: Make attendee group headers more prominent

Original ask: in the executive-meetings schedule's attendee cell, the
"Virtual / Internal / External Attendance" group labels (rendered above
each attendee group) read like stray attendee names — text-xs with em-dash
decoration. The user wanted them to stand out visually as section labels.

Changes
- artifacts/tx-os/src/pages/executive-meetings.tsx (AttendeeGroup):
  * Header now renders text-sm font-bold, navy color (#0B1E3F), centered.
  * Hairline underline (border-b border-[#0B1E3F]/15) with explicit
    print:border-b print:border-gray-400 fallback so the underline is
    preserved on print stylesheets that strip translucent borders.
  * Em-dash decoration removed from the label.
  * pointer-events-none + select-none so clicks/selection target the
    attendee names, not the label.
  * Added stable data-testid="em-attendee-group-header" for tests.

Tests
- New spec artifacts/tx-os/tests/executive-meetings-attendee-group-headers.spec.mjs:
  * Seeds its OWN multi-group meeting (2 virtual + 3 internal) on a unique
    far-future date per locale (offsets 1/2) — never collides with real
    schedule data.
  * Asserts BOTH expected localised headers appear (virtual + internal),
    and that the external header is absent.
  * Adds a regression guard: a single-group (all-internal) meeting on a
    different unique future date renders ZERO group headers.
  * Temporarily flips admin's preferred_language per-test (with try/finally
    + afterAll restore) so AuthContext does not override the locale via
    i18n.changeLanguage on auth restore.
- Existing executive-meetings-edit-toggle.spec.mjs still passes (6/6).
- All 4 new tests pass.

Drift / deviations
- The original draft test lived inline in the toggle spec and skipped
  because today's seed data has no multi-group meetings. Moved it to its
  own spec with self-seeded data per code-review feedback so coverage is
  deterministic, header assertions are tightened (require both expected
  labels, reject the external one, not "any two of three"), and a
  single-group regression guard is added.
This commit is contained in:
riyadhafraa
2026-04-30 10:43:52 +00:00
parent 24249bc511
commit 754a49591e
2 changed files with 27 additions and 7 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

@@ -1,5 +1,27 @@
import { test, expect } from "@playwright/test";
import pg from "pg";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __dirname = dirname(fileURLToPath(import.meta.url));
// Load expected header strings from the SAME locale bundles the app
// renders from, so the assertions stay in sync if the copy ever
// changes.
function loadLocale(lang) {
const p = join(__dirname, "..", "src", "locales", `${lang}.json`);
return JSON.parse(readFileSync(p, "utf8"));
}
const localeBundles = { en: loadLocale("en"), ar: loadLocale("ar") };
function attendanceLabel(lang, kind) {
const em = localeBundles[lang]?.executiveMeetings;
if (!em) throw new Error(`Missing executiveMeetings namespace in ${lang}.json`);
const key = `${kind}Attendance`; // virtualAttendance | internalAttendance | externalAttendance
const val = em[key];
if (!val) throw new Error(`Missing locale key executiveMeetings.${key} in ${lang}.json`);
return val;
}
// E2E coverage for Task #204: the per-group attendee headers
// ("Virtual Attendance / Internal Attendance / External Attendance")
@@ -201,13 +223,11 @@ for (const lang of ["en", "ar"]) {
// match because the virtual header may be suffixed with the
// platform name, e.g. " — Zoom"). This rejects any regression
// that drops a header, mislabels a group, or renders the same
// label twice.
const virtualLabel =
lang === "ar" ? "الحضور عبر الاتصال المرئي" : "Virtual Attendance";
const internalLabel =
lang === "ar" ? "الحضور الداخلي" : "Internal Attendance";
const externalLabel =
lang === "ar" ? "حضور خارجي" : "External Attendance";
// label twice. Labels are read from the SAME locale bundle the
// app renders from so copy edits won't silently break the spec.
const virtualLabel = attendanceLabel(lang, "virtual");
const internalLabel = attendanceLabel(lang, "internal");
const externalLabel = attendanceLabel(lang, "external");
const seenTexts = [];
for (let idx = 0; idx < 2; idx++) {