PRD: TanStack Time v1 — Modular kernel, Calendar product, server package

Offen
#25 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Anfängerfreundlichkeit
15/100
Issue-Typ
Feature
Klarheit
Größtenteils klar
Aktivitätsstatus
Ruhig
Tech-Stack
react, typescript

Rechercherichtung

Start by reading the existing CalendarCore and ADR-0001, then map the requested kernel, modules, pipelines, adapters, and server package against the current implementation. Identify the tests covering recurrence, availability, dependencies, and public return types before proposing a sequence of changes. Done means the broad v1 requirements are implemented and verified, but this issue does not define a smaller first milestone.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

enhancement

Problem Statement

Developers building time-aware UIs — calendars, schedulers, Gantt charts — have no good headless option. Existing libraries (react-big-calendar, FullCalendar) are monolithic, opinionated about markup and styling, and impossible to restyle. Date utility libraries (date-fns, Luxon) solve arithmetic but not UI state. The result: every team either wires together three incompatible libraries or rebuilds the same calendar logic from scratch, incorrectly, every time (all-day off-by-one bugs, timezone-incorrect arithmetic, no recurrence, no conflict detection).

The current `CalendarCore` in this repo already solves many of these problems, but as a ~3,800-line god class with everything fused together: viewport, projection, recurrence, drag-resize, availability, dependencies, undo/redo, and timeline layout. There is no module boundary, no way to tree-shake unused features, no isomorphic validation, and the public API exposes `Temporal.PlainDate` directly despite the library's own stated contract.

Solution

TanStack Time v1 ships as two coherent layers:

  1. Date Primitives — pure, tree-shakeable functions covering the full date-fns surface, returning native `Date` objects, timezone- and calendar-system-correct via Temporal internally.

  2. Headless kernel + modules — a feature-agnostic kernel (event collection + viewport + projection) extended by opt-in modules in the style of TanStack Table v9. The Calendar product (kernel + recurrence + drag-resize + availability modules) ships as the v1 product. A server package exposes the same validation logic isomorphically so authoritative conflict checking runs against the real database.

Consumers get full control over markup, styling, and geometry. The library computes; the consumer renders.

User Stories

Date Primitives
  1. As a developer migrating from date-fns, I want to call `add(date, { days: 1 })` and receive a native `Date` back, so that I can drop in TanStack Time without rewriting my type signatures.
  2. As a developer, I want `startOf`, `endOf`, `add`, `subtract`, `format`, `parse`, `isBefore`, `isAfter`, `isBetween`, `intersects`, `since`, `until`, `round`, `clamp`, and `range` as standalone importable functions, so that I only bundle what I use.
  3. As a developer working across timezones, I want all arithmetic to respect the supplied timezone so that `startOf(date, 'month', { timeZone: 'America/New_York' })` returns the correct instant for New York, not my server's local zone.
  4. As a developer using non-Gregorian calendars, I want the library to internally use Temporal so that calendar-system-correct arithmetic is possible without me importing the polyfill directly.
  5. As a developer, I want civil values (day boundaries, month starts) returned as `YYYY-MM-DD` ISO strings rather than `Date` objects, so that I never hit the all-day off-by-one bug from UTC-midnight encoding.
Calendar Product — Core
  1. As a calendar app developer, I want to instantiate a `CalendarCore` with an event array and get back a reactive `Day[]` projection for the current viewport, so that my view layer only needs to iterate and render.
  2. As a developer, I want `Day.date` to be a `YYYY-MM-DD` string (not a `Temporal.PlainDate`), so that my component does not need to import the Temporal polyfill.
  3. As a developer, I want to navigate the viewport (previous, next, today, specific date) and have the projection recompute automatically, so that I do not manage derived state myself.
  4. As a developer, I want to switch between day, week, workWeek, month, and agenda view modes, so that one calendar instance drives multiple view layouts.
  5. As a developer, I want `getTimeSlots()` to return the time slots for a day/week view without pixel values, so that I can render them at any scale.
