Skip to content

feat(companion): user date/time format preferences via shared @shelf/datetime - #2798

Merged
DonKoko merged 8 commits into
mainfrom
feat/companion-datetime
Aug 4, 2026
Merged

feat(companion): user date/time format preferences via shared @shelf/datetime#2798
DonKoko merged 8 commits into
mainfrom
feat/companion-datetime

Conversation

@DonKoko

@DonKoko DonKoko commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What & why

The companion (Expo/React Native) app formatted every date/time device-local,
ignoring the user's Shelf format preferences that the webapp respects (shipped in
#2654 / #2791). This makes the companion honor those same preferences — date
format, time format (12/24h), and timezone — by sharing one formatter with the
webapp so the two can never drift.

Rather than re-porting the formatter to native, the webapp's pure formatter is
extracted into a new workspace package @shelf/datetime that both apps consume.
It's pure JS (uses only Intl, which Hermes ships), so the companion side ships via
EAS OTA — no App Store submission.

The three commits

  1. refactor(datetime) — new @shelf/datetime package (the webapp's formatter
    core moved verbatim; the two webapp-coupled type imports replaced by local
    string-union enums mirroring the Prisma enums, and a dependency-free FormatHints
    in place of ClientHint). apps/webapp/app/utils/date-format.ts becomes a thin
    export * re-export, so all ~54 webapp import sites are unchanged. Zero behavior
    change
    — the webapp's 57 formatter tests pass against the re-exported package.
    Metro transpiles the TS source directly (verified via an export build); webapp
    consumes it through ssr.noExternal, same as @shelf/database.

  2. feat(mobile-api) — add the four preference columns (dateFormat,
    timeFormat, weekStart, timeZone) to the requireMobileAuth select so
    /api/mobile/me returns them. Additive and backward-compatible; a new test
    asserts the columns are both selected and returned (internal-only fields stay
    stripped).

  3. feat(companion) — resolve the raw prefs once in OrgContext with a
    device-hint fallback (Intl.DateTimeFormat().resolvedOptions()), expose
    useDateFormatter() / useFormatPrefs() hooks, and swap 12 display surfaces
    (bookings, audits, assets, kits, notes, custom fields) onto the bound formatter.

Intended behavior changes

  • Companion dates/times now render in the user's chosen format + timezone, not
    the device's. A user set to YYYY_MM_DD / H24 / Asia/Tokyo sees exactly that
    everywhere.
  • Absolute calendar dates (custom-field DATE) render with no timezone shift
    (the day is preserved).
  • Until /api/mobile/me loads (or against a pre-prefs server), formatting falls back
    to device-local rather than an arbitrary US/UTC default.

Deliberately untouched: device-timezone capture for API submission
(getTimeZone / toLocalWire / timeZone payloads), the relative booking
countdown, and the native date-picker widget's internal value. Only visible display
strings change.

Verification

  • Webapp: tsc clean · 57 formatter tests green · 10 mobile-auth tests green · lint clean
  • Companion: tsc clean · ESLint 0 new issues · react-doctor 0 errors
  • Zero residual toLocale* / device-local formatters in companion display paths

Rollout

  • Webapp (package + re-export + /me) ships with a normal deploy.
  • Companion (display swap) ships via EAS OTA after merge — pure JS, no native
    changes, no store submission.

Testing notes

On-device iOS smoke recommended before/after merge: with a user set to
YYYY_MM_DD / H24 / Asia/Tokyo, eyeball a booking's from/to, an audit due date,
an asset created/updated date, a custom-field DATE, and a picker label — plus a
booking near a UTC-midnight boundary to confirm the timezone flip.

Summary by CodeRabbit

  • New Features

    • Dates and times now respect preferred date format, time format, week start, locale, and timezone across companion and web experiences.
    • Bookings, audits, notes, assets, custom fields, and dashboard displays now use consistent localized formatting.
    • Date-picker labels preserve local times while applying preferred display formats.
  • Bug Fixes

    • Invalid stored dates display safely without overflowed or incorrect values.
    • Formatting falls back reliably when preferences or timezones are unavailable.

DonKoko added 3 commits August 4, 2026 11:38
Move the webapp's pure, locale-leak-free date/time formatter into a new
`@shelf/datetime` workspace package so the webapp and the companion (Expo/RN)
app share ONE implementation and their date rendering never drifts.

