fix: use native date input on contract signing page for mobile #12

Merged
forgejo_admin merged 2 commits from 11-fix-date-input-mobile into main 2026-03-24 20:10:21 +00:00

Summary

The date field on the contract signing page was <input type="text" readonly>, which does nothing when tapped on mobile. Users thought it was broken. Changed to a native type="date" input with disabled attribute so it renders correctly on all platforms.

Changes

  • src/routes/contract/[token]/+page.svelte: Changed date input from type="text" readonly to type="date" disabled, set dateStr to ISO YYYY-MM-DD format in onMount, added displayDate derived value for human-readable date in the success overlay

Test Plan

  • Tests pass locally (12/12)
  • Build passes (npm run build)
  • Manual verification: date field renders as native disabled date picker on mobile, not a blank text box
  • Success overlay still shows human-readable date (e.g. "March 24, 2026")
  • "Already signed" view unchanged (uses player.contract_signed_at from server)

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
## Summary The date field on the contract signing page was `<input type="text" readonly>`, which does nothing when tapped on mobile. Users thought it was broken. Changed to a native `type="date"` input with `disabled` attribute so it renders correctly on all platforms. ## Changes - `src/routes/contract/[token]/+page.svelte`: Changed date input from `type="text" readonly` to `type="date" disabled`, set `dateStr` to ISO YYYY-MM-DD format in `onMount`, added `displayDate` derived value for human-readable date in the success overlay ## Test Plan - [x] Tests pass locally (12/12) - [x] Build passes (`npm run build`) - [ ] Manual verification: date field renders as native disabled date picker on mobile, not a blank text box - [ ] Success overlay still shows human-readable date (e.g. "March 24, 2026") - [ ] "Already signed" view unchanged (uses `player.contract_signed_at` from server) ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related - Closes #11
The date field was type="text" readonly, which does nothing on mobile
tap. Changed to type="date" disabled with ISO format dateStr, and added
a separate displayDate derived value for the human-readable success
overlay.

Closes #11

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Review: LGTM

Diff is minimal and scoped exactly to the issue. Verified:

  • dateStr now ISO YYYY-MM-DD format (required by input[type=date])
  • displayDate derived value handles timezone-safe parsing with T00:00:00 suffix
  • Input changed from type="text" readonly to type="date" disabled
  • Success overlay uses displayDate for human-readable format
  • "Already signed" view (line 156, player.contract_signed_at) is untouched
  • Build passes, 12/12 tests pass
  • No secrets, no unrelated changes
**Review: LGTM** Diff is minimal and scoped exactly to the issue. Verified: - `dateStr` now ISO `YYYY-MM-DD` format (required by `input[type=date]`) - `displayDate` derived value handles timezone-safe parsing with `T00:00:00` suffix - Input changed from `type="text" readonly` to `type="date" disabled` - Success overlay uses `displayDate` for human-readable format - "Already signed" view (line 156, `player.contract_signed_at`) is untouched - Build passes, 12/12 tests pass - No secrets, no unrelated changes
Author
Owner

PR #12 Review

DOMAIN REVIEW

Stack: SvelteKit, TypeScript, Svelte 5 runes ($state, $derived, $props).

This is a UX bug fix: the contract signing page date field was <input type="text" readonly>, which renders as a dead/blank field on mobile. The fix changes it to <input type="date" disabled> so mobile browsers show a native (greyed-out) date picker, and adds a displayDate derived value so the success overlay continues to show a human-readable date.

Timezone regression on line 28: dateStr = now.toISOString().split('T')[0] converts to UTC before extracting the date string. For a user in UTC-7 (e.g., Utah) at 11:30 PM local time, toISOString() returns the next day's date. This is the same category of bug that issue #9 / PR #10 fixed (SSR/timezone mismatch). The old code used toLocaleDateString() which respected the browser's local timezone. The new code introduces a UTC-dependent date that will be wrong near midnight for all negative-UTC-offset users.

