Skip to content

[refactor] Move availability utils + offset into @bluedot/utils#2604

Merged
joshestein merged 2 commits into
masterfrom
josh/2603-wa-utils-refactor
Jun 2, 2026
Merged

[refactor] Move availability utils + offset into @bluedot/utils#2604
joshestein merged 2 commits into
masterfrom
josh/2603-wa-utils-refactor

Conversation

@joshestein

@joshestein joshestein commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Relocate the framework-agnostic weekly-availabilities conversion logic and timezone offset helpers out of apps/availability into @bluedot/utils, so server-side code can consume them without pulling in React. Adds gridToUtcIntervalString / utcIntervalStringToGrid helpers. TimeAvailabilityGrid stays in @bluedot/ui, now importing MINUTES_IN_UNIT + TimeAvailabilityMap from @bluedot/utils.

Closes #2603

Relocate the framework-agnostic weekly-availabilities conversion logic and timezone offset helpers out of apps/availability into @bluedot/utils, so server-side code can consume them without pulling in React. Adds gridToUtcIntervalString / utcIntervalStringToGrid helpers. TimeAvailabilityGrid stays in @bluedot/ui, now importing MINUTES_IN_UNIT + TimeAvailabilityMap from @bluedot/utils.

Closes #2603

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@joshestein, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 47 minutes and 43 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 604b53e6-a34f-4a8c-b807-19bd5c1be47c

📥 Commits

Reviewing files that changed from the base of the PR and between e5edbb2 and 0363b8b.

📒 Files selected for processing (1)
  • libraries/utils/src/timeAvailability.ts
📝 Walkthrough

Walkthrough

This PR consolidates availability conversion utilities and timezone offset helpers into @bluedot/utils as framework-agnostic domain code. TimeAvailabilityMap type and conversion constants move from @bluedot/ui, which now imports them from @bluedot/utils. Two encapsulating conversion functions—gridToUtcIntervalString and utcIntervalStringToGrid—enable round-tripping between timezone-aware grid selections and persisted UTC interval strings. The form page, input, and offset selector components are updated to use the new shared location. Tests verify round-trip conversion correctness across zero, positive, and negative timezone offsets.

Possibly related PRs

  • bluedotimpact/bluedot#1871: Both PRs modify availability form conversion logic in apps/availability/src/pages/form/[slug].tsx to change how UTC interval strings are converted between prefilled state and the form's grid representation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main objective: moving availability utilities and offset logic into @bluedot/utils package.
Description check ✅ Passed The description provides clear context for why the changes were made and references the related issue, though it does not follow the repository's template structure.
Linked Issues check ✅ Passed The PR fully implements all objectives from #2603: moving conversion/offset logic into @bluedot/utils, exporting shared constants/types, adding two encapsulating helpers (gridToUtcIntervalString and utcIntervalStringToGrid), keeping TimeAvailabilityGrid in @bluedot/ui while importing from @bluedot/utils, and enabling server-side code reuse.
Out of Scope Changes check ✅ Passed All changes are directly scoped to relocating availability conversion logic and timezone offset utilities from apps/availability to @bluedot/utils, with no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch josh/2603-wa-utils-refactor

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 and usage tips.

@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.

🧹 Nitpick comments (1)
libraries/ui/src/TimeAvailabilityGrid.tsx (1)

5-5: Confirm @bluedot/utils root barrel is safe for client imports
libraries/utils/src/index.ts re-exports both validateEnv and slackAlert, but validateEnv only touches process.env in the default parameter (envSource = process.env), so the browser won’t error on import (only if validateEnv() is called). slackNotifications.ts has no node:*/require/fs-style imports and relies on browser-compatible globals (fetch, timers), so importing the barrel from TimeAvailabilityGrid.tsx shouldn’t break client bundles.
Optional: still consider subpath exports so server-oriented helpers aren’t exposed to accidental client usage.

🤖 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 `@libraries/ui/src/TimeAvailabilityGrid.tsx` at line 5, The import from the
root barrel (`@bluedot/utils`) pulls MINUTES_IN_UNIT and TimeAvailabilityMap which
currently is safe but exposes server-oriented helpers; to prevent accidental
client-side imports of server-only utilities, either switch this file to import
the specific client-safe symbol subpath(s) (e.g., the module that exports
MINUTES_IN_UNIT/TimeAvailabilityMap) instead of the root barrel, or add a short
inline comment in TimeAvailabilityGrid.tsx noting that the root barrel is safe
for client use and why (validateEnv only reads process.env in its default arg
and slack notifications use browser globals). Update references around
MINUTES_IN_UNIT and TimeAvailabilityMap accordingly.
🤖 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.

