Skip to content

chore: resolve issue #151 — fix hire-date off-by-one in local timezone - #152

Merged
matthewod11-stack merged 1 commit into
mainfrom
chore/orchestrator-issue-151-2026-07-15
Jul 15, 2026
Merged

chore: resolve issue #151 — fix hire-date off-by-one in local timezone#152
matthewod11-stack merged 1 commit into
mainfrom
chore/orchestrator-issue-151-2026-07-15

Conversation

@matthewod11-stack

Copy link
Copy Markdown
Owner

Auto-generated by portfolio-orchestrator nightly run on 2026-07-15. Resolves #151.

What

formatDate and calculateTenure (src/components/ui/utils.ts) parsed date-only YYYY-MM-DD strings with new Date(str), which is UTC midnight — rendering as the previous calendar day in any negative-offset timezone (e.g. hire date 2023-05-15 showed 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 test76 passing / 0 failing (8 new in utils.test.ts, incl. a TZ-independent regression lock)
  • npx tsc --noEmit — clean
  • 2 files changed (util + colocated test); within the issue's max-files-changed: 6

risk: low · approach: extract-and-move · do-not-touch: src-tauri/ (honored — frontend-only)

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>
Copilot AI review requested due to automatic review settings July 15, 2026 08:21
@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 formatDate and calculateTenure to use parseLocalDate.
  • 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

  • calculateTenure doesn’t handle invalid date strings: parseLocalDate/new Date(...) return an Invalid Date without throwing, so the math yields NaN and 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
matthewod11-stack merged commit ebc2cb2 into main Jul 15, 2026
8 checks passed
@matthewod11-stack
matthewod11-stack deleted the chore/orchestrator-issue-151-2026-07-15 branch July 15, 2026 15:26
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hire date displays one day early (UTC-midnight parse rendered in local time)

2 participants