Calendar Product — Recurrence Module
  1. As a developer, I want to define a recurring event with `frequency`, `interval`, `until`/`count`, `byWeekday` (with optional ordinal, e.g. "2nd Tuesday"), and `byMonthDay` (incl. negative, e.g. "last day of month"), so that I can express every recurrence pattern a standard calendar UI offers.
  2. As a developer, I want occurrences to be expanded ephemerally within the current viewport so that I never store expanded occurrences in my database.
  3. As a developer, I want to edit a single occurrence (creating an Override) without affecting others in the series.
  4. As a developer, I want to edit "this and following" occurrences by splitting the master event at a point in the series.
  5. As a developer, I want to delete a single occurrence (creating an Exception / EXDATE) without deleting the series.
  6. As a developer, I want `goToNextOccurrence` / `goToPreviousOccurrence` so that I can implement "jump to next event in series" navigation.
Calendar Product — Drag-Resize Module
  1. As a developer, I want to attach a `ResizeController` to pointer events and receive `deltaMinutes` updates in logical time units, so that my drag/resize handler does not need to know the pixel density of the calendar grid.
  2. As a developer, I want `validateResize` to return a result that tells me whether the proposed resize is blocked, and why (unavailable time, min-duration, dependency violation), before I commit it.
  3. As a developer, I want drag and resize operations to respect the same availability and dependency constraints as manual edits.
Calendar Product — Availability Module
  1. As a developer, I want to define Resources with availability windows (weekday + start/end time), capacity, and before/after buffers.
  2. As a developer, I want `getUnavailableRanges(date)` to return fractional ranges (not pixels) so that I can render blocked time bands at any scale.
  3. As a developer, I want advisory conflict detection when adding or editing an event, so that the UI can warn the user instantly without a server round-trip.
  4. As a developer, I want `getUnavailabilityDetails()` to explain which resource is unavailable and why (outside hours, over capacity, no availability defined), so that I can show a meaningful error message.
Calendar Product — Dependencies, Undo/Redo, Timeline View
  1. As a developer building a project timeline, I want to define FS/SS/FF/SF dependency links between events so that moving a predecessor automatically shifts its dependents.
  2. As a developer, I want `validateEventDependencies` to check whether placing an event satisfies its predecessor constraints before committing.
  3. As a developer, I want `undo()` / `redo()` to operate on atomic write batches (not individual field changes), so that a multi-event cascade (e.g. move + propagated dependent shifts) is undone as one step.
  4. As a developer, I want `getTimelineLayout()` to return logical lane/position data (fractional offsets, lane index) without pixel values, so that I can render a Gantt strip at any width.
Layout — Percentage Prop-Getter
  1. As a developer, I want `getEventProps(event)` to return a `style` object with percentage-based `top`, `height`, `left`, `width` values that I can spread directly onto an element, so that I do not have to implement overlap resolution myself.
  2. As a developer, I want raw logical fields (`startFraction`, `endFraction`, `column`, `columnCount`, `lane`) also available on each event in the projection, so that canvas and virtualized renderers can skip the prop-getter entirely.
  3. As a developer, I want the prop-getter to handle view orientation (vertical for day/week, horizontal for timeline) via a config on the view, so that the same logic covers both without duplication.
Framework Adapter
  1. As a React developer, I want a `useCalendar` hook that instantiates `CalendarCore` and returns reactive selectors, so that my components re-render only when the data they consume changes.
  2. As a React developer, I want `useCalendar` to accept a `strategy` option so that switching between client and server data strategies requires no change to my component tree.
  3. As a Solid developer, I want equivalent reactive primitives that prove the core is framework-agnostic, so that a future Solid adapter can be built on the same kernel.
Client / Server Data Strategy
  1. As a developer, I want to choose `strategy: 'client'` at construction and have all computation (recurrence-expand, conflict-detect, slot-generation) run in the browser over a supplied event array.
  2. As a developer with large datasets, I want to choose `strategy: 'server'` and have the same pipeline stages run server-side, with the client receiving materialized results and only performing viewport-clip and layout.
  3. As a developer, I want the observable output of the calendar to be identical regardless of which strategy I choose, so that I can switch without rewriting my view layer.
  4. As a developer using server strategy, I want the kernel to produce a serializable operation request (the TanStack Query key) so that caching and invalidation work without extra plumbing.