- New `packages/datetime` (TS source, consumed like `@shelf/database`): the
  formatter core verbatim, with the two webapp-coupled type imports replaced by
  local string-union enums (mirroring the Prisma enums member-for-member) and a
  dependency-free `FormatHints` type in place of `ClientHint`.
- `apps/webapp/app/utils/date-format.ts` becomes a thin `export *` re-export, so
  all ~54 webapp import sites are unchanged. Zero behavior change — the webapp's
  57 formatter tests pass against the re-exported package.
- Wire the package into both bundlers (webapp `ssr.noExternal`; Metro transpiles
  the TS source directly, verified via an export build).
Add the four format-preference columns (dateFormat, timeFormat, weekStart,
timeZone) to the `requireMobileAuth` user select so `/api/mobile/me` returns
them. They flow through `safeUser` unchanged; internal-only fields stay stripped.

Additive and backward-compatible — older companion builds ignore the new keys.
Add a `requireMobileAuth` test asserting the columns are selected AND returned
(the only automated guard against a future refactor dropping them).
Render every date/time in the companion app in the acting user's Shelf format
preferences (date format, time format, timezone) instead of device-local, using
the shared `@shelf/datetime` formatter.

- Resolve the raw prefs from `/api/mobile/me` once in `OrgContext`, with a
  device-hint fallback (`Intl.DateTimeFormat().resolvedOptions()`) for any unset
  field and for pre-format-prefs servers, so unknown-user states stay
  device-local rather than an arbitrary US/UTC default.
- New `useDateFormatter()` / `useFormatPrefs()` hooks bind the resolved prefs.
- Swap 12 display surfaces (bookings, audits, assets, kits, notes, custom
  fields) onto the bound formatter. Module-scope helpers take `prefs`; absolute
  calendar dates (custom-field DATE) render without a timezone shift; the shared
  `formatDue` gains a `prefs` arg. Device-tz capture for submission and the
  relative booking countdown are untouched.
- Remove the device-local `formatDate`/`formatDateTime` from `lib/constants`.

Ships via EAS OTA (pure JS, no native changes).
Copilot AI lite review requested due to automatic review settings August 4, 2026 08:57
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🩺 React Doctor — webapp

✅ No new findings on the files changed by this PR.

Run locally with pnpm webapp:doctor for a full scan, or cd apps/webapp && pnpm exec react-doctor . --diff for the same diff-only view.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

🩺 React Doctor — companion

Findings on the files changed by this PR:

  • 0 errors
  • 40 warnings — advisory
⚠️ 40 warnings (click to expand)
  • react-doctor/rn-no-legacy-expo-packages (11)
    • apps/companion/app/(tabs)/audits/index.tsx:14
    • apps/companion/app/(tabs)/bookings/index.tsx:19
    • apps/companion/app/(tabs)/audits/[id].tsx:17
    • apps/companion/app/(tabs)/assets/[id].tsx:17
    • apps/companion/app/(tabs)/bookings/edit.tsx:30
    • apps/companion/components/asset-detail/notes-section.tsx:9
    • apps/companion/app/(tabs)/bookings/[id].tsx:24
    • apps/companion/app/(tabs)/assets/kits/[id].tsx:27
    • apps/companion/app/(tabs)/bookings/new.tsx:34
    • apps/companion/app/(tabs)/home.tsx:14
    • apps/companion/components/asset-edit/custom-field-input.tsx:37
  • react-doctor/prefer-useReducer (9)
    • apps/companion/app/(tabs)/audits/index.tsx:45
    • apps/companion/app/(tabs)/bookings/index.tsx:91
    • apps/companion/app/(tabs)/audits/[id].tsx:95
    • apps/companion/app/(tabs)/assets/[id].tsx:65
    • apps/companion/app/(tabs)/bookings/edit.tsx:59
    • apps/companion/app/(tabs)/bookings/[id].tsx:102
    • apps/companion/app/(tabs)/assets/kits/[id].tsx:70
    • apps/companion/lib/org-context.tsx:61
    • apps/companion/app/(tabs)/bookings/new.tsx:65
  • react-doctor/no-giant-component (9)
    • apps/companion/app/(tabs)/audits/index.tsx:45
    • apps/companion/app/(tabs)/bookings/index.tsx:91
    • apps/companion/app/(tabs)/audits/[id].tsx:95
    • apps/companion/app/(tabs)/assets/[id].tsx:65
    • apps/companion/app/(tabs)/bookings/edit.tsx:59
    • apps/companion/app/(tabs)/bookings/[id].tsx:102
    • apps/companion/app/(tabs)/assets/kits/[id].tsx:70
    • apps/companion/app/(tabs)/bookings/new.tsx:65
    • apps/companion/app/(tabs)/home.tsx:48
  • react-doctor/rn-no-dimensions-get (4)
    • apps/companion/app/(tabs)/assets/[id].tsx:1061
    • apps/companion/app/(tabs)/assets/[id].tsx:1062
    • apps/companion/app/(tabs)/assets/kits/[id].tsx:605
    • apps/companion/app/(tabs)/assets/kits/[id].tsx:606
  • react-doctor/rn-prefer-reanimated (3)
    • apps/companion/app/(tabs)/audits/index.tsx:9
    • apps/companion/app/(tabs)/bookings/index.tsx:11
    • apps/companion/app/(tabs)/audits/[id].tsx:11
  • react-doctor/rerender-functional-setstate (1)
    • apps/companion/app/(tabs)/bookings/index.tsx:266
  • react-doctor/no-cascading-set-state (1)
    • apps/companion/app/(tabs)/bookings/edit.tsx:107
  • jsx-a11y/no-autofocus (1)
    • apps/companion/app/(tabs)/bookings/new.tsx:261
  • react-doctor/no-array-index-as-key (1)
    • apps/companion/components/asset-edit/custom-field-input.tsx:285

