Open Finapp
Development

Troubleshooting

Known gotchas for the local Supabase+PowerSync dev setup and offline-first data layer.

Local Docker / Supabase

Colima: disable Supabase analytics

On Colima (macOS Docker alternative), the Supabase vector (log analytics) container fails to start because it cannot bind-mount the Docker socket. Disable it in app/supabase/config.toml:

app/supabase/config.toml
[analytics]
enabled = false

Without this, supabase start hangs or errors on Colima environments.

postgres:18 image - wrong volume mount path

If you use a postgres:18 container (for example, for the PowerSync bucket-storage database), mount the data volume at /var/lib/postgresql, not /var/lib/postgresql/data:

volumes:
  - pg_storage_data:/var/lib/postgresql

Mounting at /var/lib/postgresql/data causes the container to fail on startup because postgres:18 changed the default data directory.

PowerSync client

No INSERT ... ON CONFLICT on PowerSync tables

PowerSync client tables are SQLite views backed by INSTEAD OF triggers. They accept plain INSERT, UPDATE, and DELETE statements, but not INSERT ... ON CONFLICT (upsert syntax).

Upsert logic must be done manually - check for row existence first, then INSERT or UPDATE:

// from app/services/powersync/mutations.ts
async function upsertRow(table, id, row) {
  await db.writeTransaction(async (tx) => {
    const existing = await tx.getOptional(`SELECT id FROM ${table} WHERE id = ?`, [id])
    if (existing) {
      await tx.execute(`UPDATE ${table} SET ... WHERE id = ?`, [...values, id])
    }
    else {
      await tx.execute(`INSERT INTO ${table} (id, ...) VALUES (?, ...)`, [id, ...values])
    }
  })
}

See app/services/powersync/mutations.ts for the full implementation.

Every synced table must have an id PK

PowerSync requires an id primary key on every synced table. For user_settings, the id column holds the user's Supabase uid. This is how the table satisfies the requirement while having exactly one row per user.

Synchronous auth gate

The route guard in app/app/middleware/auth.global.ts is synchronous - it reads the persisted Supabase session from localStorage (hasPersistedSession(), app/app/composables/useAuthSession.ts) with no network call, so it works offline. There is no auth cookie.

supabase-js persists the session to localStorage itself as part of sign-in, so by the time login code navigates, the gate already passes. The PowerSync plugin separately watches the reactive uid from useSupabaseAuth() and connects/disconnects PowerSync as the session resolves - navigation does not wait for it.

If you move login flow logic around, navigate only after the sign-in promise resolves (session persisted) - otherwise the guard will redirect back to /login.