Server Package
  1. As a backend developer, I want to call `validateOnServer(write, { loadEvents }, ctx)` and have the library compute the required event range, call my `loadEvents` adapter, and run the same conflict/availability/dependency rules as the client, so that I get authoritative validation without coupling the library to my database.
  2. As a developer, I want the server package to be runtime-agnostic (Node, Bun, Deno, edge workers) so that it works in any deployment target.
  3. As a developer, I want the documentation to clearly state that transactional isolation (serializable reads + writes in one transaction) is my responsibility, and to recommend a DB exclusion constraint as a backstop for raw time-overlap, so that I do not build a false sense of safety on advisory-only guards.
Module System
  1. As a library author, I want to register modules at construction (`createCalendar({ modules: [...] })`) so that unused modules are tree-shaken at build time.
  2. As a library author, I want each module to declare which pipeline stage it participates in and a priority within that stage, so that stage ordering is deterministic regardless of registration order.
  3. As a library author, I want writes to be committed as atomic batches so that undo/redo and devtools observe one logical action, not a stream of individual field mutations.

Implementation Decisions

  • One feature-agnostic kernel. `CalendarCore` is refactored into a small kernel (event collection + viewport + projection + plain CRUD + `getRequiredRange`/rollback) and a set of modules. Calendar, Scheduler, and Timeline are products (module presets), not separate kernels. (ADR-0001)

  • Mandatory extraction. The availability, dependency-propagation, and recurrence validation logic currently fused into `CalendarCore` must be extracted into pure isomorphic functions (`validate(write, events, config) → conflicts`) with no store, viewport, DOM, or DB dependency. This extraction is the prerequisite for the module pipeline, server package, and Data Strategy.

  • Two ordered pipelines with kernel-defined stages and per-module priority. Not a hook bus. Read pipeline: `recurrence-expand → clip-to-viewport → layout`. Write pipeline: `recurrence-materialize → dependency-transform → availability-validate → commit → undo-snapshot/emit`. Stage order is fixed; priorities resolve module ordering within a stage. A module that both vetoes and transforms would break reasoning — validate-veto and transform are separate stages.

  • Public return types. Instant-bearing results return native `Date`. Civil values (`Day.date`, `currentPeriod`, `activeDate`, all-day event boundaries) return `YYYY-MM-DD` ISO strings. Temporal objects never appear in the public surface. This requires changing `Day.date`, `CalendarStore.currentPeriod`, and `activeDate` from `Temporal.PlainDate`. (ADR-0002)

  • Logical layout; percentage prop-getter on the view. The kernel and projection are pixel-free. `getUnavailableRanges` drops its `containerHeight` parameter and returns fractional minute ranges. `getTimelineLayout` returns fractional `left`/`width`. `getEventProps()` on the view returns a `%`-based `style` object. Raw logical fields remain public. (ADR-0003)

  • Advisory / authoritative validation split. The kernel stays sync; fetching is TanStack Query's job. The kernel exposes `getRequiredRange(write)` so the orchestration layer can ensure the required events are loaded before running the sync pipeline. The server package wraps the same pure validation core with a `loadEvents(range, ctx) → Event[]` adapter seam. (ADR-0004)

  • Data Strategy. Chosen at construction: `'client'` (all pipeline stages in-process) or `'server'` (delegatable stages run via consumer-provided transport + server-side handlers). Clip-to-viewport and layout always run client-side. The boundary is serializable (ADR-0002), so stages are network-transportable without additional marshalling. In `'client'` mode advisory and authoritative validation collapse; in `'server'` mode ADR-0004 applies. (ADR-0006)

  • Recurrence rule model scoped to the UI-builder subset. `RecurrenceRule` adds `byMonthDay` (negative allowed) and ordinal weekdays (`{ weekday: number, nth: number }`). Full RFC 5545 RRULE (BYWEEKNO, BYYEARDAY, BYSETPOS, WKST) is an optional interop adapter, not the structured model.

  • Devtools. The existing `@tanstack/time-devtools` and `@tanstack/solid-time-devtools` are retained. The `TimeClient` singleton (event bus for devtools) stays in the kernel, emitting on write commits. No scope change here.