Run locally with pnpm companion:doctor for a full scan, or cd apps/companion && pnpm exec react-doctor . --diff for the same diff-only view.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbbfea38c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/companion/lib/audit-format.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

The companion Home booking card date range display changes in a way that may contradict the PR’s stated “format-only” intent (compact label → full default), and should be clarified or adjusted.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR aligns the companion app’s visible date/time rendering with the webapp by extracting the pure preference-aware formatter into a new shared workspace package (@shelf/datetime), exposing user date/time preferences via the mobile /me endpoint, and updating companion screens to format using the resolved user prefs (with device-hint fallback).

Changes:

  • Introduce @shelf/datetime as a shared, dependency-free formatter package and re-export it from the webapp’s existing ~/utils/date-format entrypoint.
  • Extend requireMobileAuth’s user select/response to include raw date/time preference columns for /api/mobile/me, with a regression test.
  • Add companion-side format-pref resolution in OrgContext and replace device-local formatting across multiple companion UI surfaces via useDateFormatter() / useFormatPrefs().
File summaries
File Description
pnpm-lock.yaml Adds workspace links for the new @shelf/datetime package in relevant importers.
packages/datetime/tsconfig.json TypeScript config for the new shared datetime package.
packages/datetime/src/index.ts New shared formatter/detector/resolver implementation (moved from webapp, Prisma-decoupled).
packages/datetime/package.json Defines the @shelf/datetime workspace package and its TS-source exports.
apps/webapp/vite.config.ts Ensures Vite SSR bundles @shelf/datetime via ssr.noExternal.
apps/webapp/package.json Adds @shelf/datetime as a webapp dependency.
apps/webapp/app/utils/date-format.ts Replaces formatter implementation with a thin export * re-export of @shelf/datetime.
apps/webapp/app/modules/api/mobile-auth.server.ts Adds date/time preference columns to the mobile-auth user select so /api/mobile/me can return them.
apps/webapp/app/modules/api/mobile-auth.server.test.ts Adds a unit test to guard selection/return of the four preference fields and stripping internal fields.
apps/companion/package.json Adds @shelf/datetime as a companion dependency.
apps/companion/lib/use-date-formatter.ts Introduces hooks to expose resolved prefs and bound formatter helpers to companion components.
apps/companion/lib/org-context.tsx Resolves raw prefs from /api/mobile/me + device hints into concrete formatPrefs stored in context.
apps/companion/lib/constants.ts Removes legacy device-local toLocale* date formatting helpers.
apps/companion/lib/audit-format.ts Updates audit due-date formatting to use the shared formatter with resolved prefs.
apps/companion/lib/api/types.ts Extends the /me response typing to include optional raw date/time preference fields.
apps/companion/components/asset-edit/custom-field-input.tsx Renders DATE custom field display labels using the bound formatter (preserving date-only semantics).
apps/companion/components/asset-detail/notes-section.tsx Formats note timestamps and markdoc {% date %} tokens using user prefs/timezone.
apps/companion/components/asset-detail/custom-fields-section.tsx Formats DATE custom fields using the bound formatter.
apps/companion/app/(tabs)/home.tsx Updates booking/audit card date displays to use resolved prefs-based formatting.
apps/companion/app/(tabs)/bookings/new.tsx Uses the bound formatter for booking start/end picker display labels.
apps/companion/app/(tabs)/bookings/index.tsx Uses the bound formatter for booking list date/time rendering.
apps/companion/app/(tabs)/bookings/edit.tsx Uses the bound formatter for booking edit picker display labels.
apps/companion/app/(tabs)/bookings/[id].tsx Uses the bound formatter for booking detail date/time rendering.
apps/companion/app/(tabs)/audits/index.tsx Uses the shared formatDue with resolved prefs for audit list due labels.
apps/companion/app/(tabs)/audits/[id].tsx Uses the bound formatter for audit detail date/time rendering.
apps/companion/app/(tabs)/assets/kits/[id].tsx Uses the bound formatter for kit detail date rendering.
apps/companion/app/(tabs)/assets/[id].tsx Uses the bound formatter for asset detail date rendering.
Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file
  • Files reviewed: 26/27 changed files
  • Comments generated: 1
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread apps/companion/app/(tabs)/home.tsx
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 376dbf5d-8e81-439f-9b43-b03fc153a281

