18 Commits
1.11.0 ... main

Author SHA1 Message Date
MartinBraquet
09736cd49b Display toast notifications for push messages with navigation fixes 2026-04-03 18:58:36 +02:00
MartinBraquet
f16bef97dc Update styles in SkipLink component to use bg-canvas-100 instead of bg-primary-500 2026-04-03 18:56:32 +02:00
MartinBraquet
92d4222f96 Add debug key 2026-04-03 18:28:19 +02:00
MartinBraquet
008110b015 Refactor Sentry tags for Android app info 2026-04-03 17:25:26 +02:00
MartinBraquet
29ace2d2e5 Release 2026-04-03 13:54:45 +02:00
MartinBraquet
07f927d738 Merge remote-tracking branch 'origin/main' 2026-04-03 13:53:56 +02:00
MartinBraquet
993117ba72 Integrate Sentry for Android app version and build tracking 2026-04-03 13:53:43 +02:00
Martin Braquet
c8801a0235 Enhance CodeRabbit configuration settings
Updated CodeRabbit configuration with new settings for reviews, chat auto-reply, and pre-merge checks.
2026-04-02 19:09:13 +02:00
Martin Braquet
93cd105871 Add .coderabbitignore to exclude specific files 2026-04-02 19:02:54 +02:00
Martin Braquet
4f98d99dd9 Update .coderabbit.yaml configuration
Removed auto-generated PR summary settings and path filters.
2026-04-02 19:02:40 +02:00
Martin Braquet
b46d39d9b7 Enable automatic reviews in CodeRabbit configuration 2026-04-02 18:53:34 +02:00
Martin Braquet
5ea095662b Add CodeRabbit configuration for PR reviews
This configuration file sets up CodeRabbit's behavior for PR summaries and reviews, including instructions for summarizing changes, review profiles, and file exclusions.
2026-04-02 18:53:00 +02:00
MartinBraquet
2400d50247 Add www.compassmeet.com to allowed remotePatterns in next.config.ts 2026-04-02 15:50:30 +02:00
MartinBraquet
8ffd69ff15 Bump Android version code to 81 2026-04-02 15:48:52 +02:00
MartinBraquet
0b721ec7b9 Update alt text in PhotosModal and add WEB_URL clarification comment 2026-04-02 15:48:26 +02:00
MartinBraquet
2019c835a0 Release 2026-04-02 15:21:21 +02:00
MartinBraquet
ff23a8c1bc Wrap useProfile with ProfileProvider and refactor to use React Context 2026-04-02 15:00:48 +02:00
MartinBraquet
df775e9aa3 Improve error handling in run by preserving additional error details 2026-04-02 14:07:02 +02:00
13 changed files with 193 additions and 35 deletions

102
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,102 @@
# Enables IDE autocompletion for this config file
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
# Language for CodeRabbit's review comments
language: en
# Enable experimental features (currently not using any specific early_access features)
early_access: true
chat:
# CodeRabbit will automatically respond to @coderabbitai mentions in PR comments
auto_reply: true
reviews:
auto_review:
# Automatically trigger reviews when PRs are opened or updated
enabled: true
# Skip auto-review if PR title contains these keywords
ignore_title_keywords:
- "WIP"
# Don't auto-review draft PRs
drafts: false
# Only auto-review PRs targeting these branches
base_branches:
- main
- develop
# Include a high-level summary at the start of each review
high_level_summary: true
# Generate sequence diagrams for complex code flows
sequence_diagrams: true
# Don't include poems in reviews (fun feature, but keeping it professional)
poem: false
# Show review completion status
review_status: true
# Keep the walkthrough section expanded by default
collapse_walkthrough: false
# Include summary of all changed files
changed_files_summary: true
# Don't automatically request changes on the PR (just leave comments)
request_changes_workflow: false
# Pre-merge checks to enforce before merging PRs
pre_merge_checks:
description:
# Validate that PR has a proper description
mode: warning # Options: off, warning, error
docstrings:
# Disable docstring coverage checks (let's assume we don't need them)
mode: off
# Exclude these paths from reviews (build artifacts and dependencies)
path_filters:
- "!**/node_modules/**" # npm dependencies
- "!**/android/**" # Native Android build files
- "!**/ios/**" # Native iOS build files
- "!**/.expo/**" # Expo build cache
- "!**/.expo-shared/**" # Expo shared config
- "!**/dist/**" # Build output
# Custom review instructions for specific file patterns
path_instructions:
# TypeScript/JavaScript files - main app code
- path: "**/*.{ts,tsx,js,jsx}"
instructions: |
General practices:
- Summarize the changes clearly.
- Format the summary with bullet points.
- Highlight any potential breaking changes for users.
- We use early returns to avoid deep nesting.
- Ensure all public functions have docstrings.
- Flag any hardcoded strings; they should be in the constants file.
- Check for edge cases like null values or empty arrays.
- Suggest performance optimizations where appropriate.
Mobile best practices:
- Proper use of hooks (useRouter, useFonts, useAssets)
- Accessibility: touch targets min 44x44, screen reader support
- Safe area handling and platform-specific code (iOS vs Android)
- Memory leaks in useEffect and event listeners
Performance:
- Use FlatList/SectionList for lists (never ScrollView with .map)
- React.memo, useMemo, useCallback where appropriate
TypeScript:
- Avoid 'any', use explicit types
- Prefer 'import type' for type imports
Security:
- No exposed API keys or sensitive data
- Use expo-secure-store for sensitive storage
- Validate deep linking configurations
Internationalization:
- User-visible strings should be externalized to resource files (useT())