Testing Decisions

A good test asserts observable public outputs given inputs — never internal implementation details, private methods, or which Temporal operations were called internally. Tests must remain valid across the modularization refactor.

Seams (highest-level first):

  1. Date Primitives — function-level: input `DateInput` (string | number | Date), output `Date` or string. Each function is independently testable with no setup. Existing `*.test.ts` co-located tests are the prior art.

  2. Kernel projection output — `calendar.getDaysWithEvents()` returns `Day[]` where `Day.date` is a `YYYY-MM-DD` string and `Day.events` contains the correct occurrences for the viewport. Tests assert the shape of this output, not how recurrence was expanded internally.

  3. Validation Core — `validate(write, events, config) → conflicts[]`. Pure function; zero setup beyond an array of plain events. This is the most isolated and highest-value seam — it tests the authoritative logic that both client and server share.

  4. `getEventProps()` — given an event with known `startFraction`/`endFraction`/`column`/`columnCount`, assert the returned `style` object contains the correct percentage strings. No DOM, no rendering.

  5. `useCalendar` hook — React Testing Library. Assert that the hook returns the expected `Day[]` shape and that actions (`addEvent`, `navigate`) produce the correct next state. The existing React tests in `packages/react-time` are the prior art.

  6. Server package — `validateOnServer(write, { loadEvents }, ctx)` with a mock `loadEvents`. Assert that the correct range is requested and that conflicts are returned correctly. No real DB.

  7. Data Strategy — given the same event array, assert that `strategy: 'client'` and `strategy: 'server'` (with a mock transport returning the pre-computed results) produce identical `Day[]` output at the view layer.

Out of Scope

  • Scheduler product — free-slot projection and `book()` are deferred. The kernel and availability module are built to support it; the product preset is not v1.
  • Vue, Svelte, Angular adapters — the README and `package.json` are aspirational; these adapters are post-v1.
  • `.ics` / full RFC 5545 RRULE interop — an optional adapter post-v1. The core recurrence model is the UI-builder subset only.
  • Non-Gregorian calendar QA — the engine must remain correct via Temporal; it is not a v1 QA/support commitment.
  • Pixel layout helpers — a pixel prop-getter (for canvas/virtualized renderers) may be added as an opt-in module post-v1; it is never in the core.
  • HTTP transport / route handlers — the library defines operation contracts and server-side handlers; the consumer owns the transport. No bundled HTTP layer.
  • Database adapters / transaction management — the consumer's responsibility. The library documents the transactional-isolation requirement and recommends a DB exclusion constraint as a backstop.

Further Notes

  • The `CONTRIBUTING_VISION.md` at the repo root is the full contributor guide, including the module decomposition table, the language glossary pointer (`CONTEXT.md`), and the explicit "what this is not" list.
  • `docs/adr/0001–0006` contain the full decision records with considered alternatives. Reviewers should read ADR-0001 (pipeline stages) and ADR-0002 (public types) before touching `CalendarCore`.
  • The god-class extraction (pure isomorphic validation core out of `CalendarCore`) is the mandatory first PR. Nothing else should be started until that seam exists.
  • Dependencies, undo/redo, and the timeline view are already implemented in `CalendarCore` and ship in v1 as-is. They become proper modules during modularization rather than being cut and re-added.
Vorherrschende Sprache
TypeScript
Sterne
620
Forks
6
PR-Merge-Kennzahlen
Keine gemergten PRs in 30 T.

Entwicklungsumgebung

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus TanStack/time

Alle Issues in TanStack/time

Ähnliche Issues

Weitere Issues zu TypeScript

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.