📥 Commits

Reviewing files that changed from the base of the PR and between b93d85f and 315797c.

📒 Files selected for processing (1)
  • apps/companion/lib/org-context.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/companion/lib/org-context.tsx

Walkthrough

The PR adds the shared @shelf/datetime package, propagates user date and time preferences through mobile authentication, resolves them in companion context, and updates webapp and companion date displays to use preference-aware formatting.

Changes

Datetime preference flow

Layer / File(s) Summary
Shared datetime formatter
packages/datetime/*, apps/webapp/app/utils/date-format.test.ts
Adds preference detection, resolution, timezone handling, formatter caching, date formatting, and regression tests.
Preference API and context wiring
apps/webapp/app/modules/api/mobile-auth.server.ts, apps/webapp/app/modules/api/mobile-auth.server.test.ts, apps/webapp/app/utils/date-format.ts, apps/webapp/vite.config.ts, apps/companion/lib/api/types.ts, apps/companion/lib/org-context.tsx
Returns raw preferences, tests the response shape, shares the formatter, and resolves preferences in companion context.
Companion formatter bindings
apps/companion/lib/use-date-formatter.ts, apps/companion/lib/audit-format.ts, apps/companion/components/asset-detail/*, apps/companion/components/asset-edit/*
Adds preference-bound hooks and updates audit, note, custom-field, and date-input formatting.
Companion screen migrations
apps/companion/app/(tabs)/assets/*, apps/companion/app/(tabs)/audits/*, apps/companion/app/(tabs)/bookings/*, apps/companion/app/(tabs)/home.tsx
Replaces static or local date formatting with hook-based formatting and updates callback dependencies and audit due-date calls.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MobileAuth
  participant OrgContext
  participant CompanionScreen
  participant Datetime
  MobileAuth->>OrgContext: return raw formatting preferences
  OrgContext->>Datetime: resolve preferences with device hints
  CompanionScreen->>OrgContext: read resolved formatPrefs
  CompanionScreen->>Datetime: format displayed date or time
  Datetime-->>CompanionScreen: return formatted value
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding companion date/time preferences through the shared @shelf/datetime package.
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/companion-datetime

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Nitpick comments (2)
packages/datetime/src/index.ts (1)

599-682: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider unit tests for this package.

formatDate is now the single formatter for two apps and carries substantial branching: date-only strings, localeOnly, name-month preferences, additive includeWeekday, preset folding, and both clocks. No test file accompanies packages/datetime. A small table-driven suite over the five DateFormatPreference values crossed with the common option shapes would lock the behavior the PR promises to preserve.

Do you want me to generate that test suite?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/datetime/src/index.ts` around lines 599 - 682, Add a focused,
table-driven unit-test suite for formatDate covering all five
DateFormatPreference values and common option shapes, including date-only
strings, localeOnly, name-month preferences, additive includeWeekday, preset
folding, and both 12-hour and 24-hour clocks. Use stable timezone/locale inputs
and assert the promised formatted output without changing production behavior.
apps/webapp/app/modules/api/mobile-auth.server.test.ts (1)

288-301: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a factory for the user row.

dbRow hardcodes every field inline. The coding guidelines ask for factories with field overrides instead of hardcoded test data. If a user-row factory already exists in the webapp test helpers, reuse it and override only the four preference fields.

As per coding guidelines: "Use factories for consistent, realistic test data, allow field overrides, and avoid hardcoded test data where practical."

#!/bin/bash
# Description: Look for existing user/test factories in the webapp test helpers.
fd -H -t f -i 'factor' apps/webapp -E node_modules
rg -nP -i 'export (const|function) (create|make|build)\w*User' apps/webapp -g '*.ts' -g '*.tsx'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/webapp/app/modules/api/mobile-auth.server.test.ts` around lines 288 -
301, Replace the hardcoded dbRow object definition with a call to an existing
user-row factory from the webapp test helpers, passing field overrides for only
the four preference fields (dateFormat, timeFormat, weekStart, timeZone) while
relying on the factory to provide the base user data with id, email, firstName,
lastName, profilePicture, onboarded, deletedAt, and lastMobileActiveAt. Preserve
the test's behavior by ensuring the factory returns the same data shape when
called with these overrides.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/companion/app/`(tabs)/bookings/edit.tsx:
- Around line 65-66: Update the editable booking flow around useDateFormatter,
DateTimePicker state, and toLocalWire submission so display, picker values, and
submitted times all use the same timezone. Prefer device-local formatting for
the picker button labels, or explicitly convert values from the preference
timezone before passing them to the picker and submission, preserving consistent
from/to values throughout editing.

In `@apps/companion/components/asset-edit/custom-field-input.tsx`:
- Around line 376-384: Update the display-value logic near useDateFormatter and
the displayValue assignment to validate the raw value before calling formatDate.
Reuse the component’s existing parsed/validated date result when available, and
fall back to an empty or safe display value for invalid dates such as 2026-02-30
so formatting cannot normalize them into another calendar day.

In `@apps/companion/lib/audit-format.ts`:
- Line 68: Update formatDue to perform all calendar-day comparisons and
neutral-date cutoff calculations in prefs.timeZone, reusing the timezone-aware
boundary logic from formatDate rather than device-local date math. Preserve the
existing today, tomorrow, overdue, and neutral labels while making them
consistent across UTC and non-UTC environments, and add boundary coverage for
both timezone cases.

In `@apps/webapp/app/modules/api/mobile-auth.server.test.ts`:
- Around line 302-312: Reset the module-scoped database spies before each test
by adding a beforeEach setup and clearing the relevant mocks. In the test
asserting requireMobileAuth, update the findUnique call inspection to use the
most recent recorded call rather than mock.calls[0], while preserving the
existing select regression checks.

In `@packages/datetime/package.json`:
- Around line 13-16: Update the TypeScript devDependency in apps/companion from
~5.9.3 to ^6.0.2, matching the workspace packages’ version used by
packages/datetime and the other referenced packages.

In `@packages/datetime/src/index.ts`:
- Around line 303-322: Update resolveFormatPrefs in
packages/datetime/src/index.ts to narrow dateFormat, timeFormat, and
weekStartsOn against their known member sets before returning them, and select
the first valid timezone candidate from userPrefs, detected, or the hardcoded
default before applying the fallback. In apps/companion/lib/api/types.ts lines
39-42, rely on resolveFormatPrefs’ runtime narrowing rather than the declared
union; leave the DATE_ORDER fallback in packages/datetime/src/index.ts line 650
as defense in depth.
- Around line 303-322: Update the timezone selection around candidateTimeZone to
choose the first valid value from userPrefs?.timeZone, detected?.timeZone, and
HARDCODED_DEFAULT_PREFS.timeZone, rather than using nullish coalescing alone.
Reuse isValidTimeZone for each candidate in priority order, then return
candidateTimeZone directly in the result while preserving the existing default
behavior.
- Around line 521-526: Update the wantTime computation in the relevant formatter
to treat opts.includeTime === true as sufficient on its own, removing the
dateStyle exclusion so includeTime appends time when used with a dateStyle.
Preserve the existing onlyTime, timeStyle, hour, and minute conditions.
- Around line 577-583: Update the date-formatting branch for “MM_DD_YYYY” and
“MMM_DD_YYYY” so it returns only rendered.year when the filtered monthDay value
is empty, avoiding a leading comma for year-only requests. Preserve the existing
comma-separated output when monthDay contains a value.
- Around line 684-698: Update the H12 formatting branch in the wantTime logic to
honor n.hourStyle by applying pad2 to the hour when it is "2-digit", while
preserving the Intl-derived hour for other styles. Keep the existing minute and
day-period formatting unchanged so H12 produces consistent zero-padding with the
H24 branch.

---

Nitpick comments:
In `@apps/webapp/app/modules/api/mobile-auth.server.test.ts`:
- Around line 288-301: Replace the hardcoded dbRow object definition with a call
to an existing user-row factory from the webapp test helpers, passing field
overrides for only the four preference fields (dateFormat, timeFormat,
weekStart, timeZone) while relying on the factory to provide the base user data
with id, email, firstName, lastName, profilePicture, onboarded, deletedAt, and
lastMobileActiveAt. Preserve the test's behavior by ensuring the factory returns
the same data shape when called with these overrides.

In `@packages/datetime/src/index.ts`:
- Around line 599-682: Add a focused, table-driven unit-test suite for
formatDate covering all five DateFormatPreference values and common option
shapes, including date-only strings, localeOnly, name-month preferences,
additive includeWeekday, preset folding, and both 12-hour and 24-hour clocks.
Use stable timezone/locale inputs and assert the promised formatted output
without changing production behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b031f735-9b0e-41ad-aeb4-468d808c8c69

📥 Commits

Reviewing files that changed from the base of the PR and between a08594c and cbbfea3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (26)
  • apps/companion/app/(tabs)/assets/[id].tsx
  • apps/companion/app/(tabs)/assets/kits/[id].tsx
  • apps/companion/app/(tabs)/audits/[id].tsx
  • apps/companion/app/(tabs)/audits/index.tsx
  • apps/companion/app/(tabs)/bookings/[id].tsx
  • apps/companion/app/(tabs)/bookings/edit.tsx
  • apps/companion/app/(tabs)/bookings/index.tsx
  • apps/companion/app/(tabs)/bookings/new.tsx
  • apps/companion/app/(tabs)/home.tsx
  • apps/companion/components/asset-detail/custom-fields-section.tsx
  • apps/companion/components/asset-detail/notes-section.tsx
  • apps/companion/components/asset-edit/custom-field-input.tsx
  • apps/companion/lib/api/types.ts
  • apps/companion/lib/audit-format.ts
  • apps/companion/lib/constants.ts
  • apps/companion/lib/org-context.tsx
  • apps/companion/lib/use-date-formatter.ts
  • apps/companion/package.json
  • apps/webapp/app/modules/api/mobile-auth.server.test.ts
  • apps/webapp/app/modules/api/mobile-auth.server.ts
  • apps/webapp/app/utils/date-format.ts
  • apps/webapp/package.json
  • apps/webapp/vite.config.ts
  • packages/datetime/package.json
  • packages/datetime/src/index.ts
  • packages/datetime/tsconfig.json
💤 Files with no reviewable changes (1)
  • apps/companion/lib/constants.ts

Comment thread apps/companion/app/(tabs)/bookings/edit.tsx Outdated
Comment thread apps/companion/components/asset-edit/custom-field-input.tsx Outdated
Comment thread apps/companion/lib/audit-format.ts
Comment thread apps/webapp/app/modules/api/mobile-auth.server.test.ts Outdated
Comment thread packages/datetime/package.json
Comment thread packages/datetime/src/index.ts Outdated
Comment thread packages/datetime/src/index.ts Outdated
Comment thread packages/datetime/src/index.ts Outdated
Comment thread packages/datetime/src/index.ts
Follow-up to the shared @shelf/datetime work, resolving reviewer findings:

- Booking new/edit picker labels now render DEVICE-local (`localeOnly`) to match
  the native picker + `toLocalWire` submission, instead of the preferred
  timezone, while still honoring the user's date/time format. (CodeRabbit)
- Audit "Due today/tomorrow" relative labels compute the calendar-day delta in
  `prefs.timeZone` via a new tested `calendarDayIndex` helper, so they agree with
  the timezone-formatted absolute date. (Codex + CodeRabbit)
- `resolveFormatPrefs` now narrows every field against its known member set and
  picks the first VALID timezone candidate, so a corrupted/untrusted stored value
  degrades to the device hint instead of passing through (or falling to UTC).
  This matters now that the companion feeds unvalidated JSON into it. (CodeRabbit)
- Home booking card keeps its compact month/day range (no year), still tz-aware.
  (Copilot)
- Custom-field DATE display shares one validated date with the picker, so an
  invalid stored value renders "Select date..." rather than an overflowed date.
  (CodeRabbit)
- Formatter hardening: `includeTime` is additive with `dateStyle`; a year-only
  month-name request no longer emits a leading comma; the H12 clock honors
  `hour: "2-digit"` padding like H24. (CodeRabbit)
- mobile-auth test resets the shared spy and reads the latest call. (CodeRabbit)

Adds 9 regression tests to date-format.test.ts (all green).
Copilot AI review requested due to automatic review settings August 4, 2026 09:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

The companion DATE custom-field input still treats invalid stored values as “selected” for accessibility/styling, producing inconsistent UI and an incorrect accessibility label.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (2)

apps/companion/components/asset-edit/custom-field-input.tsx:452

  • When displayValue is empty (invalid stored date), the text still uses pickerSelectedText styling because it’s keyed off value. This makes the placeholder render with “selected” styling. Key the styling off displayValue so invalid values look like the empty state.
        <Text
          style={value ? styles.pickerSelectedText : styles.pickerPlaceholder}
        >
          {displayValue || "Select date..."}
        </Text>

apps/companion/components/asset-edit/custom-field-input.tsx:444

  • The button’s accessibilityLabel treats any non-empty value as valid, but displayValue is intentionally empty when the stored YYYY-MM-DD is invalid. This can produce an a11y label like "<label>: , tap to change" for corrupted values; it should key off displayValue instead.

This issue also appears on line 448 of the same file.

        accessibilityLabel={
          value
            ? `${accessibilityLabel}: ${displayValue}, tap to change`
            : `${accessibilityLabel}, tap to choose`
        }
  • Files reviewed: 27/28 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/companion/components/asset-edit/custom-field-input.tsx`:
- Line 408: Update the date display and selected-state logic in the custom field
input to use the validated parsed date consistently: base displayValue and the
related selected text styling and “tap to change” announcement on parsedDate !==
null rather than the raw value, while preserving empty output and unselected
behavior for invalid dates.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f0bfd55-3284-4cdd-a06d-d0aeb3226764

📥 Commits

Reviewing files that changed from the base of the PR and between cbbfea3 and 5d30ae5.

📒 Files selected for processing (8)
  • apps/companion/app/(tabs)/bookings/edit.tsx
  • apps/companion/app/(tabs)/bookings/new.tsx
  • apps/companion/app/(tabs)/home.tsx
  • apps/companion/components/asset-edit/custom-field-input.tsx
  • apps/companion/lib/audit-format.ts
  • apps/webapp/app/modules/api/mobile-auth.server.test.ts
  • apps/webapp/app/utils/date-format.test.ts
  • packages/datetime/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/companion/app/(tabs)/home.tsx
  • apps/companion/app/(tabs)/bookings/edit.tsx
  • apps/webapp/app/modules/api/mobile-auth.server.test.ts
  • apps/companion/app/(tabs)/bookings/new.tsx
  • packages/datetime/src/index.ts

Comment thread apps/companion/components/asset-edit/custom-field-input.tsx
…tyling + a11y

The DATE custom-field input keyed its "selected" text styling and accessibility
label off the raw `value`, but `displayValue` is already validated. A present-
but-invalid stored value (e.g. "2026-02-30") therefore showed selected styling
and announced "tap to change" with an empty label. Key both off the validated
`parsedDate` so an invalid value reads as unselected everywhere, consistent with
the display text and the picker. (Copilot + CodeRabbit re-review, PR #2798.)
Copilot AI review requested due to automatic review settings August 4, 2026 11:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

A new JSDoc link in @shelf/datetime references a helper that is not exported/defined by the package, so the documentation is currently incorrect/broken.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

packages/datetime/src/index.ts:82

  • JSDoc references detectFormatPrefsForPersistence, but that helper is not exported/defined in @shelf/datetime (it lives in the webapp’s ~/utils/client-hints.tsx). As-is, the link is broken and it reads like the function should exist in this package.
  /**
   * Nullable for PERSISTENCE. The timezone comes from the `CH-time-zone` COOKIE,
   * which is absent on a user's first authenticated request (and on
   * server-established sessions like SSO/OAuth callbacks that never render
   * `ClientHintCheck` first). Persisting the "UTC" fallback then is
   * indistinguishable from a real UTC and permanently blocks the lazy backfill
   * from ever writing the true zone — so persistence paths
   * ({@link detectFormatPrefsForPersistence}) set this to null when the cookie is
   * absent, leaving the column null for a later retry. The read path
   * ({@link detectFormatPrefsFromHints}) always yields a concrete zone.
  • Files reviewed: 27/28 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 4, 2026 12:48
`DetectedFormatPrefs.timeZone` linked `{@link detectFormatPrefsForPersistence}`,
which lives in the webapp's client-hints, not in @shelf/datetime — so the link
dangled once the formatter moved into the package. Reference it in prose instead;
the in-package `{@link detectFormatPrefsFromHints}` stays a link. (Copilot
re-review, PR #2798.)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

calendarDayIndex can currently throw on invalid timeZone/invalid dates and should be hardened consistently with the rest of the shared formatter to avoid crashing callers.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

packages/datetime/src/index.ts:180

  • calendarDayIndex constructs Intl.DateTimeFormat directly, so an invalid timeZone (or an invalid/NaN Date) will throw and can crash callers. Since this package is explicitly hardened elsewhere against bad timezones (and is used on the companion against untrusted JSON), this helper should also degrade safely (e.g., fall back to UTC for invalid zones and return NaN for invalid dates).
 */
