adf70e4f281ad00d72049d3dfd780237164ff435
410 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
adf70e4f28 |
Task #301: Custom editor fonts + Tab quick-add for attendees
Curated 22 font files from the user's uploaded zip into
artifacts/tx-os/public/fonts/ and exposed five families in the
in-place editor's font dropdown:
- DIN Next LT Arabic (5 weights)
- Tajawal (7 weights)
- Helvetica Neue LT Arabic (3 weights)
- Helvetica Neue (5 weights)
- Majalla (2 weights)
Declared via @font-face in artifacts/tx-os/src/custom-fonts.css with
font-display: swap so weights are only fetched on demand. Each
<option> in the toolbar dropdown now previews itself in its own
family.
Tab quick-add: pressing Tab inside an attendee name's EditableCell
commits the current value and immediately opens a new pending
attendee row right after it, so users can type names continuously
without mousing. Implemented via:
- new optional `onTabNext` prop on EditableCell, captured into a
ref so the handler (created once via useEditor with [] deps)
always fires the latest callback;
- new `chainStartAdd` shared prop on AttendeeFlow that bypasses
the parent's `hasAnyPending` UI gate (the state-level guard in
`setPendingAttendee(prev => prev ? prev : new)` still prevents
truly overlapping pendings);
- wiring on both existing-attendee and pending-attendee cells.
Drift from plan:
- The plan also mentioned Shift+Tab to focus the previous person.
Scoped out — focus-from-outside isn't supported by EditableCell
today, and the user only asked for forward Tab. Shift+Tab now
falls through to default browser focus behaviour.
E2E test (testing skill) passed for both features. Architect review
returned a Pass with one minor note that IBM Plex Sans Arabic has
no @font-face entry in custom-fonts.css — that family is already
loaded via the existing Google Fonts <link> in index.html (it is
the app's default --app-font-sans), so the dropdown option works
as expected.
Pre-existing api-server test failures are unrelated and predate
this task.
|
||
|
|
a79e1e125a |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 09224b88-5f3a-4c5a-bec0-93eaaf66ca08 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/iRjGQNR Replit-Helium-Checkpoint-Created: true |
||
|
|
0dc84d69f6 |
Shorten the Executive Meetings page header
The Executive Meetings screen used to show a two-line header next to the
calendar icon: a primary "إدارة الاجتماعات التنفيذية" / "Executive
Meetings Management" line and a smaller secondary line in the opposite
language under it. The user asked for a single short title and no
second-language line beneath it.
Changes:
- artifacts/tx-os/src/locales/ar.json + en.json
- Removed the dual-line keys executiveMeetings.titleAr and
executiveMeetings.titleEn.
- Replaced them with a single executiveMeetings.title key whose
locale-resolved value is "قائمة الاجتماعات" (Arabic) and
"Meetings list" (English).
- artifacts/tx-os/src/pages/executive-meetings.tsx
- Page header: removed the second smaller-text <div> entirely and
pointed the remaining bold line at the new key.
- Print-only daily-schedule header (around line 1960): also pointed
at the new key for consistency on the printed page.
The renamed key is more honest than the old titleAr/titleEn pair, which
no longer made sense once we collapsed to a single line.
Out of scope (untouched, per plan):
- The bilingual page subtitle / sidebar item shown in the user's
screenshot — those live elsewhere.
- Browser tab title, alert popup title, and any other Executive
Meetings surface.
- Header layout, color, or icon.
Verification:
- TypeScript: clean.
- e2e (testing skill): in Arabic locale the header shows the new
"قائمة الاجتماعات" title and neither old long string is present
anywhere on the page; after switching to English locale the header
becomes "Meetings list" and again the old strings are gone.
|
||
|
|
4d290cb10d |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 7eff3391-bf57-4f34-b8c2-410f9fa489ca Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/iRjGQNR Replit-Helium-Checkpoint-Created: true |
||
|
|
75272cfb51 |
Remove repeated "+ شخص هنا" chip from attendee cells
The Executive Meetings schedule used to render a small dashed "+ شخص هنا"
("+ person here") chip in the gap between every two consecutive person
rows of an attendee cell. With three attendees the user saw three chips
stacked under the names, which looked noisy and repetitive. The user
asked for the chip to be removed entirely; the existing trailing "+"
button at the end of the cell already covers the add-person flow.
Changes:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- Removed the inter-person chip render block (the `<li>` + `<button>`
that used data-testid="em-add-person-after-row-*" /
data-testid="em-add-person-after-*"). Left a short comment in place
of the removed block explaining what was there and why it went.
- Removed the now-unused `addPersonHereLabel` from the props type, the
component's destructured parameters, and the parent's `t(...)` call.
- artifacts/tx-os/src/locales/en.json + ar.json
- Removed the orphaned `executiveMeetings.schedule.addPersonHere` key.
Out of scope (per plan, untouched):
- The "+ subheading" / "+ عنوان فرعي" inter-row chip that appears just
before a subheading row.
- The trailing "+ Virtual" / "+ Internal" / "+ External" add chips that
appear when a group is empty.
- The per-row trailing "+" button and the inline add-row flow.
Verification:
- TypeScript: clean.
- e2e (testing skill): on /executive-meetings, the page contains zero
elements matching the old chip's data-testid prefixes and zero
occurrences of the literal text "+ شخص هنا" or "+ person here", while
the schedule and attendee lists render normally.
|
||
|
|
1ac81b7a35 |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 70d86683-5d4a-4f99-9ac0-eb156e5defe4 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/GMnW1ol Replit-Helium-Checkpoint-Created: true |
||
|
|
0a1091dfb8 |
Improve how attendee names are displayed in meeting alerts
Modify `upcoming-meeting-alert.tsx` to strip HTML tags from attendee names, preventing raw HTML or empty tags from being displayed. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 56a48682-927f-4cf0-8067-bfa5734c01e9 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/GMnW1ol Replit-Helium-Checkpoint-Created: true |
||
|
|
80d6267599 |
Tidy the upcoming-meeting alert Details panel
The expanded Details panel had two visible problems pointed out by the user: 1. The "Notes / الملاحظات" block always rendered — including a "No notes added." fallback for meetings without notes — adding empty noise the user did not want. 2. Attendee names were printed as plain text but the database stores them as sanitized rich-text HTML, so they showed up as literal "<p>محمد علي</p>". Changes: - Removed the entire Notes block from the DetailsPanel sub-component in artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx. Dropped the unused StickyNote icon import and the now-unused notesText constant. - Each attendee name is now rendered with the same HTML-stripping pattern the popup already uses for the meeting title: name.replace(/<[^>]+>/g, "").trim() || name. - Removed the now-unused i18n keys executiveMeetings.alert.detailsNotes and detailsNoNotes from both en.json and ar.json. Out of scope (per plan): no schema/API changes, no rich-text rendering of attendee names, no color/toggle changes. Verification: - TypeScript: clean. - e2e (testing skill): a fresh meeting with HTML-tagged attendee names was created, the popup expanded, and the test confirmed (a) no Notes section / "alert-details-notes" testid present, (b) attendee names visible without "<p>", "</p>", "<span>" tags, and (c) the meeting URL link still renders. |
||
|
|
b60935ed00 |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 5b2a9a59-f8a7-4e97-bd4f-ac8cc8a6bd23 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/GMnW1ol Replit-Helium-Checkpoint-Created: true |
||
|
|
fd4d99a97e |
Add settings to customize meeting alert appearance and behavior
Implement a settings card for upcoming meeting alerts, allowing users to enable/disable the alert, choose color presets, and reset to defaults. Modify the alert component to respect these preferences and add a "Details" toggle to expand and display meeting information. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a4e71a69-cd61-4758-aeb2-ee141845ce95 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/GMnW1ol Replit-Helium-Checkpoint-Created: true |
||
|
|
d044496d5e |
Remove AM/PM indicators from meeting time displays
Remove day period indicators (AM/PM, ص/م) from the 12-hour time display in executive meeting schedules and PDF exports. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: d895acbd-d69d-4d3e-8ded-4cb2be114779 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/0VZWDfs Replit-Helium-Checkpoint-Created: true |
||
|
|
4726af889d |
Task #295: Drop AM/PM (م/ص) from meeting schedule times
What changed:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- Rewrote the local formatTime helper to call
Intl.DateTimeFormat.formatToParts directly, filter out segments of
type "dayPeriod", join the remaining parts and trim. Locale uses
"ar-u-nu-latn" / "en-US-u-nu-latn" so Latin digits remain forced
in Arabic. hour12: true, hour: "numeric", minute: "2-digit"
preserved from Task #292 — only the AM/PM (en) and ص/م (ar)
suffix is gone.
- Removed the now-unused i18nFormatTime import (the shared helper
in lib/i18n-format.ts always emits dayPeriod when hour12: true,
and this page now intentionally diverges).
- Touches both call sites — the schedule cell (~L3452) and the
Manage tab list (~L4583) — via the same helper.
- artifacts/api-server/src/lib/pdf-renderer.ts
- Applied the identical formatToParts + filter("dayPeriod") + trim
transformation in formatTimeRange so the printed PDF Time column
matches the on-screen schedule for both languages.
Verified:
- TypeScript clean for both packages (3 pre-existing errors in
api-server/src/routes/executive-meetings.ts last touched in #293,
unrelated to PDF renderer).
- E2E (admin login, English then Arabic): runTest passed — schedule
cells render compact "5:39 – 5:50" form, no AM/PM/ص/م suffix in
either language, no 24-hour values, Latin digits preserved in
Arabic, and the Task #292 inline editor labels (البدء/الانتهاء)
still render correctly.
- Architect code review: PASS, no critical issues.
- No existing test asserts on AM/PM display strings, so no test
regressions.
Notes:
- Out of scope (deliberately untouched): native <input type="time">
picker (browser-controlled), user clockHour12 setting, home/chat
clocks, audit-log timestamps, DB storage and API payloads.
- Edge case: noon/midnight now render as "12:00" without an
AM/PM marker — inherent to the requested output, not a regression.
- artifacts/tx-os/public/opengraph.jpg shows a tiny size bump in the
diff. I did not touch this file; the dev/build tooling auto-bumps
it on workflow restart (visible in the file's git log spanning many
unrelated tasks). Cannot be removed without destructive git ops
which are restricted.
|
||
|
|
18ccb64410 |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 6672d5ac-fede-4b94-9d64-dbe83f8f72b2 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/Pd92hH9 Replit-Helium-Checkpoint-Created: true |
||
|
|
ba7232d663 |
Task #205: Add "Download CSV" export for role permission history
Backend
- New endpoint: GET /api/roles/:id/audit/export
- Reuses parseRoleAuditFilters/buildRoleAuditWhere so it honors actor
and from/to filters identically to the existing /audit list.
- Resolves added/removed permission ids -> names in a single query.
- Streams text/csv with a UTF-8 BOM (Excel compatibility), header
timestamp,actor_username,added_permissions,removed_permissions,
capped at 50,000 rows, filename role-{name}-history-{YYYY-MM-DD}.csv.
- Extracted csvEscape into artifacts/api-server/src/lib/csv.ts (with
formula-injection mitigation) and refactored audit.ts to import it.
- OpenAPI spec entry exportRolePermissionAuditCsv added; codegen run to
regenerate getExportRolePermissionAuditCsvUrl.
Frontend
- artifacts/tx-os/src/pages/admin.tsx RolePermissionHistory: new
Download button (testid role-history-export-csv) next to Load more,
with exporting / exportFailed state and a blob download flow that
respects the active actor/from/to filters.
- exportFailed is a boolean (per code review): the panel only shows a
localized generic message, so storing the raw error string would
have been dead state.
- en/ar locale strings: admin.roles.historyExport.button =
"Download CSV" / "تنزيل CSV", plus a failure message.
Tests
- 4 new server tests in role-permission-audit.test.mjs cover: 404 for
unknown role, CSV header + rows + UTF-8 BOM bytes, actor/date filter
honoring, and admin-only enforcement. All audit tests pass.
- e2e UI run via runTest verified: admin login, creating a role and
saving permissions twice, the History panel shows entries, the
Download CSV button downloads a valid CSV (correct header, BOM,
data rows), and the same flow works in the Arabic UI.
Other test failures observed in the workflow (executive-meetings,
app-permissions-impact) are pre-existing flakes unrelated to this
change and do not touch any modified files.
Replit-Task-Id: 8b9814d2-bea8-4b3b-a313-97aec91d458c
|
||
|
|
f2c031e1fe |
Task #292: Show schedule meetings in 12h format and add LTR Start/End labels to inline editor
What changed:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- formatTime helper now uses hour: "numeric", minute: "2-digit", hour12: true
(was hour12: false). All on-page time displays — schedule cells and the
Manage tab list — flow through this single helper. Latin digits remain
enforced via the shared i18nFormatTime helper, so Arabic renders as
"5:39 م" rather than mixing Arabic-Indic numerals into the table.
- Inline time editor (InlineTimeEditor):
- Wrapper locked to dir="ltr" so start-on-left / end-on-right (and
save/cancel after end) layout never mirrors under RTL.
- Each <input type="time"> wrapped in a vertical mini-column with a
small muted <label> above ("Start"/"End" or "البدء"/"الانتهاء").
- Stable per-meeting input ids (em-time-{start,end}-input-<id>) wired
via htmlFor so click-to-focus works and screen readers don't jump
across rows. Existing aria-labels preserved for accessibility.
- All existing data-testids preserved; keyboard (Enter/Escape) and
blur-to-save behavior unchanged.
- artifacts/tx-os/src/locales/en.json + ar.json
- Added executiveMeetings.schedule.timeStartShort ("Start"/"البدء")
and timeEndShort ("End"/"الانتهاء"). Existing timeStart/timeEnd
("Start time"/"End time") were too long for the compact label slot
above the time inputs and remain in use as aria-labels.
Out of scope (verified):
- API-side PDF renderer at artifacts/api-server/src/lib/pdf-renderer.ts
already uses hour12: true (line 291) — no change needed.
- No tx-os print/PDF page exists; scope confined to the schedule page.
- Audit log timestamps, home/chat clocks, clockHour12 user setting:
out of scope, untouched.
Verification:
- TypeScript clean.
- E2E (English): visual screenshot confirmed schedule shows
"5:39 PM – 5:50 PM" / "6:20 PM – 6:25 PM"; inline editor shows Start/End
labels above inputs in correct LTR order.
- E2E (Arabic): runTest passed — time cells use Latin digits with ص/م
markers; inline editor shows البدء/الانتهاء labels with LTR layout
(start-on-left, end-on-right, save/cancel on right).
- Architect code review: PASS, no critical issues.
- No existing test asserts a specific 24h display string, so no e2e
test breakage introduced.
Notes:
- Pre-existing test workflow failures on executive-meetings*.test.mjs
(500 errors on meeting create + Reorder 400 vs 200) are unrelated to
this task — they pre-date both this task and Task #205.
- Re-applies and extends the 12h direction from earlier Task #160 which
had regressed back to 24h.
|
||
|
|
49bf44f0a2 |
Add functionality to download role permission audit history as a CSV file
Introduce a new admin-only API endpoint for exporting role permission audit data to CSV, including resolved permission names and UTF-8 BOM for Excel compatibility. Frontend button and backend logic implemented to support this feature. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 0cb48020-8f0c-42bb-8fe8-d638905f7fce 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 |
||
|
|
de7973f35b |
Task #293: DB CHECK constraint for executive_meetings.row_color (defense-in-depth for #288)
Mirrors the API-side row-colour whitelist at the database layer so any out-of-band write path (manual psql, future bulk-import jobs, restored backups) cannot smuggle an unrenderable colour past the Zod guard introduced in #288. Changes: - lib/db/src/schema/executive-meetings.ts: export new shared constant EXECUTIVE_MEETING_ROW_COLOR_KEYS (red/amber/green/blue/violet/gray) + ExecutiveMeetingRowColor union type. Add a Drizzle check() constraint named `executive_meetings_row_color_palette_check` that allows NULL or any of the six keys. The CHECK uses sql.raw to inline the palette as quoted SQL literals (PG CHECK definitions are DDL and reject parameter placeholders); safe because the keys come from a hardcoded compile-time constant of single-word identifiers, never user input. Generating the literal list from the same constant guarantees the API guard and the DB constraint stay in sync. - artifacts/api-server/src/routes/executive-meetings.ts: import EXECUTIVE_MEETING_ROW_COLOR_KEYS from @workspace/db and rewire rowColorSchema to z.enum(EXECUTIVE_MEETING_ROW_COLOR_KEYS).nullable(). Deletes the local ROW_COLOR_KEYS const so the palette has exactly one source of truth. No behaviour change at runtime. - artifacts/api-server/tests/executive-meetings-row-color.test.mjs: append a focused defense-in-depth test that bypasses the API and asserts (a) NULL is allowed, (b) each of the six palette keys round-trips on raw UPDATE, (c) off-palette UPDATEs reject with PG SQLSTATE 23514 referencing the constraint by name, (d) the row keeps its previous colour after the rejected updates, and (e) raw INSERT with an off-palette value is rejected the same way. Migration applied via pnpm --filter @workspace/db push (no destructive prompts; existing rows are NULL or valid keys from #288 so the constraint installs cleanly). All 7 tests in the row-color file pass. Architect review: APPROVED, no critical findings. The single pre-existing Reorder test failure in the wider suite is unrelated to this change (Reorder does not touch row_color). |
||
|
|
4d497606d4 |
Task #288: Share Executive Meetings row highlight colors across devices
Per-row highlight colors on the Executive Meetings daily schedule were
stored in each browser's localStorage, so a color set by the admin on
their laptop never reached the executive office or the big-screen
viewer. Move the color into a shared, server-stored field on the
meeting row so every viewer sees the same color in real time.
Schema
- Add nullable `row_color varchar(16)` to `executive_meetings`
(lib/db/src/schema/executive-meetings.ts). Migration applied via
`pnpm --filter @workspace/db push`.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- Whitelist ROW_COLOR_KEYS = red/amber/green/blue/violet/gray.
- Extend meetingPatchSchema with optional `rowColor` (nullable enum).
- Existing PATCH /executive-meetings/:id picks it up, stamps
updatedBy, writes the standard audit-log entry, and fires
emitExecutiveMeetingsDaysChanged so other viewers' day query
invalidates and re-fetches.
- Permission gating unchanged: requireMutate → executive_viewer 403.
Frontend (artifacts/tx-os/src/pages/executive-meetings.tsx)
- `rowColors` is now derived from the meetings query via useMemo.
The localStorage write effect is removed.
- New async setRowColor() PATCHes the server and invalidates the day
query (key ["/api/executive-meetings", date]).
- Best-effort migration of legacy localStorage colors:
- drops invalid keys / unknown colors immediately,
- skips ids the server already colored (no overwrite),
- PATCHes ids in the currently loaded day,
- PRESERVES ids for dates the user hasn't visited yet so they
migrate when they do navigate there (architect review caught a
data-loss bug in the first cut where unvisited-day entries were
being deleted),
- keeps failed PATCHes for retry, removes the localStorage key
only once nothing is left,
- in-flight Set prevents double-PATCH if the effect re-fires.
Tests
- New artifacts/api-server/tests/executive-meetings-row-color.test.mjs
(6 specs) covers PATCH set / clear / invalid-key 400 / viewer 403 /
realtime executive_meetings_changed socket event for the affected
date / audit attribution. All pass.
- E2E (runTest) verified two browser contexts share the color
without manual refresh.
Documentation
- replit.md: added "Task #288 — Shared row colours" section.
Drift / notes
- Pre-existing TS errors in admin.tsx and pre-existing isHighlighted /
font_settings scope errors in executive-meetings.ts are unrelated
to this change.
- Follow-up #293 proposed: add a DB-level CHECK constraint mirroring
the API whitelist for defense-in-depth.
|
||
|
|
46ce8e64e4 |
Task #288: Share Executive Meetings row highlight colors across devices
Per-row highlight colors on the Executive Meetings daily schedule were
stored in each browser's localStorage, so a color set by the admin on
their laptop never reached the executive office or the big-screen
viewer. Move the color into a shared, server-stored field on the
meeting row so every viewer sees the same color in real time.
Schema
- Add nullable `row_color varchar(16)` to `executive_meetings`
(lib/db/src/schema/executive-meetings.ts). Migration applied via
`pnpm --filter @workspace/db push`.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- Whitelist ROW_COLOR_KEYS = red/amber/green/blue/violet/gray.
- Extend meetingPatchSchema with optional `rowColor` (nullable enum).
- Existing PATCH /executive-meetings/:id picks it up, stamps
updatedBy, writes the standard audit-log entry, and fires
emitExecutiveMeetingsDaysChanged so other viewers' day query
invalidates and re-fetches.
- Permission gating unchanged: requireMutate → executive_viewer 403.
Frontend (artifacts/tx-os/src/pages/executive-meetings.tsx)
- `rowColors` is now derived from the meetings query via useMemo.
The localStorage write effect is removed.
- New async setRowColor() PATCHes the server and invalidates the day
query (key ["/api/executive-meetings", date]).
- Best-effort migration of legacy localStorage colors:
- drops invalid keys / unknown colors immediately,
- skips ids the server already colored (no overwrite),
- PATCHes ids in the currently loaded day,
- PRESERVES ids for dates the user hasn't visited yet so they
migrate when they do navigate there (architect review caught a
data-loss bug in the first cut where unvisited-day entries were
being deleted),
- keeps failed PATCHes for retry, removes the localStorage key
only once nothing is left,
- in-flight Set prevents double-PATCH if the effect re-fires.
Tests
- New artifacts/api-server/tests/executive-meetings-row-color.test.mjs
covers PATCH set / clear / invalid-key 400 / viewer 403 / audit
attribution. All 5 pass.
- E2E (runTest) verified two browser contexts share the color
without manual refresh.
Documentation
- replit.md: added "Task #288 — Shared row colours" section.
Drift / notes
- Pre-existing TS errors in admin.tsx and pre-existing isHighlighted /
font_settings scope errors in executive-meetings.ts are unrelated
to this change.
- Follow-up #293 proposed: add a DB-level CHECK constraint mirroring
the API whitelist for defense-in-depth.
|
||
|
|
6876a83bbb |
Audit log: filter by actor (#197)
Admins can now filter the audit log by who acted, not just by what was
acted on.
User-facing changes
- Replaced the flat actor <select> with an autocomplete combobox
(Popover + cmdk Command) that searches by username AND display name,
in English or Arabic.
- Added an `actorId` URL hash param that survives full reload — picks
up alongside the existing target filter on initial mount and is
cleared when the admin navigates to a different section.
- Each audit row's actor avatar + name is now clickable, mirroring the
existing target chip pivot, so admins can jump from "this row" to
"everything by this person" in one click. Rows with a null actor
(system / deleted user) render the same avatar/name without the
click affordance, since the API can only filter by a concrete id.
- Active actor pill renders in sky (target pill remains emerald) and
has a clear button.
- CSV export already accepted `actorUserId` on the backend; the
frontend export call now forwards the filter and the OpenAPI
description has been updated to document it.
Files
- artifacts/tx-os/src/pages/admin.tsx
- New AuditActorPicker component, hash helpers
(parseAuditHashActor / syncAuditHashActor), pivotToActor /
clearActorFilter handlers, sky-colored active pill, clickable
actor in AuditLogRow.
- artifacts/api-server/tests/audit-logs-actor-filter.test.mjs (new)
- 6 tests covering: filter narrows results, excludes other actors
on the same target_type, combines with target filter, invalid
/zero/negative actorUserId → 400, CSV export honors filter.
- artifacts/tx-os/src/locales/{en,ar}.json
- actorSearch / actorEmpty / actorWithName / actorWithId /
clearActorFilter / actorPivotAria.
- lib/api-spec/openapi.yaml
- Audit export endpoint description now mentions
targetType / targetId / actorUserId.
Verification
- pnpm --filter @workspace/tx-os run typecheck → clean.
- All 6 new actor-filter tests + all 7 existing target-filter tests
pass against a live API server.
- E2E run via runTest() succeeded: opened the picker, selected an
actor, verified the pill + reload-safe hash, cleared, pivoted via
a row click, and confirmed CSV export honored the filter.
Notes / drift
- URL key is `actorId` (per task spec), but the React state and API
param remain `actorUserId` to match the existing backend.
- The broader `test` workflow has pre-existing failures in
executive-meetings.test.mjs and service-orders.test.mjs unrelated
to this change (admin 401 / HTML-404 fallback). Captured as
follow-up #291.
Replit-Task-Id: 0f02b232-eda3-46db-8235-98ecce2ebdb7
|
||
|
|
ab5ec2e2e2 |
Add shared row highlighting to executive meeting scheduler
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 |
||
|
|
6c6f9c1848 |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 5670060a-d0e8-4cba-bce2-918e04416bb4 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 |
||
|
|
2a005646f3 |
Task #196: Add inline "Recent activity" sections to admin detail panels
- Added shared `RecentActivityForTarget` component in `artifacts/tx-os/src/pages/admin.tsx` that lists the 10 most recent audit entries for a given (targetType, targetId), reusing `formatAuditSummary` for consistent wording with the audit log section, plus actor + timestamp metadata. Renders loading / empty / list states with stable data-testids.
- Added `openAuditLogForTarget(targetType, targetId)` helper inside `AdminPage`. Writes the URL hash with `section=audit-log&targetType=<t>&targetId=<n>` BEFORE calling `setSection("audit-log")` so the existing section-sync effect (which would otherwise drop section-scoped params on a section change) preserves the deep-link parameters. Also closes any open editor modals.
- Wired the component into all five entity detail panels:
* App edit modal
* Service edit modal
* `UserGroupsEditor` (user edit)
* `GroupDetailEditor` (history tab)
* Role edit dialog
GroupsPanel and RolesPanel previously took no props; both now accept and forward an `onOpenAuditLogForTarget` prop. `OpenAuditLogForTarget` type is shared.
- Added i18n keys `admin.audit.recent.{title,loading,empty,viewAll,viewAllAria}` in both `en.json` and `ar.json`.
- Verified end-to-end via Playwright runTest: deep-link hash, modal closure, and audit-log filter pre-population all work for app/service/group/role/user panels.
No backend changes required (the existing `targetType`/`targetId` filter on `GET /api/audit` was already supported).
Replit-Task-Id: c03451f9-a6bb-4dd4-b082-f34ce3b6001d
|
||
|
|
bb340de455 | Git commit prior to merge | ||
|
|
1b1697c450 |
Add conflict detection and resolution for meeting postponements
Implement optimistic locking for meeting postponements to prevent concurrent edits. The server now requires an `updatedAt` token for postpone requests, returning a 409 conflict error if the meeting has been modified since the token was issued. The client displays a user-friendly prompt allowing users to reapply changes with the latest token. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 1abdc3d2-c834-4a37-b104-397852e4732a 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 |
||
|
|
cc26550f0c |
Task #282: Keep the postpone form visible above the upcoming meeting alert
Problem ------- Clicking "تأجيل" (Postpone) on the floating upcoming-meeting alert opened the postpone modal, but the alert panel — pinned at z-[60] — covered most of the modal. The user could not see or interact with the postpone controls (presets, custom minutes, Confirm). Root cause ---------- - artifacts/tx-os/src/components/executive-meetings/upcoming-meeting-alert.tsx pins the floating panel at `z-[60]`. - artifacts/tx-os/src/components/ui/dialog.tsx (the shared primitive) uses `z-50` for both overlay and content. - Result: the alert always wins the layering fight. Fix --- Scoped to this single flow (the task brief explicitly said NOT to bump the global Dialog z-index, so other dialogs/toasts stay unaffected): - While `postponeOpen` is true, the alert panel <div> gets the `hidden` class (display:none) and `aria-hidden=true`. The modal then sits on a clean stage — no overlap, no hit-test interference. - When the modal closes (Apply, Back, Escape, overlay click), the panel reappears automatically if the meeting is still inside the upcoming-window. Test coverage ------------- - New Playwright case: opens the alert → opens postpone → asserts the alert is hidden, the postpone panel + chip-10 are visible AND clickable (proving they're not behind an overlay) → presses Escape → asserts the alert returns. Passes in 10.8s. - Re-ran all 5 existing Postpone-tagged tests against the patched component — 5/5 pass (~37.9s). Architect review ---------------- PASS. Confirmed: scope is non-leaky (only UpcomingMeetingAlert touched), behavior is direction-agnostic (no RTL branch needed), no regression to Done/Dismiss/Open Meetings/Reschedule/Cancel paths, accessibility correct (display:none removes from a11y tree). Deviations ---------- None. Implementation matches the task brief exactly. |
||
|
|
6447908548 |
Task #195: Audit-log every service deletion, not only forced ones
Background
----------
`DELETE /api/services/:id` previously wrote an audit_logs row only when
hasDeps && force was true. A clean delete (no orders, or ?force=false on a
service with no dependents) left no trace, so admins reviewing the Audit
Log could not tell who removed a service or when, and the new
"Target: service #… · Name" line never appeared for routine deletions.
Implementation status (already in tree from prior work in this branch)
----------------------------------------------------------------------
- artifacts/api-server/src/routes/services.ts wraps the delete + audit
insert in a single db.transaction:
* hasDeps && force -> service.force_delete with { nameEn, nameAr,
orderCount } (unchanged on-the-wire shape, so existing forced-only
filter and target-filter behavior is preserved).
* otherwise (no deps, or force=true with no deps) -> new service.delete
row with { nameEn, nameAr } and the actor.
- artifacts/tx-os/src/lib/audit-summary.ts already has a service.delete
case rendering admin.audit.summary.service.delete (with name) or
service.deleteId (id-only fallback) in both EN and AR.
- delete-force-warnings.test.mjs already covers the route-level no-deps
and ?force=true-with-no-deps paths.
This commit
-----------
Adds the canonical audit-log coverage tests requested by the task to
artifacts/api-server/tests/audit-log-coverage.test.mjs:
- "DELETE /api/services/:id (no deps, no force)" -> exactly one
service.delete row with nameEn + nameAr, no orderCount, and no
service.force_delete companion.
- "DELETE /api/services/:id without force on a service with deps" ->
409 + service still present + zero audit rows of either action.
- "DELETE /api/services/:id?force=true (with deps)" -> exactly one
service.force_delete row with orderCount=2 and no plain service.delete
companion.
Also adds an insertService helper, a createdServiceIds bucket, and a
service_orders/services teardown block to keep the suite self-cleaning.
Verification
------------
- node --test tests/audit-log-coverage.test.mjs -> 29/29 pass (3 new).
- node --test tests/delete-force-warnings.test.mjs tests/service-orders.test.mjs
tests/audit-logs-forced-only-filter.test.mjs
tests/audit-logs-target-filter.test.mjs -> 34/34 pass (no regressions).
Deviations
----------
None. Scope matches the task brief exactly.
Replit-Task-Id: b12a13e6-32dd-4707-8a46-ea7a4e6336d9
|
||
|
|
50627b5b74 |
Add real-time updates for meeting alerts across user devices
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 |
||
|
|
9607a02047 |
Task #194: Show deleted user's full name in admin Audit Log
Original task
-------------
The admin Audit Log row for a user.delete entry showed
"Target: user #1234 · @ahmed" — i.e. the @username — even though
admins recognise people by name. Surface displayNameEn / displayNameAr
in that line and fall back to @username only when no display name
exists.
Backend
-------
artifacts/api-server/src/routes/users.ts
The user.delete handler already persists `displayNameEn` and
`displayNameAr` in audit metadata (added in commit
|
||
|
|
b54761b166 |
Task #192: Show row color + merge range previews in row-actions kebab menu
Original task: The Executive Meetings row-actions kebab menu has three sub-views
(delete, color, merge). Users couldn't tell at a glance which colour or merge
range a row already had — they had to drill into the sub-view first. Add subtle
inline indicators to the main menu so the current state is visible without
extra clicks.
Implementation:
- artifacts/tx-os/src/pages/executive-meetings.tsx
• RowActionsMenu now renders an inline circular swatch next to the "Row
color" item, reflecting the row's saved colour. "default" shows a hatched
pattern matching the swatch picker; other keys show the saved tint.
Decorative (aria-hidden) since the swatch sub-view is the actual control.
Carries data-testid="em-row-color-indicator-{id}" plus a data-color-key
attribute for stable, language-agnostic tests.
• Renders a "Merged: <cols>" badge next to the "Merge cells" item only when
the row has a stored merge. Column names come from the canonical merge
order (number, meeting, attendees, time) and are localized via the
existing executiveMeetings.col.{id} keys.
• New mergeColumnLabels prop is computed in MeetingRow against
CANONICAL_MERGE_ORDER (not visibleColumns) so the badge still describes
the full saved range when a boundary column is hidden. Threaded into
both call sites — the first-visible-cell anchor and the merged-cell
anchor.
• Widened the local t prop type to (k, opts?) so the badge can use the
{{cols}} interpolation template.
- artifacts/tx-os/src/locales/en.json + ar.json
• Added executiveMeetings.merge.activeBadge ("Merged: {{cols}}" /
"مدموج: {{cols}}").
Tests:
- Added artifacts/tx-os/tests/executive-meetings-row-actions-previews.spec.mjs
with two specs:
1. Row-color indicator updates from "default" → "red" → "default" via the
sub-view picker, and the dot's computed background tracks the saved
colour (#fee2e2 → rgb(254,226,226)).
2. Plain rows render no merge badge; merged rows render the badge with
text "<MeetingColLabel> + <AttendeesColLabel>". Column labels are
resolved from the live header DOM so the assertion works in both
English and Arabic (admin's preferredLanguage overrides the
tx-lang=en init seed after login).
Both pass; existing executive-meetings-row-actions-menu.spec.mjs and
executive-meetings-merge.spec.mjs continue to pass (5/5 regression).
Notable subtleties:
- A first attempt at the merge spec hit a flake where Escape-then-force-click
on the next row's kebab landed on the previous popover's Delete item
(which was still mounted), triggering a stray confirm dialog. Added an
explicit waitFor on the previous popover's unmount before opening the
next one.
Follow-up proposed: #276 (next_steps) — show the same cues directly on the
row itself, not only inside the kebab popover.
Replit-Task-Id: 7ff92dcd-f4bd-421d-93c8-4d7d3dbe0077
|
||
|
|
0fe2b8a781 |
Task #275: Postpone confirm + compact 3-tab dialog
Refactor the 5-min upcoming-meeting alert's postpone sub-dialog: - Add an explicit "Are you sure?" confirm step before any postpone action (matches the existing Cancel pattern). Both minute chips (5/10/15/30/45/60) and the manual "Apply now" button now route through requestPostpone() -> amber confirm block -> postponeBy(). Switching tabs clears any armed pending-postpone state. - Replace the three stacked bordered sections with a compact 3-tab layout (Postpone / Reschedule / Cancel). Default tab is Postpone. Reschedule grid is responsive (grid-cols-1 sm:grid-cols-3) so the dialog no longer overflows on common laptop viewports. - Implement full ARIA tab pattern: role="tablist"/"tab"/"tabpanel", id + aria-controls + aria-labelledby, roving tabindex, and ArrowLeft/Right/Home/End keyboard nav (RTL-aware). Focus moves to the newly active tab. - Bilingual: added EN+AR keys (tabPostpone, tabReschedule, tabCancel, postponeConfirmPrompt, postponeConfirmYes, rescheduleHint), trimmed postponeIntro, removed obsolete *Label keys. - Spec updated: existing "Postpone by 10", "chip immediately shifts", "Reschedule", and both "Cancel" tests now click the new tabs and go through the confirm step. Added two new tests: "Back on postpone confirm leaves meeting unchanged" and "switching tabs clears armed postpone confirm". Out of scope: server route changes, realtime alert-state push, the floating alert panel itself. Architect review: APPROVED. Reschedule grid widened to follow the literal spec progression (grid-cols-1 sm:grid-cols-2 md:grid-cols-3) per the architect's only non-blocking comment. All 11 spec tests pass in batch (1.3m, single worker, with retry). Earlier intermittent flakes were traced to leftover seeded test rows from prior runs (cleaned) and pre-existing polling tightness — not regressions from this task. |
||
|
|
84c2efc7a9 |
Task #275: Postpone confirm + compact 3-tab dialog
Refactor the 5-min upcoming-meeting alert's postpone sub-dialog: - Add an explicit "Are you sure?" confirm step before any postpone action (matches the existing Cancel pattern). Both minute chips (5/10/15/30/45/60) and the manual "Apply now" button now route through requestPostpone() -> amber confirm block -> postponeBy(). Switching tabs clears any armed pending-postpone state. - Replace the three stacked bordered sections with a compact 3-tab layout (Postpone / Reschedule / Cancel). Default tab is Postpone. Reschedule grid is responsive (grid-cols-1 sm:grid-cols-3) so the dialog no longer overflows on common laptop viewports. - Implement full ARIA tab pattern: role="tablist"/"tab"/"tabpanel", id + aria-controls + aria-labelledby, roving tabindex, and ArrowLeft/Right/Home/End keyboard nav (RTL-aware). Focus moves to the newly active tab. - Bilingual: added EN+AR keys (tabPostpone, tabReschedule, tabCancel, postponeConfirmPrompt, postponeConfirmYes, rescheduleHint), trimmed postponeIntro, removed obsolete *Label keys. - Spec updated: existing "Postpone by 10", "chip immediately shifts", "Reschedule", and both "Cancel" tests now click the new tabs and go through the confirm step. Added two new tests: "Back on postpone confirm leaves meeting unchanged" and "switching tabs clears armed postpone confirm". Out of scope: server route changes, realtime alert-state push, the floating alert panel itself. Architect review: PASS / mergeable. All 11 spec tests pass in isolation; intermittent batch-run flakes were traced to leftover seeded test rows from prior runs (cleaned) and pre-existing polling tightness, not regressions. |
||
|
|
cbda85fc73 |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a686e421-ab75-4435-aa36-9b1065d2a1ed 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 |
||
|
|
f0888b6b82 |
Task #188: Add browser test for reorder rollback when the API rejects a row drag
Added a Playwright e2e scenario in
`artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs`
that exercises the optimistic-update + onError rollback path in
`reorderRows()` (artifacts/tx-os/src/pages/executive-meetings.tsx ~L1484).
What the test does:
- Seeds two meetings (A, B) on a unique future date via direct DB inserts.
- Logs in as admin, navigates to /executive-meetings, jumps to that date,
enables edit mode.
- Captures the original DOM order [A, B].
- Installs a `page.route("**/api/executive-meetings/reorder")` override
that responds with 500 + `{ error: "Simulated reorder failure" }`. The
`error` field (not `message`) is intentional so apiJson() throws that
exact string into the destructive toast description.
- Drags row B above row A using the same mechanic as the success-path
reorder test (warm-up move past dnd-kit's 6px PointerSensor activation
distance, then a stepped move targeting just above the row's vertical
center).
- Waits for the intercepted 500, then asserts the toast description
("Simulated reorder failure") and a localized title (en or ar) is
visible — proving the user-facing error surfaced.
- Polls the DOM to confirm the row order rolled back to [A, B].
- Defense-in-depth: queries the DB to confirm daily_number / start_time /
end_time for both rows are unchanged, so a future regression that
somehow bypasses the route override can't hide.
- Cleans up: page.unroute, and the existing afterAll deletes the seeded
rows.
Test seeding:
- Uses uniqueFutureDate(6); offsets 1..5 are already claimed by other
tests in this file, and the file-level afterAll cleanup means two
tests sharing date + daily_number would collide on the
UNIQUE (meeting_date, daily_number) index.
Verified by running:
npx playwright test --grep "rolls back to the original order"
and the prior success-path drag test together — both pass.
No production code changes; this is a pure test addition.
Replit-Task-Id: 02cfe898-1db8-40e9-ba6b-cd5df9f0a3f4
|
||
|
|
b92903d0d3 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Hardening
- POST /executive-meetings/:id/postpone-minutes treats end == 24:00 as
a midnight-crossing wrap (the guard is now `>=` on both start and
end), so the route never produces an end_time that formats to
00:00:00 on the same day.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
ed64fb6442 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Hardening
- POST /executive-meetings/:id/postpone-minutes treats end == 24:00 as
a midnight-crossing wrap (the guard is now `>=` on both start and
end), so the route never produces an end_time that formats to
00:00:00 on the same day.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
f729026ce0 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Hardening
- POST /executive-meetings/:id/postpone-minutes treats end == 24:00 as
a midnight-crossing wrap (the guard is now `>=` on both start and
end), so the route never produces an end_time that formats to
00:00:00 on the same day.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
c3a9f0b146 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Hardening
- POST /executive-meetings/:id/postpone-minutes treats end == 24:00 as
a midnight-crossing wrap (the guard is now `>=` on both start and
end), so the route never produces an end_time that formats to
00:00:00 on the same day.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
0c9ebdf645 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Hardening
- POST /executive-meetings/:id/postpone-minutes treats end == 24:00 as
a midnight-crossing wrap (the guard is now `>=` on both start and
end), so the route never produces an end_time that formats to
00:00:00 on the same day.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
a180143430 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Hardening
- GET /executive-meetings/alert-state now rejects any date that is not
the server's current local date (returns 400 date_not_today).
- POST /executive-meetings/:id/postpone-minutes treats end == 24:00 as
a midnight-crossing wrap (the guard is now `>=` on both start and
end), so the route never produces an end_time that formats to
00:00:00 on the same day.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
f7a789ade3 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
ad2f7615fb |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
0cf44b62b2 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Copy
- "Dismiss alert" / "تجاهل التنبيه" wording on the dismiss control
(and the toast that follows) to match the product spec.
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
ea62e3382f |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Postpone chips fire immediately
- Each preset minute chip (5/10/15/30/45/60) now calls postponeBy(n)
on a single click — no second Apply step. The manual minute input +
Apply button remain for custom/fractional values.
Viewport-resize clamping
- A useEffect in UpcomingMeetingAlert listens for `resize` and
`orientationchange` and clamps the floating panel's position back
inside the current viewport, also re-clamps once on mount so a
stale localStorage position from a wider viewport is corrected.
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
9 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Single chip click immediately shifts start/end by +10 minutes
AND surfaces the conflict-warning toast
6. Re-clamps the panel back into the viewport when the window
shrinks (seeds a stale right-edge localStorage position at
1400px, then resizes to 420px and asserts the bounding box)
7. Reschedule to a different day clears today's alert
8. Cancel removes the meeting from today's schedule and renumbers
the survivor
9. Arabic locale renders the RTL alert with Arabic title
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
bfb47b7fea |
Task #273: 5-minute pre-meeting alert for Executive Meetings
Floating, draggable alert that appears on every Tx OS page when an
Executive Meeting is within five minutes of starting.
Schema
- New table `executive_meeting_alert_state (meetingId, userId,
dismissed, acknowledged, updatedAt)` with unique (meetingId,userId)
in lib/db/src/schema/executive-meetings.ts. Apply via
`pnpm --filter @workspace/db run push-force` per environment.
API (artifacts/api-server/src/routes/executive-meetings.ts)
- GET /executive-meetings/alert-state?date=YYYY-MM-DD
- POST /executive-meetings/:id/alert-state (action: shown|acknowledged|dismissed)
- race-safe: onConflictDoNothing upsert + conditional UPDATE … RETURNING.
- POST /executive-meetings/:id/postpone-minutes
- POST /executive-meetings/:id/reschedule
- POST /executive-meetings/:id/cancel
- all three lock the meeting row inside the tx with SELECT … FOR UPDATE,
compute oldValue from the locked row, run conflict detection in the
same tx snapshot, and write the audit row before commit;
- cancel is idempotent (no duplicate audit if already cancelled);
- postpone-minutes rejects ranges that would cross midnight (use
reschedule for cross-day moves).
- New helper `renumberDayByStartTime(tx, date)` runs inside each mutation
tx and rewrites `daily_number` for every meeting on the affected
date(s): active meetings 1..N by start_time, cancelled meetings
pushed to the tail. Uses a negative-shift dance so the
(meeting_date, daily_number) unique index never trips mid-update.
Reschedule renumbers both the old and new date when the day moves.
- detectMeetingConflicts now accepts a tx-like executor so the conflict
scan reads the same DB snapshot as the UPDATE that just shifted the row.
Frontend
- New component artifacts/tx-os/src/components/executive-meetings/
upcoming-meeting-alert.tsx — globally mounted in App.tsx inside
AuthProvider. Draggable with localStorage position persistence,
RTL-aware, polls every 30 s, shows start–end window, postpone-by-
minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a
cancel-meeting flow that requires an explicit confirm step before
the destructive call fires.
- Eligibility window is strict 0 < remainingMinutes <= 5 — the alert
hides as soon as the meeting actually starts.
- Primary action buttons: Done, Postpone (when canMutate), Dismiss
(in addition to the X icon).
- artifacts/tx-os/src/pages/executive-meetings.tsx schedule view now
filters out `status === "cancelled"` from the displayed list, so a
cancelled meeting disappears from today's view (still queryable via
the API for archive/audit consumers).
i18n
- New `executiveMeetings.alert.*` keys in both en.json and ar.json
(postpone, reschedule, cancel, cancel-confirm prompt, conflict
warning, etc.).
Tests
- artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs —
8 Playwright scenarios, all green:
1. Done acknowledges the alert
2. Postpone-10 shifts both start and end and clears the alert
3. Cancel-with-confirm marks the meeting cancelled
4. Dismiss (X) writes a dismissed audit row
5. Postpone-chip-10 + conflict-warning toast
6. Reschedule to a different day clears today's alert
7. Cancel removes the meeting from today's schedule and renumbers
the survivor
8. Arabic locale renders the RTL alert with Arabic title
Docs
- replit.md updated with the new table, routes, and migration step.
Pre-existing tsc errors at lines 546, 662, and 2107 of
executive-meetings.ts are unrelated to this task.
|
||
|
|
a72c00fe19 |
Task #273: 5-minute pre-meeting alert for Executive Meetings
- New `executive_meeting_alert_state` table (per-user, per-meeting) tracking `dismissed`/`acknowledged`. Migration via `pnpm --filter @workspace/db run push-force`. - New API routes in artifacts/api-server/src/routes/executive-meetings.ts: GET /alert-state, POST /:id/alert-state, POST /:id/postpone-minutes, POST /:id/reschedule, POST /:id/cancel. All three mutation routes acquire a row lock with SELECT ... FOR UPDATE inside the transaction, compute oldValue from the locked snapshot, run conflict detection in the same tx, and write the audit row before commit. Cancel is idempotent. Postpone-minutes rejects ranges that would cross midnight (use reschedule for cross-day moves). - Alert-state route uses race-safe onConflictDoNothing upsert + a conditional UPDATE ... RETURNING so transition audits never duplicate. - New i18n keys `executiveMeetings.alert.*` in en.json + ar.json, including the cancel-confirm prompt. - New component artifacts/tx-os/src/components/executive-meetings/ upcoming-meeting-alert.tsx — globally mounted in App.tsx inside AuthProvider. Draggable with localStorage position persistence, RTL-aware, polls every 30s, shows start–end window, postpone-by- minutes chips [5,10,15,30,45,60], full reschedule sub-form, and a Cancel-meeting flow that requires an explicit confirm step before the destructive call fires (gated on confirmCancel state). - Playwright spec executive-meetings-upcoming-alert.spec.mjs with 6 scenarios: appear+Done, postpone-10 shifts times, cancel-with- confirm, dismiss audit, postpone-chip+conflict-warning toast, AR/RTL render. All 6 pass. - replit.md updated with the new table, routes, and migration step. Pre-existing tsc errors at lines 489, 605, and 2039 of executive-meetings.ts are not touched by this task. |
||
|
|
5bd5eb9dc7 |
Transitioned from Plan to Build mode
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: bb3089c6-5d21-4679-9f33-af781d0bf496 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/rSPepbs Replit-Helium-Checkpoint-Created: true |
||
|
|
a29f7a25c6 |
Add browser test for drag-to-reorder schedule columns (task #187)
Adds a Playwright scenario to artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs that: - Logs in as admin, seeds one meeting on a far-future date, and lands on the schedule for that date. - Hops to the Settings tab to confirm the column customizer panel (em-customize-columns-panel) still surfaces the feature, then bounces back to the schedule. - Enters edit mode (required for SortableHeader to register dnd-kit attributes/listeners) and asserts the default header order (number, meeting, attendees, time) via th[data-testid^=em-col-header-]. - Drags the "attendees" header above the "meeting" header using a warm-up move past the 6px PointerSensor activation distance and a stepped pointer move whose end point lands slightly LEFT of the target's horizontal center (horizontalListSortingStrategy needs the drop point on the leading side to insert before the target). - Polls the rendered headers for the new order (number, attendees, meeting, time), then reads em-schedule-cols-v1 from localStorage to confirm persistence. - Reloads the page and re-asserts the new order to prove it restores from localStorage rather than reverting to DEFAULT_COLUMNS. Deviation from the task wording: the task description says the test should "open the customize-columns popover" and "drag one column chip above another". The customize panel is no longer a popover — it was moved into the Settings tab in #265 — and it never had draggable chips; column reordering is wired to the SortableHeader cells inside the schedule's floating thead. The new test honors the spirit of the task by visiting the Settings panel for sanity, then performing the actual reorder gesture on the table headers (the only mechanism the codebase exposes). Validation: the new test passes in isolation and as part of the schedule-features suite. One unrelated existing test ("custom highlight color paints the current meeting's box-shadow ring") is currently flaky/failing on its own without my changes; left untouched as it is outside this task's scope. Replit-Task-Id: 06c14e68-096b-407d-86b9-bd5a45674aee |
||
|
|
bbf494f2a6 | Git commit prior to merge | ||
|
|
d55c7ee8bb |
Add browser test for editing attendee names with formatting (Task #186)
Original task: cover the inline attendee-name edit flow in the Executive Meetings schedule. The attendee row uses the same EditableCell + Tiptap toolbar as titles (parent task #122), but until now only the title path had end-to-end coverage. A regression in attendee-name formatting would only have surfaced via manual QA. Changes: - Extended artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs with a new scenario: * seeds a meeting + one plain-text attendee directly in the DB, * logs in as admin, navigates to the schedule, jumps to the seeded date, flips on edit mode, * clicks the inline attendee EditableCell, selects all, applies bold + the red color swatch via the toolbar, and saves via the check button while waiting for PUT /api/executive-meetings/:id/attendees to succeed, * reloads the page, re-navigates to the date, and asserts the rendered HTML in the cell still contains <strong>/<b> and the red color (#dc2626 or rgb(220,38,38)), * additionally queries executive_meeting_attendees.name in the DB to confirm the formatted HTML round-tripped through the server, not just survived in the local Tiptap document. - Added a small insertAttendee helper in the same spec to seed initial state without driving the manage-dialog flow. - The existing afterAll already deletes attendee rows for the created meetings (cascade-safe), so no cleanup changes were needed. Verification: ran the new test in isolation (passed in 9.1s) and the full schedule-features spec (5/5 passed in 40.1s). No code changes outside the test file. Follow-up filed: #270 — browser test for clearing an attendee's name to trigger the delete-row gesture (separate documented behavior path with no end-to-end coverage today). Replit-Task-Id: 68a0ce72-6cdf-45e0-a7c7-4fea9c833fe7 |