chore: resolve issue #151 — fix hire-date off-by-one in local timezone - #152
Merged
Merged
Conversation
formatDate and calculateTenure parsed 'YYYY-MM-DD' via new Date(str), which is UTC midnight and renders as the previous day in negative-offset zones (hire_date 2023-05-15 showed 'May 14, 2023' in PDT). Add a shared parseLocalDate helper that builds date-only strings from parts (local midnight) while passing timestamp strings through unchanged; route both formatters through it. All date-only surfaces (hire date, DOB, termination, rating/survey/review dates) share this util, so they are fixed together. Verification: 76 frontend tests pass (8 new in utils.test.ts), tsc --noEmit clean. Resolves #151. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
6 tasks
There was a problem hiding this comment.
Pull request overview
Fixes the “date-only parsed as UTC” off-by-one bug (issue #151) by ensuring YYYY-MM-DD strings are interpreted at local midnight for display and tenure calculations.
Changes:
- Added
parseLocalDate(dateStr)to parse date-only strings as local dates while passing timestamp strings through to the native parser. - Updated
formatDateandcalculateTenureto useparseLocalDate. - Added Vitest coverage for the parsing/formatting behavior and tenure regression.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/components/ui/utils.ts | Introduces parseLocalDate and routes existing date formatting/tenure logic through it to prevent UTC-midnight shifts. |
| src/components/ui/utils.test.ts | Adds unit tests covering local parsing and stable formatting for date-only strings. |
Comments suppressed due to low confidence (1)
src/components/ui/utils.ts:75
calculateTenuredoesn’t handle invalid date strings:parseLocalDate/new Date(...)return an Invalid Date without throwing, so the math yieldsNaNand the function returns strings like"NaNm"instead of the em dash fallback.
try {
const hire = parseLocalDate(hireDate);
const now = new Date();
const years = Math.floor(
(now.getTime() - hire.getTime()) / (365.25 * 24 * 60 * 60 * 1000)
);
const months = Math.floor(
((now.getTime() - hire.getTime()) % (365.25 * 24 * 60 * 60 * 1000)) /
(30.44 * 24 * 60 * 60 * 1000)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+34
to
+41
| export function parseLocalDate(dateStr: string): Date { | ||
| const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr); | ||
| if (dateOnly) { | ||
| const [, year, month, day] = dateOnly; | ||
| return new Date(Number(year), Number(month) - 1, Number(day)); | ||
| } | ||
| return new Date(dateStr); | ||
| } |
Comment on lines
+41
to
+55
| describe('calculateTenure', () => { | ||
| it('returns an em dash for a missing hire date', () => { | ||
| expect(calculateTenure(undefined)).toBe('—'); | ||
| }); | ||
|
|
||
| it('reads a multi-year date-only hire date without drifting the year down a day', () => { | ||
| // A hire date ~3.5 years before "now" (built from local parts) should read 3y. | ||
| const now = new Date(); | ||
| const past = new Date(now.getFullYear() - 3, now.getMonth() - 6, now.getDate()); | ||
| const y = past.getFullYear(); | ||
| const m = String(past.getMonth() + 1).padStart(2, '0'); | ||
| const d = String(past.getDate()).padStart(2, '0'); | ||
| expect(calculateTenure(`${y}-${m}-${d}`).startsWith('3y')).toBe(true); | ||
| }); | ||
| }); |
matthewod11-stack
added a commit
that referenced
this pull request
Jul 15, 2026
The backend rejects the streaming invoke with ChatError::Cancelled in addition to emitting chat-stream-cancelled. The catch block routed that rejection through categorizeError -> setMessageError, so hitting Stop decorated the partial message with a generic error + retry chip. Add isCancelledError (pure, tested) and a quiet-finalize branch in the catch, idempotent with the cancelled-event handler regardless of arrival order. Also merges current main (People Map T9 + #152 + #153) into the branch; integrated suite: 89 FE tests, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
matthewod11-stack
added a commit
that referenced
this pull request
Jul 15, 2026
…149) * chore: wire up frontend Stop button for streaming chat (#147) Issue #25 shipped the full backend cancel apparatus (StreamRegistry, cancel_stream command, chat-stream-cancelled event) but the frontend never wired it: no Stop button, and sendChatMessageStreaming never passed a stream_id, so the UI could never learn the id needed to cancel. Users could not stop a streaming response and abandoned streams billed to completion. - tauri-commands: sendChatMessageStreaming now forwards a client-generated streamId; add cancelStream() wrapping the existing cancel_stream command. - ConversationContext: generate a per-send stream id, listen for chat-stream-cancelled to reset streaming UI (backend emits this, not `done`, on cancel), expose stopStreaming(), and cancel on conversation switch/unmount so abandoned streams stop billing. - ChatInput: render a Stop button while streaming (optional isStreaming/onStop props — backward compatible; RecruitingView consumer unchanged). - App: wire stopStreaming + isStreaming into the chat ChatInput. - Extract the send/stop decision into a pure resolveChatInputMode() with unit tests (vitest 56 -> 63). Frontend-only; src-tauri/ untouched (honored do-not-touch). Verified: tsc --noEmit clean, vitest 63/63 green. Note: scope extended by one file beyond the issue's declared scope (src/App.tsx) — the sole consumer that wires the Stop gesture to the context; not in do-not-touch. Flagged in the PR for reviewer awareness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: don't render Stop as a failure (review finding) The backend rejects the streaming invoke with ChatError::Cancelled in addition to emitting chat-stream-cancelled. The catch block routed that rejection through categorizeError -> setMessageError, so hitting Stop decorated the partial message with a generic error + retry chip. Add isCancelledError (pure, tested) and a quiet-finalize branch in the catch, idempotent with the cancelled-event handler regardless of arrival order. Also merges current main (People Map T9 + #152 + #153) into the branch; integrated suite: 89 FE tests, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Auto-generated by portfolio-orchestrator nightly run on 2026-07-15. Resolves #151.
What
formatDateandcalculateTenure(src/components/ui/utils.ts) parsed date-onlyYYYY-MM-DDstrings withnew Date(str), which is UTC midnight — rendering as the previous calendar day in any negative-offset timezone (e.g. hire date2023-05-15showed May 14, 2023 in PDT).Fix
Added a shared
parseLocalDate(dateStr)helper that constructs date-only strings from their parts (local midnight) and passes timestamp strings through to the native parser unchanged. Both formatters now route through it. Because every date-only surface (hire date, DOB, termination date, rating/survey/review dates) funnels through this util, they are all corrected in one place.Verification
npm test— 76 passing / 0 failing (8 new inutils.test.ts, incl. a TZ-independent regression lock)npx tsc --noEmit— cleanrisk: low · approach: extract-and-move · do-not-touch: src-tauri/ (honored — frontend-only)