The fix is straightforward -- construct the ISO date string from local date components:

const y = now.getFullYear();
const m = String(now.getMonth() + 1).padStart(2, '0');
const d = String(now.getDate()).padStart(2, '0');
dateStr = `${y}-${m}-${d}`;

displayDate reconstruction (line 31): new Date(dateStr + 'T00:00:00') parses as local time (no Z suffix), so the display conversion back to human-readable is correct. No issue here once dateStr itself is correct.

Accessibility: disabled attribute on a display-only date field is appropriate. Screen readers will announce it as disabled. No ARIA gaps.

Component architecture: The displayDate derived value is clean Svelte 5 pattern. No issues.

BLOCKERS

  1. Timezone regression (line 28): toISOString().split('T')[0] uses UTC, not local time. This will show the wrong date for Utah users (UTC-7) between 5:00 PM and midnight local time. This directly contradicts the fix delivered in PR #10 for issue #9. Must use local date components instead.

NITS

  1. Line 31 is long (153 chars). Consider breaking the $derived expression across multiple lines for readability. Non-blocking.

SOP COMPLIANCE

  • Branch named after issue (11-fix-date-input-mobile references #11)
  • PR body follows template (Summary, Changes, Test Plan, Related)
  • Related references parent issue (Closes #11)
  • No secrets committed
  • No unnecessary file changes (single file, 6 additions / 7 deletions)
  • Commit messages are descriptive
  • N/A -- no plan slug required (standalone bug fix, not plan work)

PROCESS OBSERVATIONS

  • This is a fast, focused bug fix. One file changed, directly scoped to the issue. Good change failure risk profile once the timezone regression is addressed.
  • The date field is not sent in the POST body (only signature_name, accepted, under_18, signature_data are submitted), so this is purely a display concern. No server-side impact.
  • Test coverage: the existing 12 tests cover server-side validation logic. No component-level tests exist in this repo. Since the date is display-only (not submitted), and the change is a one-line input type swap plus a derived value, the lack of a new test is acceptable here. Not a blocker.
  • Note: issue #9 (SSR/timezone mismatch) was a prior fix. This PR inadvertently re-introduces a related timezone issue. The pattern to watch for in this codebase is any use of toISOString() for display dates -- it should always be flagged.

VERDICT: NOT APPROVED

One blocker: the toISOString() call on line 28 uses UTC instead of local time, reintroducing a timezone-dependent date display bug for Utah users. Fix that and this is ready to merge.

## PR #12 Review ### DOMAIN REVIEW **Stack**: SvelteKit, TypeScript, Svelte 5 runes (`$state`, `$derived`, `$props`). This is a UX bug fix: the contract signing page date field was `<input type="text" readonly>`, which renders as a dead/blank field on mobile. The fix changes it to `<input type="date" disabled>` so mobile browsers show a native (greyed-out) date picker, and adds a `displayDate` derived value so the success overlay continues to show a human-readable date. **Timezone regression on line 28**: `dateStr = now.toISOString().split('T')[0]` converts to UTC before extracting the date string. For a user in UTC-7 (e.g., Utah) at 11:30 PM local time, `toISOString()` returns the next day's date. This is the same category of bug that issue #9 / PR #10 fixed (SSR/timezone mismatch). The old code used `toLocaleDateString()` which respected the browser's local timezone. The new code introduces a UTC-dependent date that will be wrong near midnight for all negative-UTC-offset users. The fix is straightforward -- construct the ISO date string from local date components: ``` const y = now.getFullYear(); const m = String(now.getMonth() + 1).padStart(2, '0'); const d = String(now.getDate()).padStart(2, '0'); dateStr = `${y}-${m}-${d}`; ``` **displayDate reconstruction (line 31)**: `new Date(dateStr + 'T00:00:00')` parses as local time (no Z suffix), so the display conversion back to human-readable is correct. No issue here once `dateStr` itself is correct. **Accessibility**: `disabled` attribute on a display-only date field is appropriate. Screen readers will announce it as disabled. No ARIA gaps. **Component architecture**: The `displayDate` derived value is clean Svelte 5 pattern. No issues. ### BLOCKERS 1. **Timezone regression (line 28)**: `toISOString().split('T')[0]` uses UTC, not local time. This will show the wrong date for Utah users (UTC-7) between 5:00 PM and midnight local time. This directly contradicts the fix delivered in PR #10 for issue #9. Must use local date components instead. ### NITS 1. **Line 31 is long** (153 chars). Consider breaking the `$derived` expression across multiple lines for readability. Non-blocking. ### SOP COMPLIANCE - [x] Branch named after issue (`11-fix-date-input-mobile` references #11) - [x] PR body follows template (Summary, Changes, Test Plan, Related) - [x] Related references parent issue (`Closes #11`) - [x] No secrets committed - [x] No unnecessary file changes (single file, 6 additions / 7 deletions) - [x] Commit messages are descriptive - [ ] N/A -- no plan slug required (standalone bug fix, not plan work) ### PROCESS OBSERVATIONS - This is a fast, focused bug fix. One file changed, directly scoped to the issue. Good change failure risk profile once the timezone regression is addressed. - The date field is not sent in the POST body (only `signature_name`, `accepted`, `under_18`, `signature_data` are submitted), so this is purely a display concern. No server-side impact. - Test coverage: the existing 12 tests cover server-side validation logic. No component-level tests exist in this repo. Since the date is display-only (not submitted), and the change is a one-line input type swap plus a derived value, the lack of a new test is acceptable here. Not a blocker. - Note: issue #9 (SSR/timezone mismatch) was a prior fix. This PR inadvertently re-introduces a related timezone issue. The pattern to watch for in this codebase is any use of `toISOString()` for display dates -- it should always be flagged. ### VERDICT: NOT APPROVED One blocker: the `toISOString()` call on line 28 uses UTC instead of local time, reintroducing a timezone-dependent date display bug for Utah users. Fix that and this is ready to merge.
toISOString() converts to UTC before extracting the date, causing
Utah users (UTC-7) to see tomorrow's date between 5-midnight local.
Use getFullYear/getMonth/getDate for correct local date.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

PR #12 Review (Re-review)

DOMAIN REVIEW

Stack: SvelteKit (Svelte 5 runes), TypeScript, client-side date handling.

Timezone fix -- VERIFIED RESOLVED. The previous review flagged toISOString() converting to UTC, which could shift the displayed date by -1 day for users west of UTC. The fix correctly uses local date components:

const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
dateStr = `${yyyy}-${mm}-${dd}`;

getFullYear(), getMonth(), and getDate() all return values in the user's local timezone. No UTC conversion occurs. This is the correct approach.

displayDate derived value -- correct. new Date(dateStr + 'T00:00:00') constructs the Date using local time (no Z suffix), so the round-trip from YYYY-MM-DD back to human-readable ("March 24, 2026") will not shift dates. This is well-handled.

type="date" with disabled -- correct. The disabled attribute prevents user interaction while rendering as a native date picker on mobile (the original bug). Since submitSignature() reads from component state (not form fields) and does not send the date to the server at all (the server uses NOW() for contract_signed_at), disabled vs readonly has no functional impact on data submission.

Accessibility: The <label for="signDate"> correctly associates with id="signDate" on the input. The disabled attribute is semantically appropriate for a field the user should not modify. No accessibility regressions.

BLOCKERS

None.

NITS

  1. Line 34 -- long line. The displayDate derived value is a single 140+ character line. Consider breaking it across multiple lines for readability. Non-blocking.

  2. Line 158 -- pre-existing timezone nuance (not in scope). The "already signed" view does new Date(player.contract_signed_at).toLocaleDateString(...) where contract_signed_at is a PostgreSQL NOW() timestamp. If the DB timezone differs from the user's timezone, this could theoretically show a shifted date. This is pre-existing and outside this PR's scope, but worth noting for a future ticket.

SOP COMPLIANCE

  • Branch named after issue (11-fix-date-input-mobile references issue #11)
  • PR body has Summary, Changes, Test Plan, Related sections
  • Related references parent issue (Closes #11)
  • Related does not reference a plan slug (acceptable -- this is a standalone bugfix, not plan work)
  • No secrets committed
  • No unnecessary file changes (1 file changed, tightly scoped)
  • Commit messages are descriptive

PROCESS OBSERVATIONS

Clean, minimal bugfix. Single file changed with +9/-7 lines. The fix addresses exactly the reported issue (date field unresponsive on mobile) plus the QA-flagged timezone blocker from the previous review. No scope creep. The test plan includes manual verification steps for mobile behavior, which is appropriate for a UI input type change that cannot be unit-tested without a browser environment.

VERDICT: APPROVED

## PR #12 Review (Re-review) ### DOMAIN REVIEW **Stack:** SvelteKit (Svelte 5 runes), TypeScript, client-side date handling. **Timezone fix -- VERIFIED RESOLVED.** The previous review flagged `toISOString()` converting to UTC, which could shift the displayed date by -1 day for users west of UTC. The fix correctly uses local date components: ```js const yyyy = now.getFullYear(); const mm = String(now.getMonth() + 1).padStart(2, '0'); const dd = String(now.getDate()).padStart(2, '0'); dateStr = `${yyyy}-${mm}-${dd}`; ``` `getFullYear()`, `getMonth()`, and `getDate()` all return values in the user's local timezone. No UTC conversion occurs. This is the correct approach. **`displayDate` derived value -- correct.** `new Date(dateStr + 'T00:00:00')` constructs the Date using local time (no `Z` suffix), so the round-trip from YYYY-MM-DD back to human-readable ("March 24, 2026") will not shift dates. This is well-handled. **`type="date"` with `disabled` -- correct.** The `disabled` attribute prevents user interaction while rendering as a native date picker on mobile (the original bug). Since `submitSignature()` reads from component state (not form fields) and does not send the date to the server at all (the server uses `NOW()` for `contract_signed_at`), `disabled` vs `readonly` has no functional impact on data submission. **Accessibility:** The `<label for="signDate">` correctly associates with `id="signDate"` on the input. The `disabled` attribute is semantically appropriate for a field the user should not modify. No accessibility regressions. ### BLOCKERS None. ### NITS 1. **Line 34 -- long line.** The `displayDate` derived value is a single 140+ character line. Consider breaking it across multiple lines for readability. Non-blocking. 2. **Line 158 -- pre-existing timezone nuance (not in scope).** The "already signed" view does `new Date(player.contract_signed_at).toLocaleDateString(...)` where `contract_signed_at` is a PostgreSQL `NOW()` timestamp. If the DB timezone differs from the user's timezone, this could theoretically show a shifted date. This is pre-existing and outside this PR's scope, but worth noting for a future ticket. ### SOP COMPLIANCE - [x] Branch named after issue (`11-fix-date-input-mobile` references issue #11) - [x] PR body has Summary, Changes, Test Plan, Related sections - [x] Related references parent issue (`Closes #11`) - [ ] Related does not reference a plan slug (acceptable -- this is a standalone bugfix, not plan work) - [x] No secrets committed - [x] No unnecessary file changes (1 file changed, tightly scoped) - [x] Commit messages are descriptive ### PROCESS OBSERVATIONS Clean, minimal bugfix. Single file changed with +9/-7 lines. The fix addresses exactly the reported issue (date field unresponsive on mobile) plus the QA-flagged timezone blocker from the previous review. No scope creep. The test plan includes manual verification steps for mobile behavior, which is appropriate for a UI input type change that cannot be unit-tested without a browser environment. ### VERDICT: APPROVED
forgejo_admin deleted branch 11-fix-date-input-mobile 2026-03-24 20:10:21 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/westside-contracts!12
No description provided.