Skip to content

fix: derive calendar days in the app timezone, not UTC - #32

Merged
nioasoft merged 1 commit into
mainfrom
fix/app-timezone-day-boundaries
Aug 2, 2026
Merged

fix: derive calendar days in the app timezone, not UTC#32
nioasoft merged 1 commit into
mainfrom
fix/app-timezone-day-boundaries

Conversation

@nioasoft

@nioasoft nioasoft commented Aug 2, 2026

Copy link
Copy Markdown
Owner

למה

הדשבורד מחשב גבולות today/week/month דרך appDateBoundaries() ב-Asia/Jerusalem, אבל כל נתיבי הכתיבה השתמשו ב-new Date().toISOString().split("T")[0] — היום ב-UTC. lib/dates.ts נכתב בדיוק בשביל זה ויובא ע"י קובץ אחד בלבד.

שני באגים, חומרה שונה לגמרי

1. דוחות — קרה 100% מהזמן ⚠️

new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split("T")[0]

בונה חצות מקומית של ה-1 בחודש ומסריאל כ-UTC. ממזרח לגריניץ' זה היום האחרון של החודש הקודם:

local 1st of Aug  : Sat Aug 01 2026 00:00:00 GMT+0300
what the code gets: 2026-07-31

כל דוח "החודש הזה" גרר בשקט יום מהחודש הקודם. 4 מופעים ב-AdHocReportTab.

2. תאריך רשומה/טיימר — חלון לילי

api/timer/start נרשם על היום ב-UTC, ו-api/timer/stop לא כותב date בכלל — טיימר שמתחיל בין 00:00 ל-03:00 שעון ישראל נשאר על אתמול לצמיתות. עדיין לא נדפק בפרוד (drift 193/203 באפס) כי העבודה היא ביום.

שינויים

קובץ מה
api/timer/start, api/tasks/[id]/move toISOString()appToday()
entries/page.tsx ברירת מחדל בטופס (4 מופעים)
AdHocReportTab.tsx טווח הדוח (8 מופעים)
ChargePaymentsPanel paid_at — כסף
entries/import תאריך בשורת הדוגמה ב-CSV
task-card השוואת "באיחור"
api/dashboard/stats גרף 12 חודשים: CURRENT_DATE (שעון ה-DB) → startOfMonthStr

lib/dates.ts הוא Intl + Date טהור, לכן נטען גם בקומפוננטות לקוח. הבחירה ב-Asia/Jerusalem קשיח (ולא ביום המקומי של הדפדפן) מיישרת את ברירות המחדל עם הגבולות שהשרת מחשב.

Test plan

  • npx tsc --noEmit → 0
  • npm run lint → נקי (gate של zero-warning)
  • npm test → 73 קבצים, 0 נכשלו
  • npm run build → עבר
  • בדיקה חדשה tests/unit/app-today.test.ts — 17 assertions: שעון קיץ/חורף, חצות מקומית, סוף פברואר, מעבר DST ב-25.10, ו-startOfMonth שחייב להיות ה-1
  • שינוי ה-SQL הורץ מול הפרוד: חלון זהה (2025-09-01), 203 שורות מול 203

ללא מיגרציה. ללא שינוי בשורות קיימות.

נדחה בכוונה

types.setTypeParser(1082, v => v) ב-lib/db.tslib/db.ts:18-20 דורס את הפרסר של timestamp (OID 1114) אבל לא של date (1082), ולכן עמודות date חוזרות כאובייקט Date למרות שהן מטופסות string. כבר גרם לקראש (ההערה ב-app/api/reports/route.ts:530-537) ול-workaround ב-app/[locale]/entries/page.tsx:605. תיקון של שתי שורות שמחסל את כל הקטגוריה, אבל blast radius של כל route שקורא date, והוא לא משפיע על משתמשים ישראלים (Vercel ב-UTC + דפדפן ב-UTC+3 מתקזזים). להרים כשיהיה משתמש ראשון ממערב ל-UTC.

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clock-bill Ready Ready Preview Aug 2, 2026 8:09am

