[codex] add calendar workflow foundations#157
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Storybook Image DiffOutcome: success Changed Storybook baselines: none in this PR. This check compares design-system component screenshots against committed baselines. Inline images show only added or modified Storybook baseline PNGs. |
Storybook Component Screenshot PreviewOutcome: success Screenshots: primitive component stories captured on desktop and mobile. This lane is separate from full-route Playwright screenshots and focuses on design-system component regressions. |
Playwright Hosted Data-FlowOutcome: success This optional check runs the mutation-backed profile flow against a configured hosted dev/staging target with isolated E2E test data. |
Playwright Data-Flow PreviewOutcome: success Captured flow:
Artifacts include screenshots, traces, and recorded video for the flow run. |
Playwright Image DiffOutcome: success Changed screenshot baselines: none in this PR. This check compares public route screenshots against committed baselines. Inline images show only added or modified baseline PNGs. |
Playwright Public Screenshot PreviewOutcome: success Screenshots: all public route checks passed on desktop and mobile. Full screenshot set is available in the artifact. Pixel diff baselines are handled by the separate Playwright Image Diff check. |
Vercel Preview DeploymentPreview: https://vr-dex-biwxapzl4-basicbit.vercel.app |
There was a problem hiding this comment.
Pull request overview
Adds foundations for calendar export/import workflows: a shared public ICS serializer that now supports multi-event public feeds, plus a Google Calendar import “review staging” layer in Convex (tables, normalizer, document creation helper, and publication blockers), with supporting docs and tests.
Changes:
- Add
createPublicEventFeedIcsto serialize a public ICS feed containing multiple safeVEVENTentries. - Introduce Convex schema + helpers for Google Calendar import staging (normalized candidates/fields + publication guardrails).
- Document the implemented #138 foundations and explicitly call out remaining non-goals/follow-on work.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/web/calendar-ics.test.ts | Adds coverage for multi-event public feed ICS serialization. |
| tests/backend/event-calendar-imports.test.ts | New tests for Google Calendar normalization, staging inserts, and publication blockers. |
| docs/planning/dependency-map.md | Records that #138 now has implemented foundations. |
| docs/planning/calendar-integration.md | Updates the calendar direction doc to reflect implemented export + import-staging foundations. |
| docs/backend/event-schema.md | Documents the new calendar import/export slice and staging table responsibilities. |
| convex/schema.ts | Adds staging tables (eventImportBatches, eventImportCandidates, eventImportCandidateFields) + indexes. |
| convex/_generated/api.d.ts | Exposes the new _eventCalendarImports module in generated API types. |
| convex/_eventCalendarImports.ts | Implements validators/types, Google Calendar normalization, staging document writer, and publication blockers. |
| apps/web/src/lib/calendar/ics.ts | Adds public feed ICS serialization and a shared VEVENT line helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (const match of value?.match(HTTPS_URL_PATTERN) ?? []) { | ||
| const url = requireHttpsUrl(match.replace(/[),.;]+$/g, ""), "Imported event link"); | ||
|
|
||
| if (url !== undefined && !seen.has(url)) { | ||
| seen.add(url); | ||
| links.push(url); | ||
| } |
| if (field.reviewState === "needs_correction") { | ||
| blockers.add("field_needs_correction"); | ||
| } |
There was a problem hiding this comment.
Rejected fields pass publication
getEventImportPublicationBlockers only blocks unreviewed and needs_correction, so a candidate with reviewState: "accepted", publicationState: "review_pending", and a public field marked "rejected" returns no blocker. That lets a reviewer-rejected imported fact proceed to the later publication step despite the field-level review state recording it as rejected; add a rejected-field blocker or require every publishable field to be accepted.
Artifacts
Repro: generated failing Node test for rejected public import field publication blockers
- Contains supporting evidence from the run (text/typescript; charset=utf-8).
Repro: failing test output showing rejected public field returns empty blockers
- Keeps the command output available without making the summary code-heavy.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: convex/_eventCalendarImports.ts
Line: 552-554
Comment:
**Rejected fields pass publication**
`getEventImportPublicationBlockers` only blocks `unreviewed` and `needs_correction`, so a candidate with `reviewState: "accepted"`, `publicationState: "review_pending"`, and a public field marked `"rejected"` returns no blocker. That lets a reviewer-rejected imported fact proceed to the later publication step despite the field-level review state recording it as rejected; add a rejected-field blocker or require every publishable field to be `accepted`.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7d9e243405
ℹ️ 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".
| ): { timestamp: number; timezone?: string; allDay: boolean } { | ||
| if (value?.dateTime !== undefined) { | ||
| return { | ||
| timestamp: requireIsoTimestamp(value.dateTime, fieldName), |
There was a problem hiding this comment.
Preserve Google timeZone when dateTime has no offset
When a valid Google event supplies dateTime like 2026-06-14T15:00:00 together with timeZone: "America/Los_Angeles", this parses the string in the server's local timezone instead of converting from the supplied IANA timezone, so the candidate can be staged at 15:00Z rather than 22:00Z. That corrupts review data for Google events using the offsetless+timeZone form; parse these with the provided timezone or reject offsetless values.
Useful? React with 👍 / 👎.
| sourceLabel: string, | ||
| fallbackTimeZone: string | undefined, | ||
| ): NormalizedGoogleCalendarImportCandidate { | ||
| const externalEventId = normalizeInlineText(event.id, "", EVENT_TEXT_MAX_LENGTH); |
There was a problem hiding this comment.
Preserve full Google event IDs
Using normalizeInlineText here truncates the opaque Google event id to 240 chars even though Google allows event ids up to 1024 chars. For valid long ids, two events with the same 240-char prefix collapse to the same stored externalEventId, breaking later update/cancellation matching; preserve the full id or explicitly reject over-length identifiers.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| const sourceUrl = requireHttpsUrl(event.htmlLink, "Google Calendar event URL"); | ||
| const start = parseGoogleCalendarTime(event.start, "Event start", fallbackTimeZone); |
There was a problem hiding this comment.
Stage Google cancellation tombstones without start times
For cancelled events returned by an incremental or showDeleted Google import, Google may send a cancellation tombstone with only identifiers and no start. This call then throws before cancellationState is recorded, so a single deleted recurring instance can abort the whole batch instead of being staged or skipped for review; handle status === "cancelled" before requiring start/end fields.
Useful? React with 👍 / 👎.
Summary
#138calendar workflow foundation and remaining non-goalsValidation
node --import tsx --test tests/web/calendar-ics.test.ts tests/backend/event-calendar-imports.test.tsnode --import tsx --test tests/backend/**/*.test.tsnode_modules\.bin\tsc.CMD --noEmit --project convex\tsconfig.jsonnode_modules\.bin\eslint.CMD src\lib\calendar\ics.tsfromapps/webnode_modules\.bin\next.CMD typegen;node scripts\ensure-next-type-stubs.mjs;node_modules\.bin\tsc.CMD --noEmit --incremental falsefromapps/webnode_modules\.bin\markdownlint-cli2.CMD docs\planning\calendar-integration.md docs\backend\event-schema.md docs\planning\dependency-map.mdnode scripts/run-convex-local.mjs dev --local --once --run health:status --tail-logs disablegit diff --checkPart of #138.