5
.coderabbitignore Normal file
View File

@@ -0,0 +1,5 @@
**/*.md
package-lock.json
yarn.lock
dist/**
test/mocks/**

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.compassconnections.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 79
versionName "1.16.0"
versionCode 84
versionName "1.17.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View File

@@ -238,7 +238,16 @@ public class MainActivity extends BridgeActivity implements ModifiedMainActivity
checkForUpdates();
Uri data = getIntent().getData();
if (data != null) pendingDeepLink = data.toString();
if (data != null) {
pendingDeepLink = data.toString();
} else {
// Check for notification endpoint when app is opened from cold start via notification click
String endpoint = getIntent().getStringExtra("endpoint");
if (endpoint != null) {
Log.i("CompassApp", "onCreate found endpoint from notification: " + endpoint);
pendingDeepLink = endpoint;
}
}
}
private void handleDeepLink(String url) {

View File

@@ -55,7 +55,11 @@ export const LOCAL_BACKEND_DOMAIN = `${IS_WEBVIEW_DEV_PHONE ? '192.168.1.3' : IS
export const DOMAIN = IS_LOCAL ? LOCAL_WEB_DOMAIN : ENV_CONFIG.domain
export const DEPLOYED_WEB_URL = `https://www.${ENV_CONFIG.domain}`
export const WEB_URL = IS_LOCAL ? `http://${LOCAL_WEB_DOMAIN}` : `https://www.${DOMAIN}`
// Careful: buildOgUrl uses WEB_URL and works only on https://www.compassmeet.com (not https://compassmeet.com)
// x-vercel-error: INVALID_IMAGE_OPTIMIZE_REQUEST. Could work if needed though with some Vercel tweak
export const WEB_URL = IS_LOCAL ? `http://${LOCAL_WEB_DOMAIN}` : `https://${DOMAIN}`
export const BACKEND_DOMAIN = IS_LOCAL ? LOCAL_BACKEND_DOMAIN : ENV_CONFIG.backendDomain
export const FIREBASE_CONFIG = ENV_CONFIG.firebaseConfig
export const PROJECT_ID = ENV_CONFIG.firebaseConfig.projectId

View File

@@ -66,7 +66,9 @@ export async function run<T>(
export async function run<T>(q: PromiseLike<PostgrestSingleResponse<T> | PostgrestResponse<T>>) {
const {data, count, error} = await q
if (error != null) {
throw error
const err = new Error(error.message)
Object.assign(err, error) // copies code, details, hint onto the Error
throw err
} else {
return {data, count}
}

View File

@@ -85,7 +85,7 @@ export const ViewProfileCardButton = (props: {
src={src}
width={width}
height={height}
alt={t('profile_card.loading', 'Loading your card, it may take a few seconds...')}
alt={t('profile_card.loading', `${user.username}'s profile card`)}
className={'rounded-2xl'}
/>
<ShareProfileButtons

View File

@@ -3,13 +3,13 @@ export function SkipLink() {
<>
<a
href="#main-content"
className="absolute -top-10 left-4 z-50 bg-primary-500 px-4 py-2 text-white transition-all focus:top-4"
className="absolute -top-10 left-4 z-50 bg-canvas-100 px-4 py-2 transition-all focus:top-4"
>
Skip to main content
</a>
<a
href="#main-navigation"
className="absolute -top-10 left-4 z-50 bg-primary-500 px-4 py-2 text-white transition-all focus:top-4"
className="absolute -top-10 left-4 z-50 bg-canvas-100 px-4 py-2 transition-all focus:top-4"
>
Skip to navigation
</a>

View File

@@ -6,13 +6,15 @@ import {
} from 'common/profiles/profile'
import {Row} from 'common/supabase/utils'
import {User} from 'common/user'
import {useEffect} from 'react'
import {createContext, ReactNode, useContext, useEffect} from 'react'
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state'
import {useUser} from 'web/hooks/use-user'
import {db} from 'web/lib/supabase/db'
export const useProfile = () => {
type OwnProfile = (Row<'profiles'> & {user: User}) | null | undefined
const useOwnProfile = (): OwnProfile => {
const user = useUser()
const [profile, setProfile] = usePersistentLocalState<Row<'profiles'> | undefined | null>(
undefined,
@@ -20,13 +22,11 @@ export const useProfile = () => {
)
const refreshProfile = () => {
if (user) {
// logger.debug('Refreshing profile in useProfile for', user?.username, profile);
getProfileRowWithFrontendSupabase(user.id, db).then((profile) => {
if (!profile) setProfile(null)
else setProfile(profile)
})
}
if (!user?.id) return
debug('Refreshing own profile for', user.username)
getProfileRowWithFrontendSupabase(user.id, db).then((p) => {
setProfile(p ?? null)
})
}
useEffect(() => {
@@ -36,6 +36,21 @@ export const useProfile = () => {
return user && profile ? {...profile, user} : profile === null ? null : undefined
}
const MISSING = Symbol('missing')
const ProfileContext = createContext<OwnProfile | typeof MISSING>(MISSING)
export const useProfile = () => {
const ctx = useContext(ProfileContext)
if (ctx === MISSING) throw new Error('useProfile must be used within a ProfileProvider')
return ctx as OwnProfile
}
export const ProfileProvider = ({children}: {children: ReactNode}) => {
const profile = useOwnProfile()
return <ProfileContext.Provider value={profile}>{children}</ProfileContext.Provider>
}
export const useProfileByUser = (user: User | undefined) => {
const userId = user?.id
const [profile, setProfile] = usePersistentInMemoryState<Profile | undefined | null>(

View File

@@ -1,7 +1,7 @@
import {PushNotifications} from '@capacitor/push-notifications'
import {debug} from 'common/logger'
import {useRouter} from 'next/router'
import {useEffect} from 'react'
import toast from 'react-hot-toast'
import {useUser} from 'web/hooks/use-user'
import {api} from 'web/lib/api'
import {isAndroidApp} from 'web/lib/util/webview'
@@ -9,7 +9,6 @@ import {isAndroidApp} from 'web/lib/util/webview'
export default function AndroidPush() {
const user = useUser() // authenticated user
const isAndroid = isAndroidApp()
const router = useRouter()
useEffect(() => {
if (!user?.id || !isAndroid) return
debug('AndroidPush', user)
@@ -36,12 +35,14 @@ export default function AndroidPush() {
})
PushNotifications.addListener('pushNotificationReceived', (notif) => {
debug('Push received', notif)
const url = notif?.data?.url
if (url) {
router.push(url)
window.location.href = url
}
console.debug('Push received', notif, window.location.pathname)
const endpoint = notif?.data?.endpoint as string
if (!endpoint) return
if (!endpoint.startsWith('/messages/')) return
if (endpoint === window.location.pathname) return
const author = notif?.title
const message = notif?.body
toast.success(`${author}: "${message}"`)
})
}, [user?.id, isAndroid])

View File

@@ -51,6 +51,7 @@ const nextConfig: NextConfig = {
remotePatterns: [
{hostname: 'martinbraquet.com'},
{hostname: 'compassmeet.com'},
{hostname: 'www.compassmeet.com'},
{hostname: 'lh3.googleusercontent.com'},
{hostname: 'i.imgur.com'},
{hostname: 'firebasestorage.googleapis.com'},

View File

@@ -5,10 +5,12 @@ import {App} from '@capacitor/app'
import {Capacitor} from '@capacitor/core'
import {Keyboard} from '@capacitor/keyboard'
import {StatusBar} from '@capacitor/status-bar'
import * as Sentry from '@sentry/node'
import clsx from 'clsx'
import {DEPLOYED_WEB_URL} from 'common/envs/constants'
import {IS_VERCEL, PNG_FAVICON} from 'common/hosting/constants'
import {debug} from 'common/logger'
import {isUrl} from 'common/parsing'
import type {AppProps} from 'next/app'
import {Major_Mono_Display} from 'next/font/google'
import Head from 'next/head'
@@ -25,6 +27,7 @@ import {useFontPreferenceManager} from 'web/hooks/use-font-preference'
import {useHasLoaded} from 'web/hooks/use-has-loaded'
import {HiddenProfilesProvider} from 'web/hooks/use-hidden-profiles'
import {PinnedQuestionIdsProvider} from 'web/hooks/use-pinned-question-ids'
import {ProfileProvider} from 'web/hooks/use-profile'
import {updateStatusBar} from 'web/hooks/use-theme'
import {updateBackendLocale} from 'web/lib/api'
import {DAYJS_LOCALE_IMPORTS, registerDatePickerLocale} from 'web/lib/dayjs'
@@ -162,10 +165,23 @@ function MyApp(props: AppProps<PageProps>) {
const link = window.AndroidBridge?.getPendingDeepLink?.()
if (link) {
handleAppLink({url: link, endpoint: new URL(link).pathname})
handleAppLink({endpoint: isUrl(link) ? new URL(link).pathname : link})
}
}, [])
useEffect(() => {
const fetchAppInfo = async () => {
if (!Capacitor.isNativePlatform()) return
const appInfo = await App.getInfo().catch((e) => debug('Could not load Android app info:', e))
const appVersion = appInfo?.version
if (appVersion) {
Sentry.setTag('androidApp.version', appVersion)
Sentry.setTag('androidApp.buildNumber', appInfo?.build)
}
}
fetchAppInfo()
}, [])
const title = 'Compass'
const description = 'The platform for intentional connections'
@@ -208,15 +224,17 @@ function MyApp(props: AppProps<PageProps>) {
<I18nContext.Provider value={{locale, setLocale}}>
<ErrorBoundary>
<AuthProvider serverUser={pageProps.auth}>
<ChoicesProvider>
<PinnedQuestionIdsProvider>
<HiddenProfilesProvider>
<WebPush />
<AndroidPush />
<Component {...pageProps} />
</HiddenProfilesProvider>
</PinnedQuestionIdsProvider>
</ChoicesProvider>
<ProfileProvider>
<ChoicesProvider>
<PinnedQuestionIdsProvider>
<HiddenProfilesProvider>
<WebPush />
<AndroidPush />
<Component {...pageProps} />
</HiddenProfilesProvider>
</PinnedQuestionIdsProvider>
</ChoicesProvider>
</ProfileProvider>
</AuthProvider>
</ErrorBoundary>
</I18nContext.Provider>

View File

@@ -6,6 +6,7 @@
"package_name": "com.compassconnections.app",
"sha256_cert_fingerprints": [
"29:61:EB:C6:DE:D4:E3:50:FE:8B:56:D2:B5:F1:E7:1D:8D:6F:96:82:26:05:63:5B:44:BB:56:45:B1:D7:6A:EE",
"4B:80:99:49:D0:30:6F:8F:3C:D0:E7:BB:F6:39:CA:7B:99:A3:17:20:96:F9:4E:1F:13:CC:81:90:54:65:51:3B",
"9D:88:96:66:81:E9:C5:82:5E:96:6C:B7:C4:A0:0C:AA:26:1B:0A:04:F8:7D:9D:F2:4D:C4:3E:02:99:A3:42:DF"
]
}