Request Review

The dashboard computed today/week/month via appDateBoundaries()
(Asia/Jerusalem) while every write path used
`new Date().toISOString().split("T")[0]` — the UTC day. lib/dates.ts was
imported by exactly one file.

Two bugs, very different blast radius:

1. Ad-hoc reports: `new Date(y, m, 1).toISOString().split("T")[0]` builds
   LOCAL midnight of the 1st and serializes it as UTC, so east of
   Greenwich the "first day of this month" preset resolved to the
   PREVIOUS month's last day — 100% of the time, silently pulling an
   extra day into every default report.

2. Timer/entry dates: a 2-3h nightly window (00:00-03:00 local) where a
   new entry landed on yesterday. /api/timer/stop never rewrites `date`,
   so it stayed wrong permanently.

All call sites now use appToday() / appDateBoundaries().startOfMonth,
including paid_at, the CSV import sample row, and the task overdue check.
The 12-month chart window moves off CURRENT_DATE (DB session clock) onto
the same app-timezone month start as the rest of the handler — verified
against prod: identical window, identical row count.

No migration, no change to existing rows.
@nioasoft
nioasoft force-pushed the fix/app-timezone-day-boundaries branch from b165a58 to 390484f Compare August 2, 2026 08:08
@nioasoft
nioasoft merged commit 266b4d5 into main Aug 2, 2026
3 checks passed
@nioasoft
nioasoft deleted the fix/app-timezone-day-boundaries branch August 2, 2026 08:12
nioasoft added a commit that referenced this pull request Aug 2, 2026
…k the UI/UX audit (#35)

* chore: ignore local agent caches, security audits and logo artifacts

Three classes of local-only files were sitting untracked in the working
tree and would be swept in by any `git add .`:

- .mimocode/ — another agent tool's local cache (~56MB)
- docs/*security-audit*.html, docs/*security-review*.html — THIS REPO IS
  PUBLIC. The three reports enumerate findings with file:line evidence
  (SSRF surface, cron auth, upload handling, token storage) and embed
  local absolute paths. Committing them would publish a remediation
  roadmap to anyone. Keep local, share out-of-band.
- public/logos/ + scripts/generate_html.py — logo exploration from the
  rebrand. Referenced by no code, and anything under public/ is served
  publicly; logo_data.json alone is ~2MB of base64.

Nothing is deleted from disk — these stay locally, they just stop
following the repo around.

* docs: refresh CLAUDE.md and correct two wrong instructions in AGENTS.md

CLAUDE.md: condensed to the non-obvious. Drops guidance that had gone
stale — lib/db.ts initSchema() (removed from the codebase) and the
"Prod TODO: set Vercel DATABASE_URL=clockbill_app" (done long ago).

AGENTS.md was untracked and had drifted into actively harmful territory:

- It opened with `docker compose up -d  # PostgreSQL on :5432` and
  described a `clockbill-db` dev container. There is no local dev DB —
  .env.local points at the Neon dev branch. CI gets Postgres from GitHub
  Actions `services:`, not docker-compose.yml; nothing but AGENTS.md
  referenced that file.
- It listed `npm run db:migrate` as the way to apply migrations. That is
  the one command CLAUDE.md forbids (drizzle journal out of sync) and it
  has caused a production outage. Replaced with the db:apply flow.

Also documents the date/timezone traps found while fixing #32: derive
calendar days via lib/dates.ts, never toISOString(); and `date` columns
(OID 1082) deserialize as Date objects while typed `string`.

Notes that the CI audit gate runs first, so a failure there makes every
later step report `skipped` — which reads as "tests passed" but means
they never ran.

* docs: add the 2026-07-10 UI/UX audit

Matches the existing docs/ convention (docs/product-ux-refresh/,
docs/superpowers/plans/). The screenshots need -f because .gitignore
blanket-ignores *.png; without them index.html renders with broken
images. Verified the dashboard capture is an empty test account — no
client names or amounts.
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.

1 participant