Nitpick comments:
In `@libraries/ui/src/TimeAvailabilityGrid.tsx`:
- Line 5: The import from the root barrel (`@bluedot/utils`) pulls MINUTES_IN_UNIT
and TimeAvailabilityMap which currently is safe but exposes server-oriented
helpers; to prevent accidental client-side imports of server-only utilities,
either switch this file to import the specific client-safe symbol subpath(s)
(e.g., the module that exports MINUTES_IN_UNIT/TimeAvailabilityMap) instead of
the root barrel, or add a short inline comment in TimeAvailabilityGrid.tsx
noting that the root barrel is safe for client use and why (validateEnv only
reads process.env in its default arg and slack notifications use browser
globals). Update references around MINUTES_IN_UNIT and TimeAvailabilityMap
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 870f95b5-bb28-43d5-9e6e-9aad41c2b6fe

📥 Commits

Reviewing files that changed from the base of the PR and between df8d038 and e5edbb2.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • apps/availability/src/components/TimeAvailabilityInput.tsx
  • apps/availability/src/components/TimeOffsetSelector.tsx
  • apps/availability/src/pages/form/[slug].tsx
  • libraries/ui/src/TimeAvailabilityGrid.stories.tsx
  • libraries/ui/src/TimeAvailabilityGrid.tsx
  • libraries/ui/src/index.ts
  • libraries/utils/package.json
  • libraries/utils/src/index.ts
  • libraries/utils/src/timeAvailability.test.ts
  • libraries/utils/src/timeAvailability.ts
  • libraries/utils/src/timezoneOffset.test.ts
  • libraries/utils/src/timezoneOffset.ts

@greptile-apps

greptile-apps Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR moves timezone-offset helpers and weekly-availability grid utilities out of apps/availability into @bluedot/utils, making them consumable by server-side code without pulling in React. It also introduces two new convenience wrappers — gridToUtcIntervalString and utcIntervalStringToGrid — that encapsulate the local↔UTC conversion logic.

  • libraries/utils/src/timeAvailability.ts and timezoneOffset.ts are lifted verbatim (with minor additions) from apps/availability/src/lib/; @bluedot/ui's TimeAvailabilityGrid now imports MINUTES_IN_UNIT and TimeAvailabilityMap from @bluedot/utils instead of defining them locally.
  • MINUTES_IN_UNIT and TimeAvailabilityMap are removed from the @bluedot/ui public exports — a breaking change for external consumers, though all in-repo usages have been updated.
  • The internal helper functions (weeklyTimeAvToIntervals, intervalsToWeeklyTimeAv) still carry Record<wa.WeeklyTime, boolean> in their signatures rather than the new TimeAvailabilityMap alias, which requires explicit type casts at every call site and leaves one call in longEnoughInterval without the cast that every other call site applies.

Confidence Score: 3/5

The refactor is logically correct and well-tested, but a missing type cast in longEnoughInterval is inconsistent with every other call site and may surface as a TypeScript compile error.

The conversion logic is unchanged and round-trip tests pass for UTC, positive, and negative offsets. The main risk is in apps/availability/src/pages/form/[slug].tsx line 74, where weeklyTimeAvToIntervals is called with a TimeAvailabilityMap value without the as Record<wa.WeeklyTime, boolean> cast that gridToUtcIntervalString and intervalsToWeeklyTimeAv both rely on — if wa.WeeklyTime is not simply number, this is a hidden compile error on the critical form-submission path.

apps/availability/src/pages/form/[slug].tsx (missing cast in longEnoughInterval) and libraries/utils/src/timeAvailability.ts (internal helper signatures still reference Record<wa.WeeklyTime, boolean> instead of TimeAvailabilityMap).

Important Files Changed

Filename Overview
libraries/utils/src/timeAvailability.ts New file (moved from apps/availability). Adds TimeAvailabilityMap type and gridToUtcIntervalString/utcIntervalStringToGrid wrappers. Internal helpers still use Record<wa.WeeklyTime, boolean> instead of the new alias, forcing scattered type casts on callers.
libraries/utils/src/timezoneOffset.ts Verbatim rename from apps/availability/src/lib/offset.ts; no logic changes.
libraries/utils/src/timeAvailability.test.ts Tests moved and extended with new round-trip coverage for gridToUtcIntervalString / utcIntervalStringToGrid at UTC, positive and negative offsets.
apps/availability/src/pages/form/[slug].tsx Imports migrated to @bluedot/utils; form field type changed from Record<wa.WeeklyTime, boolean> to TimeAvailabilityMap. longEnoughInterval calls weeklyTimeAvToIntervals(watch('timeAv')) without the type cast that is applied everywhere else, which may be a TypeScript compile error.
libraries/ui/src/TimeAvailabilityGrid.tsx MINUTES_IN_UNIT and TimeAvailabilityMap now imported from @bluedot/utils; local declarations removed. No functional change.
libraries/ui/src/index.ts MINUTES_IN_UNIT and TimeAvailabilityMap removed from @bluedot/ui exports — breaking change for external consumers, but all in-repo usages have been updated to import from @bluedot/utils.
apps/availability/src/components/TimeAvailabilityInput.tsx Imports updated to @bluedot/utils for MINUTES_IN_WEEK and TimeAvailabilityMap. toWeeklyTimeAvMap runtime validation helper unchanged.
libraries/utils/package.json Adds weekly-availabilities as a runtime dependency (was previously only a dependency of the app).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["apps/availability\nform/[slug].tsx"] -->|"gridToUtcIntervalString(map, tz)"| B["@bluedot/utils\ntimeAvailability.ts"]
    A -->|"utcIntervalStringToGrid(str, tz)"| B
    B -->|"weeklyTimeAvToIntervals(map)"| C["weekly-availabilities\nwa.unionSchedules / wa.format"]
    B -->|"shiftIntervals(intervals, offset)"| C
    B -->|"wa.parseIntervals(str)"| C
    B -->|"parseOffsetFromStringToMinutes(tz)"| D["@bluedot/utils\ntimezoneOffset.ts"]
    E["@bluedot/ui\nTimeAvailabilityGrid.tsx"] -->|"MINUTES_IN_UNIT\nTimeAvailabilityMap"| B
    F["apps/availability\nTimeAvailabilityInput.tsx"] -->|"MINUTES_IN_WEEK\nTimeAvailabilityMap"| B
    G["apps/availability\nTimeOffsetSelector.tsx"] -->|"offsets"| D
