Task #315: 12h time picker with explicit AM/PM (executive meetings)

Replaces native <input type="time"> in the executive-meetings editor
with a TimePicker12h that always requires an explicit AM/PM choice
for any 1–12 hour, fixing the "1:15 silently saved as 01:15 (AM)
when user meant 13:15 (PM)" bug. Display stays compact 12h with no
AM/PM marker (per #292); wire format remains canonical HH:mm 24h.

What ships
- src/lib/time-12h.ts: periodFromCanonical, formatCanonicalAs12h,
  combineTextWithPeriod (returns "ambiguous" for any unmarked 1–12
  hour without a toggle pick; accepts hours 0 and 13–23 directly
  because they are unambiguous 24h shapes; typed marker always
  wins over toggle).
- src/components/time-picker-12h.tsx: forwardRef component with
  imperative {commit, focus, select} handle, dirtyRef to avoid
  prop overwrite mid-edit, dir="ltr" for stable RTL layout, and
  Enter-on-toggle = "set AND save".
- Wired into TimeRangeCell inline editor (commit-based save handles
  invalid/ambiguous/TimeOrderError as toasts and refocuses).
- Wired into MeetingFormDialog with picker refs + handleSaveClick
  validation: Save button now calls commit() on both pickers,
  blocks on invalid/ambiguous (toast + focus offending picker),
  and passes canonical values straight to the parent's save handler
  so a stale React state batch from onChange-suppression cannot
  silently persist the previous value.

Tests
- 12 unit tests in src/__tests__/time-12h.test.mjs (incl. new
  "hour 0 is unambiguous, accepted without a toggle").
- 17 e2e tests in tests/executive-meetings-keyboard-editing.spec.mjs
  (incl. "compact + PM toggle" 0115→13:15 and "Tab walks start
  input → start AM/PM toggle → end input → end AM/PM toggle").
- Existing manage-create e2e suite still green.

Locale keys (en + ar)
- executiveMeetings.timeEditor.{am,pm,periodGroupStart,periodGroupEnd}
- executiveMeetings.schedule.timeAmbiguousError

Deviations from plan
- Architect review #1 caught two issues that were both fixed:
  (1) combineTextWithPeriod treated hour 0 as ambiguous; now
      accepts 00:xx unambiguously.
  (2) MeetingFormDialog initially used pickers via value/onChange
      only, which left a silent-fallback hole because onChange
      suppresses ambiguous drafts. Added imperative refs +
      handleSaveClick validation; onSave signature changed to
      (committedStart, committedEnd) so the parent's save() can
      use the freshly-committed values directly.
- Reverted accidental vite.config.ts typo (@replit/... was missing
  the leading @).
This commit is contained in:
riyadhafraa
2026-05-02 11:18:52 +00:00
parent 79e03053aa
commit e63a7f8193
8 changed files with 936 additions and 108 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB