Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 3, 2026

Unauthenticated users were unable to create or view events without OAuth, forcing immediate authentication. This adds local-first event storage using IndexedDB via Dexie, allowing full event CRUD before authentication.

Changes

Database layer (compass-local.db.ts)

  • Dexie schema with events store keyed on _id
  • Reuses existing Schema_GridEvent type, no new local-only types
  • Indexes: startDate, endDate, isSomeday, createdAt

Storage utilities (indexeddb.util.ts)

  • CRUD operations: saveEventToIndexedDB, getEventsFromIndexedDB, updateEventInIndexedDB, deleteEventFromIndexedDB
  • Date range and someday filtering

Auth utilities (auth.util.ts)

  • isUserAuthenticated(): Check session validity
  • getUserId(): Returns actual ID or "local_user" placeholder

Saga modifications
All event sagas now check auth status first:

const authenticated = (yield call(isUserAuthenticated)) as boolean;

if (authenticated) {
  yield call(EventApi.create, event);
} else {
  yield call(saveEventToIndexedDB, event);
}

Modified sagas: createEvent, deleteEvent, editEvent, getEvents, getSomedayEvents, convertSomedayToCalendarEvent, convertCalendarToSomedayEvent, reorderSomedayEvents

Notes

  • Authenticated users unchanged, still use API directly
  • No pagination for local storage (returns all matching events)
  • Migration to provider-backed events post-auth is outside scope
Original prompt

This section details on the original issue you should resolve

<issue_title>Save events to indexeddb if user hasn't authenticated their gcal</issue_title>
<issue_description>

Storage: IndexedDB structure for events & tasks

Overall goals

  • Allow the user to create events and view them on their calendar without requiring them to go through the OAuth flow to get their google credentials.
  • Let unauthenticated users create/view events and tasks without going through OAuth.
  • Persist these in IndexedDB in a way that:
    • Mirrors our existing event and task types as closely as possible.
    • Minimizes “local-only” special cases.
    • Is easy to migrate to provider-backed events/tasks once the user authenticates.
  • Once authenticated and migrated, do not keep a long‑lived event mirror in IndexedDB; that would complicate 2‑way sync.

Implementation

1. Database & object stores

Database name: compass-local
Initial version: 1

On upgradeneeded, we’ll create two object stores:

  • events – for pre-auth calendar events.
  • tasks – for local tasks (used both pre-auth and post-auth unless/until we add a backend for tasks).
const DB_NAME = "compass-local";
const DB_VERSION = 1;

Upgrade handler sketch:

function openCompassLocalDB(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);

    request.onupgradeneeded = (event) => {
      const db = request.result;
      const oldVersion = event.oldVersion;

      // Version 0 → 1: create initial stores
      if (oldVersion < 1) {
        const events = db.createObjectStore("events", { keyPath: "_id" });
        events.createIndex("by_startDate", "startDate", { unique: false });
        events.createIndex("by_endDate", "endDate", { unique: false });
        events.createIndex("by_isSomeday", "isSomeday", { unique: false });
        events.createIndex("by_createdAt", "createdAt", { unique: false });

        const tasks = db.createObjectStore("tasks", { keyPath: "id" });
        tasks.createIndex("by_dateKey", "dateKey", { unique: false });
        tasks.createIndex("by_completed", "completed", { unique: false });
      }

      // Future versions: add conditional blocks here (see “Upgrades” section below).
    };

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

2. Events store: reuse existing schema, override ID only

We will not introduce a new LocalEvent type. Instead:

  • Use the existing event/grid-event types (e.g. Schema_Event / Schema_GridEvent) as the base.
  • For local events, we:
    • Generate a client-side _id (e.g. a UUID with local_evt_ prefix).
    • Optionally tag them with a source/origin flag if the existing schema already has such a field (e.g. "local" vs "provider").
  • Persist the same shape used by the UI/sagas into IndexedDB, so mapping in and out is trivial.

Store

  • Name: events
  • Key path: _id (string; same property we already use to identify events)
  • _id for local events must be unique and never collide with provider IDs; recommended: local_evt_${uuid}.

Record shape

Conceptually:

// Pseudocode – this is how we should *think* about it, not a new exported type:
type IndexedDBEvent = Schema_GridEvent & {
  // For local-only events, _id is a client-generated id.
  // For provider events, we do NOT store them here long term.
  _id: string;

  // Only if we already have some notion of source/origin in the existing schema.
  // If this doesn't exist, we SHOULD NOT add it solely for IndexedDB.
  source?: "local" | "provider";
};

Here’s an updated, concrete plan you can paste into #1407. I’ll structure it as an “Implementation Plan” section that incorporates all your feedback.

Because the automation couldn’t modify the issue, you’ll need to update the issue text manually.


Implementation Plan (Updated)

1. Recommended Technology: Dexie.js for IndexedDB

Instead of using raw IndexedDB, use Dexie.js as the abstraction layer:

  • Add Dexie as a dependency in packages/web:
    • yarn add dexie (and dexie-react-hooks if we want useLiveQuery later).
  • Create a small database module, e.g. packages/web/src/common/db/compass.db.ts:
import Dexie, { Table } from "dexie";
import { Schema_GridEvent } from "../../common/types/event.types";
import { Task } from "../../common/types/task.types";

export type ClientEvent = Schema_GridEvent & {
  /**
   * For local-only events, `_id` is a client-generated string.
   * For now, we only persist local events here, not provider-backed ones.
   */
  _id: string;
  /**
   * Whether this event has been successfully synced to the backend.
   * This will matter for future offline mode when authenticated.
   */
  synced?: boolean;
};

export class CompassDatabase extends Dexie {
  events!: Table<ClientEvent, st...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes SwitchbackTech/compass#1407

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

Copilot AI changed the title [WIP] Save events to IndexedDB for unauthenticated users Add IndexedDB storage for unauthenticated event operations Jan 3, 2026
Copilot AI requested a review from tyler-dane January 3, 2026 22:44
@tyler-dane tyler-dane changed the title Add IndexedDB storage for unauthenticated event operations feat(web): add IndexedDB storage for unauthenticated event operations Jan 3, 2026
Copy link
Contributor

@tyler-dane tyler-dane left a comment

Choose a reason for hiding this comment

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

Update names to align with the following pattern
• EventRepository (interface)
• IndexedDbEventRepository (Dexie/IDB)
• GoogleCalendarEventRepository (provider)
• Then a tiny router/chooser:
• AuthAwareEventRepository (delegates to local vs provider based on auth state)

@tyler-dane
Copy link
Contributor

Closing in favor of #1404

@tyler-dane tyler-dane closed this Jan 5, 2026
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