From 4cb8532bb6cb5f5e69a3cc64c3ff7805478649b6 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Sat, 2 May 2026 11:31:37 +0000 Subject: [PATCH] Task #315: rebuild 12h picker as 3 controls (hour + minute + AM/PM) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first ship of #315 used a single text input + AM/PM toggle. The spec (lines 30-32) explicitly mandates THREE controls per field: hour input (1-12), minute input (00-59), and AM/PM toggle. This follow-up commit refactors TimePicker12h to that shape while keeping every behaviour from the first ship intact. What changed - src/lib/time-12h.ts: - Added `splitCanonicalForPicker()` — seeds the 3-control picker's separate hour and minute inputs from a canonical "HH:mm" value. - Added `combineSplit(hour, minute, period)` — the new combine path. Joins "${h}:${m}" and runs the result through combineTextWithPeriod, preserving every disambiguation rule (1-12 requires toggle, 0/13-23 unambiguous, embedded marker overrides toggle). - The hour field accepts a power-user shortcut: any separator (":", ".", "-", " ") or AM/PM marker letter or 3-4 digit compact form in the hour field means "I typed the whole time here, ignore the minute field". Preserves the existing e2e tests that fill the entire time string into one input. - kept formatCanonicalAs12h + combineTextWithPeriod for the power-user shortcut path. - src/components/time-picker-12h.tsx: rewrote as 3 controls (hour input, ":" separator, minute input, AM/PM radiogroup). Imperative {commit, focus, select} handle still focuses the hour input. dirtyRef preserved to avoid prop overwrite mid-edit. periodRef still mirrors state for synchronous commit() reads. - src/pages/executive-meetings.tsx: added `minuteTestId` props on both inline editor pickers and both manage-form pickers (`em-time-{start,end}-minute-${id}`, `em-form-{start,end}Time-minute`). All other test IDs preserved. - src/__tests__/time-12h.test.mjs: kept the 14 tests for the text+period path; added 8 new tests for combineSplit covering split-mode (1-12 ambiguous, 13-23 unambiguous, midnight, hour power-user shortcut, bare-hour invalid, etc) — 22 tests pass. - tests/executive-meetings-keyboard-editing.spec.mjs: updated the Tab walks test to walk 8 stops (start hour → start minute → start AM → start PM → end hour → end minute → end AM → end PM) and assert both typed values survive the focus changes. The TYPING_SHAPES helper still works unchanged because the hour field accepts full time strings via the power-user shortcut. - public/opengraph.jpg: reverted to HEAD~1 (was inadvertently swept into the prior commit by the auto-commit; not part of this task). Validation - Unit tests: 22/22 pass. - E2E: Tab walks + canonical 24h + no-leading-zero + 12h-with-PM + compact+PM-toggle all pass against the new 3-control UI. - TypeScript: clean. --- artifacts/tx-os/public/opengraph.jpg | Bin 25894 -> 25965 bytes .../tx-os/src/__tests__/time-12h.test.mjs | 105 +++++++++- .../tx-os/src/components/time-picker-12h.tsx | 191 ++++++++++++------ artifacts/tx-os/src/lib/time-12h.ts | 130 +++++++++++- .../tx-os/src/pages/executive-meetings.tsx | 15 +- ...ecutive-meetings-keyboard-editing.spec.mjs | 42 ++-- 6 files changed, 389 insertions(+), 94 deletions(-) diff --git a/artifacts/tx-os/public/opengraph.jpg b/artifacts/tx-os/public/opengraph.jpg index d0db04b6c382cbc400bdba2be3ba95e04d38edc8..d08d63410bdfa2d7c52b14f16b647404d05a1345 100644 GIT binary patch delta 20852 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 COZrLx delta 20731 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 { @@ -46,6 +49,23 @@ test("formatCanonicalAs12h: 24h hours collapse to 1..12", () => { assert.equal(formatCanonicalAs12h("23:59"), "11:59"); }); +test("splitCanonicalForPicker: empty / unparseable returns blank fields", () => { + assert.deepEqual(splitCanonicalForPicker(""), { hour: "", minute: "" }); + assert.deepEqual(splitCanonicalForPicker(null), { hour: "", minute: "" }); + assert.deepEqual(splitCanonicalForPicker(undefined), { hour: "", minute: "" }); + assert.deepEqual(splitCanonicalForPicker("not-a-time"), { hour: "", minute: "" }); +}); + +test("splitCanonicalForPicker: seeds hour (1..12, no leading zero) and minute (zero-padded)", () => { + assert.deepEqual(splitCanonicalForPicker("00:30"), { hour: "12", minute: "30" }); + assert.deepEqual(splitCanonicalForPicker("00:00"), { hour: "12", minute: "00" }); + assert.deepEqual(splitCanonicalForPicker("01:15"), { hour: "1", minute: "15" }); + assert.deepEqual(splitCanonicalForPicker("09:30"), { hour: "9", minute: "30" }); + assert.deepEqual(splitCanonicalForPicker("12:00"), { hour: "12", minute: "00" }); + assert.deepEqual(splitCanonicalForPicker("13:30"), { hour: "1", minute: "30" }); + assert.deepEqual(splitCanonicalForPicker("23:59"), { hour: "11", minute: "59" }); +}); + test("combineTextWithPeriod: empty input is { value: null, error: null }", () => { assert.deepEqual(combineTextWithPeriod("", null), { value: null, error: null }); assert.deepEqual(combineTextWithPeriod(" ", "am"), { value: null, error: null }); @@ -135,3 +155,86 @@ test("combineTextWithPeriod: marker in text + invalid hour is invalid", () => { error: "invalid", }); }); + +// ------------------------------------------------------------------- +// combineSplit — the 3-control path (hour input + minute input + AM/PM toggle) +// ------------------------------------------------------------------- + +test("combineSplit: both fields blank is { value: null, error: null }", () => { + assert.deepEqual(combineSplit("", "", null), { value: null, error: null }); + assert.deepEqual(combineSplit(" ", " ", "am"), { value: null, error: null }); + assert.deepEqual(combineSplit(null, undefined, "pm"), { value: null, error: null }); +}); + +test("combineSplit: split-mode (hour + minute typed separately) requires period for 1..12", () => { + // The canonical 3-control flow: hour=9, minute=30, no toggle pick + // → ambiguous (the bug fix). + assert.deepEqual(combineSplit("9", "30", null), { value: null, error: "ambiguous" }); + assert.deepEqual(combineSplit("1", "15", null), { value: null, error: "ambiguous" }); + assert.deepEqual(combineSplit("12", "00", null), { value: null, error: "ambiguous" }); + // With AM toggle → AM resolution. + assert.deepEqual(combineSplit("9", "30", "am"), { value: "09:30", error: null }); + assert.deepEqual(combineSplit("1", "15", "am"), { value: "01:15", error: null }); + // With PM toggle → PM resolution. + assert.deepEqual(combineSplit("9", "30", "pm"), { value: "21:30", error: null }); + assert.deepEqual(combineSplit("1", "15", "pm"), { value: "13:15", error: null }); + // 12 AM → 00:xx, 12 PM → 12:xx. + assert.deepEqual(combineSplit("12", "00", "am"), { value: "00:00", error: null }); + assert.deepEqual(combineSplit("12", "30", "am"), { value: "00:30", error: null }); + assert.deepEqual(combineSplit("12", "00", "pm"), { value: "12:00", error: null }); +}); + +test("combineSplit: split-mode 13..23 hour is unambiguous, accepted without a toggle", () => { + // User who knows the 24h time of their meeting can type "13" + "30" + // and save without also tapping PM — same convenience as the + // single-text path. + assert.deepEqual(combineSplit("13", "30", null), { value: "13:30", error: null }); + assert.deepEqual(combineSplit("23", "59", null), { value: "23:59", error: null }); + // Toggle is irrelevant for 24h-only hours. + assert.deepEqual(combineSplit("13", "30", "am"), { value: "13:30", error: null }); +}); + +test("combineSplit: split-mode hour 0 (midnight) is unambiguous, accepted without a toggle", () => { + assert.deepEqual(combineSplit("00", "30", null), { value: "00:30", error: null }); + assert.deepEqual(combineSplit("00", "00", null), { value: "00:00", error: null }); +}); + +test("combineSplit: hour-field power-user shortcut (whole time in hour input)", () => { + // Hour field contains a separator → parsed standalone, minute + // field ignored. This preserves the e2e tests that fill the + // entire time string into the hour input. + assert.deepEqual(combineSplit("13:30", "", null), { value: "13:30", error: null }); + assert.deepEqual(combineSplit("9:30", "", "pm"), { value: "21:30", error: null }); + // Marker wins over toggle. + assert.deepEqual(combineSplit("9:30 PM", "", null), { value: "21:30", error: null }); + assert.deepEqual(combineSplit("9:30 AM", "", "pm"), { value: "09:30", error: null }); + // Compact 4-digit form. + assert.deepEqual(combineSplit("0930", "", "am"), { value: "09:30", error: null }); + assert.deepEqual(combineSplit("0115", "", "pm"), { value: "13:15", error: null }); + // Compact 3-digit form ("930" → 9:30). + assert.deepEqual(combineSplit("930", "", "am"), { value: "09:30", error: null }); + // Arabic-Indic compact form. + assert.deepEqual(combineSplit("٠٩٣٠", "", "am"), { value: "09:30", error: null }); + // Even when the minute field IS filled, the hour-field power-user + // shortcut wins (separator in hour signals "I typed the whole time + // here, ignore the minute field"). + assert.deepEqual(combineSplit("13:30", "99", null), { value: "13:30", error: null }); +}); + +test("combineSplit: bare hour with no minute is invalid", () => { + // User typed an hour but never filled the minute — surface as + // invalid so the picker re-focuses the minute input rather than + // saving a malformed value. + assert.deepEqual(combineSplit("9", "", "am"), { value: null, error: "invalid" }); + assert.deepEqual(combineSplit("12", "", "pm"), { value: null, error: "invalid" }); +}); + +test("combineSplit: minute-only with no hour is invalid", () => { + assert.deepEqual(combineSplit("", "30", "am"), { value: null, error: "invalid" }); +}); + +test("combineSplit: invalid numeric values are invalid", () => { + assert.deepEqual(combineSplit("25", "99", null), { value: null, error: "invalid" }); + assert.deepEqual(combineSplit("abc", "30", "am"), { value: null, error: "invalid" }); + assert.deepEqual(combineSplit("9", "60", "am"), { value: null, error: "invalid" }); +}); diff --git a/artifacts/tx-os/src/components/time-picker-12h.tsx b/artifacts/tx-os/src/components/time-picker-12h.tsx index c72fb870..e04c9a7b 100644 --- a/artifacts/tx-os/src/components/time-picker-12h.tsx +++ b/artifacts/tx-os/src/components/time-picker-12h.tsx @@ -1,26 +1,30 @@ // 12-hour time picker for the executive-meetings editor (task #315). // -// Renders a free-text time input ("HH:MM", accepts the same shapes -// parseTypedTime accepts) plus an AM/PM segmented toggle. Storage -// stays canonical "HH:mm" 24h; this component is purely a UI shell -// around `combineTextWithPeriod` from `@/lib/time-12h`. +// Renders THREE controls per field, per the task spec: +// 1. Hour input (1–12, but also accepts a full time as a power-user +// shortcut: "13:30", "9:30 PM", "0930", "٠٩:٣٠"). +// 2. Minute input (00–59). +// 3. AM/PM segmented toggle (no implicit default — opening an +// empty field seeds the toggle to null, and saving with a +// 1–12 typed hour but no toggle pick surfaces an "ambiguous" +// parse error to the parent rather than silently committing +// AM). +// +// Storage stays canonical "HH:mm" 24h; this component is purely a UI +// shell around `combineSplit` from `@/lib/time-12h`. // // Two consumers: // 1) The Manage-tab edit form: the picker is uncontrolled aside // from `value` / `onChange` (clean canonical values flow up on // blur and on toggle change; ambiguous drafts stay inside the -// picker until the user resolves them). +// picker until the user resolves them, and the dialog's Save +// button calls `commit()` to validate before persisting). // 2) The inline schedule time-cell editor (TimeRangeCell): the // parent reads the latest draft via the imperative `commit()` // handle when the user presses Enter or blurs the wrapper, so // a save that races React state batching still sees the right // value (matches the belt-and-braces pattern task #308 set up // against the prior text input). -// -// The AM/PM toggle has NO implicit default — opening the picker -// against an empty value seeds period=null, and saving with a 1–12 -// typed hour but no toggle pick surfaces an "ambiguous" parse error -// to the parent rather than silently committing AM. import { forwardRef, @@ -33,9 +37,9 @@ import { type KeyboardEvent as ReactKeyboardEvent, } from "react"; import { - combineTextWithPeriod, - formatCanonicalAs12h, + combineSplit, periodFromCanonical, + splitCanonicalForPicker, type CombineResult, type Period, } from "@/lib/time-12h"; @@ -43,15 +47,16 @@ import { export type TimePicker12hHandle = { /** * Compute the canonical "HH:mm" 24h string from the picker's current - * draft (typed text + toggle) WITHOUT waiting for an onChange - * round-trip. Used by the inline schedule editor's save path so a - * save triggered by Enter or wrapper-blur reads the live value the - * user sees in the input. + * draft (typed hour + minute + toggle) WITHOUT waiting for an + * onChange round-trip. Used by the inline schedule editor's save + * path so a save triggered by Enter or wrapper-blur reads the live + * value the user sees in the inputs, and by the Manage form's + * Save button to validate before persisting. */ commit(): CombineResult; - /** Move keyboard focus into the time text input. */ + /** Move keyboard focus into the hour input. */ focus(): void; - /** Select the existing text inside the time input. */ + /** Select the existing text inside the hour input. */ select(): void; }; @@ -61,33 +66,43 @@ type Props = { /** Canonical "HH:mm" 24h string, or "" for empty. */ value: string; /** - * Fires when the user lands on a clean canonical value: text input - * blurred with valid input, AM/PM toggled with valid input, or - * cleared. Ambiguous / invalid mid-edit drafts are NOT propagated - * — the parent learns about those via `commit()` from the imperative - * handle. + * Fires when the user lands on a clean canonical value: any of the + * sub-controls blurred with valid input, AM/PM toggled with valid + * input, or cleared. Ambiguous / invalid mid-edit drafts are NOT + * propagated — the parent learns about those via `commit()` from + * the imperative handle. */ onChange?: (canonical: string) => void; /** Enter pressed inside any sub-control. */ onCommit?: () => void; /** Escape pressed inside any sub-control. */ onCancel?: () => void; - /** Stable id for the text input — paired with a wrapping