Commit 82d8f62
refactor: cut the event runtime over to calendar-owned contracts (#2017)
* refactor(backend): use calendar-owned event repository
Packet 03 backend cutover (phases 1-2): every backend event path now runs on
the strict calendar-owned contracts from packets 01-02.
- one event repository owns all event Mongo access; ownership is proven
through the calendar (no event.user queries); range reads are the
two-branch timed/date-only design with the series join preserved
- strict HTTP surface: EventListQuery/CreateEventInput (client ids, A25)/
ReplaceEventInput/DeleteEventInput/ReorderEventsInput plus the new
someday<->scheduled transition endpoint (A24); EventMutationError envelope
with new DUPLICATE_EVENT_ID code; legacy applyTo wire values retired
- recurrence pipeline rebuilt (parser/generator/executor) on EventRecord +
RecurrenceScope; materialized instances; thisAndFollowing still splits via
UNTIL; someday series no longer materialize duplicate instances
- compass->google propagation resolves the owning calendar, writes via
events.patch bodies (A28) with explicit calendar ids, keeps Google effects
outside the Mongo transaction; series edits patch the base once instead of
fanning out per-instance inserts
- google import + webhook propagation flow through mapGoogleEvent and match
by (calendarId, externalReference.eventId); backfill targets google-calendar
events with null externalReference (origin retired, A34)
- revoke prune deletes google-calendar events, archives those calendars
(isActive false, A16), preserves all local/someday data
- SSE publishes the ServerMessage union on one event name (A27): eventsChanged
with real calendar/event ids, syncStatusChanged, importCompleted with
accurate counts, userMetadataChanged; legacy SSE names removed
- calendar API returns strict Calendar models; POST /api/calendars removed
(A15); select accepts the bulk isVisible contract
- known 03 limitation recorded in plan 05: scope-"this" edits on synced
series occurrences do not yet propagate as Google exceptions
- frozen 2025 migrations untouched behaviorally (raw-collection casts only);
the superseded prototype backfill TEST is removed - it can only be fed by
legacy fixtures the runtime no longer produces, and packet 02's backfill
tests own that coverage with hand-rolled legacy docs
backend 63/63 suites (477 tests), core 265, scripts 139, type-check green.
Web compiles via minimal mechanical shims marked TODO(packet-03-phase-3);
web behavior cutover follows in the next commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(web): cut data and state layers over to calendar-owned contracts
Calendars module + sentinel, SSE ServerMessage parsing, event API on the new
routes, remote/local repositories, IndexedDB v4 with legacy migration and
new-shape demo seed, normalized Event queries/cache (overlap semantics now
match the backend; legacy minus-one-day week hack removed), mutations with
client ids + transition replacing the convert pair, undo/redo preserved,
revoke prune by calendar provider. Component/view layer still bridges through
event.legacy-bridge.ts pending the final web pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(web): correct someday edit, test isolation, and e2e helpers for cutover
- someday edits silently vanished: the sidebar submit path forced order=-1,
which fails the strict nonnegative SortOrderSchema during replace-input
assembly; placeholder now applies only to new drafts
- cross-file web test failures fixed at their real roots (query-key field
names in useDayEvents; a leaked authenticated useSession mock from
useSubscribeCmdItems)
- e2e IndexedDB helper reads the nested LocalEventRecord shape
- view model drops malformed cache entries loudly instead of crash-looping
- docs: architecture domain model reflects the shipped contracts; plan 03
carries an explicit status note (component-layer bridge dissolution is the
remaining work before its boxes are checked)
Full matrix green: backend 63/63 (477), web 1242, core 265, scripts 139,
type-check clean, full local e2e 11/11.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(web): reset event-repository source between test files
event.repository.source.store.ts caches its local/remote resolution in
module-level singletons (lastSessionExists/hasComputed), outside Zustand and
therefore invisible to the reset-stores registry. Bun test workers share the
module registry across files, so any earlier file resolving an authenticated
session leaked a 'remote' source into every later file in the same worker —
directory-traversal test order differs between macOS and Linux, so this only
surfaced in CI. Register the store's reset in the shared registry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(web): restore the real offline-data-store registry after mocking it
Four test files replace @web/common/storage/offline-data/offline-data.store.registry
wholesale via mock.module, which leaks process-wide for the rest of the bun
test run. Two of them mock getOfflineDataStore as a bare mock() (resolves to
undefined), so any later file in the same worker that calls it for real
throws 'undefined is not an object'. Bun test-file load order is directory
traversal, which differs between macOS (APFS) and Linux (ext4/CI), so this
only surfaced on CI, on useWeekShortcuts.test.tsx, which seeds an event via
getOfflineDataStore().putEvent(...) and does not mock the registry itself.
Capture the real module via require() before each mock.module() call and
restore it in afterAll — kills the class for any future file, in any order.
Verified with the deliberately-adversarial order (all four mocking files
loaded before the previously-failing file) and 3 consecutive full-suite runs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Revert "fix(web): restore the real offline-data-store registry after mocking it"
This reverts commit 847663e.
* fix(web): make the offline-data-store mock leak fix order-independent
The prior fix (per-file require()-before-mock capture) was unsound: if an
earlier test file in the same bun worker had already mocked the registry,
that file's own 'real' capture was itself the earlier file's mock, not the
true module — so restoration just propagated whichever mock happened to run
first. CI's Linux directory-traversal file order differs from local macOS
order and hit this case, so the useWeekShortcuts crash persisted despite the
previous fix passing locally.
Correct fix: capture the true module once in web.preload.ts, which runs
before any test file's top-level code (guaranteed unpolluted), and restore
it in the shared global afterEach that already runs after every single test
regardless of file. The four files that replace the registry via
mock.module now re-assert their own mock in beforeEach instead of relying on
their one-time top-level call, so the global restore doesn't strand their
own later tests. This makes the fix's correctness independent of file load
order entirely, rather than dependent on being first.
Verified with the full local suite (TZ=UTC, matching CI) 3 consecutive runs,
plus explicit forward/reverse/worst-case file orderings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(web): replace mock.module offline-store mocking with a runtime override
Both prior attempts to fix the offline-data-store mock leak assumed
mock.module's replacement is visible to every consumer at call time. It
isn't: mock.module redirects future module *resolution*, but a module that
already captured a live binding to an export (e.g. local.event.repository.ts
calling getOfflineDataStore() internally, bound whenever that module was
first imported/linked) keeps referencing whatever was current at ITS OWN
first import, permanently — no later mock.module call can retroactively fix
an already-linked consumer. This explains why the previous fix still failed
on CI (Linux file-load order determines which mock a given consumer gets
frozen to) and why a second symptom appeared (DayCalendarGrid getting
local.event.repository.test.ts's getEvents-less mock).
Correct fix: offline-data.store.registry.ts now holds a plain
testOverrides object that every export reads at CALL time, not import time.
This is real runtime state inside one module instance, immune to Bun's
module-resolution caching entirely. The four suites that need to fake
IndexedDB access call setOfflineDataStoreTestOverrides(...) in beforeEach
instead of mock.module; web.preload.ts's shared afterEach clears overrides
after every test.
Verified: full local suite green 3x at TZ=UTC (matching CI), plus forward,
reverse, and worst-case explicit file orderings including the two files
that previously exposed real symptoms (useWeekShortcuts, DayCalendarGrid).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(web): replace offline-store test overrides with constructor seams
The testOverrides object in offline-data.store.registry.ts put test
vocabulary in production code and needed paragraph-length comments in five
files to explain why tests worked - both smells pointing at the same root
cause: the tests were substituting a module when they only needed to
substitute a collaborator.
- offline-data.store.registry.ts: reverted to its original form (-40 lines
of override machinery)
- LocalEventRepository takes its store accessor as a constructor default;
RemoteEventRepository takes the api and local repository the same way;
initializeDatabaseWithErrorHandling takes the initializer as a parameter
default - the same deps-with-defaults convention local-event-sync.util.ts
already uses
- the four test files pass plain fakes through those seams; no mock.module
on the registry remains anywhere, so the cross-file leak class is gone by
construction rather than by cleanup bookkeeping; remote repository tests
now assert delegation to the local repository instead of reaching through
it into store internals
- useTaskState.test needs no substitute at all: with fake-indexeddb in the
preload, the real ensureOfflineDataStoreReady is fast and is exactly what
the hook promises to do
Net -108 lines. Full web suite green 3x at TZ=UTC plus worst-case explicit
file orderings; type-check clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(backend): extract transaction wrapper, drop unreachable active checks
startSession/withTransaction/endSession was copy-pasted in create, replace,
delete, and transition; one private withEventTransaction helper now owns it.
Two !calendar.isActive branches removed: getOwnedActiveCalendar already
filters isActive in its query, so they could never fire.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(backend): dedupe series materialization in event generator
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(backend): extract scheduleStartMs helper in event parser
The timed/allDay/someday to epoch-ms ternary appeared four times across
analyzeReplace and analyzeDelete; semantics preserved exactly per site.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(backend): remove dead exports and narration comment
deleteOne on the event repository and getScheduleStartMs in recur.util had
zero callers repo-wide; plus one comment that restated the next line.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(web): delete dead computeSomedayEventsRequestFilter
Exported with no call sites anywhere in packages/web after the someday
queries moved to period/anchor params.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(web): dedupe event-query-cache writer boilerplate
All seven cache writers repeated the same iterate-matching-entries,
setQueryData, null-guard scaffolding; one shared forEachEventQuery helper
owns it now. Net -28 lines, behavior identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(web): share timed/all-day grid event derivation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(web): extract order predicate and trim restated storage comments
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(web): type someday-repository test schedule via schema parses
Replaces an as-unknown-as Parameters<> cast with real branded values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(scripts): widen the flaky heap bound in the backfill memory test
Shared CI runners showed 245-296 MB of GC-timing noise for identical code
against the 250 MB threshold. The assertion exists to catch accumulation
proportional to the dataset (gigabytes at this fixture size), so 400 MB
keeps the guarantee while tolerating runner variance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(plan): record the staging-first v1 rollout strategy (A36)
Staging cuts over with the runtime-cutover merge and receives every packet
continuously (main auto-deploys staging); production stays on the
pre-cutover release, keeps writing legacy data (the backfill is rerunnable),
and cuts over exactly once - runbook then the manual Deploy Production
action - after the 09 gates pass on staging.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(skills): teach simplify where to find complexity
Detector-ordered hunting list distilled from this PR's simplification pass:
size-first triage, comment density as a mechanism smell, mock volume and
mock.module seams, test-only hooks in production code, cast smells,
copy-pasted scaffolding, dead/unreachable code, hidden-shared-state
coupling, and bridge audits - plus the restraint counterweight (don't unify
lookalikes, don't break repo-wide conventions locally, report what you
inspected and left alone).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent cb83f16 commit 82d8f62
199 files changed
Lines changed: 10714 additions & 18675 deletions
File tree
- .claude/skills/simplify
- docs
- architecture
- self-hosting
- e2e/utils
- handoff/someday
- packages
- backend/src
- __tests__
- drivers
- mocks.gcal/factories
- calendar
- controllers
- services
- common
- errors/handlers
- services
- gcal
- events/controllers
- event
- classes
- controllers
- queries
- services
- recur
- repo
- util
- servers/sse
- sync
- controllers
- services
- event-propagation
- __tests__
- compass-to-google-this-event
- compass-to-google
- google-to-compass
- google-sync
- import
- notify/handler
- watch
- user/services
- core/src
- constants
- types
- scripts/src
- __tests__/integration
- migrations
- seeders
- web/src
- __tests__
- __mocks__
- utils
- factories
- state
- api/util
- auth/google
- hooks/useConnectGoogle
- util
- calendars
- common
- storage
- migrations
- data
- external
- offline-data
- types
- utils
- datetime
- event
- sync
- validators
- components
- CommandPalette
- hooks
- ContextMenu
- FloatingEventForm
- PlannerSidebar
- SomedayEventSections/SomedayEvents/SomedayEventContainer
- draft/hooks
- events
- mutations
- queries
- recurrence
- repositories
- stores
- sse
- client
- hooks
- provider
- views
- Day
- components/Calendar
- hooks
- events
- shortcuts
- tasks
- interaction
- Forms
- EventForm
- DateControlsSection/DateTimeSection/TimePicker
- hooks
- Week
- components
- Draft/hooks
- actions
- state
- Grid/MainGrid
- hooks/shortcuts
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
22 | 81 | | |
23 | 82 | | |
24 | 83 | | |
| |||
157 | 216 | | |
158 | 217 | | |
159 | 218 | | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
167 | 226 | | |
168 | 227 | | |
169 | 228 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
19 | 41 | | |
20 | 42 | | |
21 | 43 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
7 | 11 | | |
8 | 12 | | |
9 | 13 | | |
| |||
13 | 17 | | |
14 | 18 | | |
15 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
16 | 29 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
32 | 57 | | |
33 | 58 | | |
34 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
8 | 19 | | |
9 | 20 | | |
10 | 21 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
50 | 56 | | |
51 | 57 | | |
52 | 58 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
75 | 79 | | |
76 | 80 | | |
77 | 81 | | |
| |||
0 commit comments