export function calendarDayIndex(
  value: string | Date,
  timeZone: string
): number {
  const date = value instanceof Date ? value : new Date(value);
  const parts = new Intl.DateTimeFormat("en-US", {
    timeZone,
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
  }).formatToParts(date);
  const get = (t: string) => Number(parts.find((p) => p.type === t)?.value);
  return Math.floor(
    Date.UTC(get("year"), get("month") - 1, get("day")) / 86_400_000
  );
  • Files reviewed: 27/28 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 4, 2026 12:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

OrgContext currently passes resolvedOptions().timeZone through without a fallback/guard, which can yield an invalid/undefined timezone flowing into resolved prefs in environments where Intl timezone data is missing or partial.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (1)

apps/companion/lib/org-context.tsx:160

  • Intl.DateTimeFormat().resolvedOptions().timeZone can be missing/unsupported (there are other call sites in the companion that already fall back to "UTC"). Here we pass timeZone straight through to resolveFormatPrefs, which can result in undefined being treated as a valid timezone and then flowing into formatter prefs (or throwing in environments where Intl is partially available). Add a safe fallback before building the FormatHints.
  const formatPrefs = useMemo<ResolvedFormatPrefs>(() => {
    const { locale, timeZone } = Intl.DateTimeFormat().resolvedOptions();
  • Files reviewed: 27/28 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

…tl data

OrgContext passed `Intl.DateTimeFormat().resolvedOptions()` straight into the
format-pref hints. resolveFormatPrefs already validates the zone downstream, but
guard at the source too: coalesce an undefined/blank locale or timeZone to
en-US / UTC and wrap the call in try/catch, so an environment with missing or
partial Intl timezone data degrades cleanly instead of feeding an undefined hint
through (or throwing out of render). (Copilot re-review, PR #2798.)
Copilot AI review requested due to automatic review settings August 4, 2026 13:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Ready to approve

The formatter extraction is consistent with existing workspace patterns, the mobile /me contract is guarded by a targeted unit test, and companion formatting changes are centralized via context/hooks with safe fallbacks.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file
  • Files reviewed: 27/28 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@DonKoko
DonKoko merged commit 6361221 into main Aug 4, 2026
9 checks passed
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.

2 participants