Loading

Comments Outside Diff (2)

  1. apps/availability/src/pages/form/[slug].tsx, line 74-75 (link)

    P1 Missing type cast on weeklyTimeAvToIntervals call

    watch('timeAv') returns TimeAvailabilityMap (Record<number, boolean>), but weeklyTimeAvToIntervals expects Record<wa.WeeklyTime, boolean>. Everywhere else in the codebase this mismatch is handled with an explicit cast — gridToUtcIntervalString in timeAvailability.ts uses map as Record<wa.WeeklyTime, boolean> and intervalsToWeeklyTimeAv returns timeAv as Record<wa.WeeklyTime, boolean>. Without the same cast here TypeScript may flag this once the broader codebase is compiled, and the inconsistency leaves callers uncertain about which pattern to follow.

  2. libraries/utils/src/timeAvailability.ts, line 18-44 (link)

    P2 Internal function signatures still use Record<wa.WeeklyTime, boolean> instead of the new TimeAvailabilityMap

    weeklyTimeAvToIntervals takes Record<wa.WeeklyTime, boolean> and intervalsToWeeklyTimeAv returns Record<wa.WeeklyTime, boolean>, even though TimeAvailabilityMap (Record<number, boolean>) is now the canonical exported type. This forces callers to perform explicit casts (as seen in gridToUtcIntervalString and longEnoughInterval) and will surprise any new consumer of the raw helpers. Aligning these signatures to use TimeAvailabilityMap directly would remove all the scattered as Record<wa.WeeklyTime, boolean> casts.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "[refactor] Move availability utils + off..." | Re-trigger Greptile

Drops the scattered `as Record<wa.WeeklyTime, boolean>` casts: the helpers
operate on the canonical TimeAvailabilityMap, so callers no longer need to cast.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Will-Howard Will-Howard temporarily deployed to josh/2603-wa-utils-refactor - bluedot-preview PR #2604 June 2, 2026 16:57 — with Render Destroyed
@Will-Howard Will-Howard temporarily deployed to josh/2603-wa-utils-refactor - bluedot-storybook-preview PR #2604 June 2, 2026 16:57 — with Render Destroyed
@joshestein

Copy link
Copy Markdown
Collaborator Author

Thanks bots. Addressed in 0363b8b.

Greptile P2 (internal signatures should use TimeAvailabilityMap) — applied. weeklyTimeAvToIntervals now takes TimeAvailabilityMap and intervalsToWeeklyTimeAv returns it, which let me drop every as Record<wa.WeeklyTime, boolean> cast (in intervalsToWeeklyTimeAv, gridToUtcIntervalString, and the call site).

Greptile P1 (missing cast in longEnoughInterval) — this isn't actually a compile error: wa.WeeklyTime is Brand<number, 'WeeklyTime'>, so Record<number, boolean> is assignable to Record<wa.WeeklyTime, boolean> (the branded-number key collapses to a number index signature). Confirmed with tsc --noEmit on both @bluedot/utils and apps/availability (exit 0, before and after). The P2 fix removes the inconsistency anyway — no call site casts now.

CodeRabbit (subpath exports for the @bluedot/utils barrel) — declining. Bundlers tree-shake unused exports, and validateEnv / slackAlert aren't referenced from TimeAvailabilityGrid so they won't land in the client bundle. CodeRabbit's own analysis confirms both are client-safe regardless. Subpath exports would be a broader package restructure, out of scope here.

Typecheck + the 7 timeAvailability tests pass after the change.

@joshestein joshestein merged commit 3cc13c9 into master Jun 2, 2026
7 checks passed
@joshestein joshestein deleted the josh/2603-wa-utils-refactor branch June 2, 2026 17:01
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.

Move weekly-availabilities utils + offset into @bluedot/utils

2 participants