diff --git a/.gitignore b/.gitignore
index 78581db..67b89fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,4 +43,8 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/.env
fastlane/*.log
-vendor/bundle
\ No newline at end of file
+vendor/bundle
+
+# Claude Code skills cache
+.agents/
+skills-lock.json
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index 8ca03a3..a123142 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -230,4 +230,4 @@ DEPENDENCIES
fastlane
BUNDLED WITH
- 1.17.2
+ 2.4.10
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4d63416..4a1ffc9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -13,8 +13,8 @@ android {
applicationId "com.donetick.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 37
- versionName "1.2.16"
+ versionCode 49
+ versionName "1.2.28"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 1588034..c1ddb92 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -125,6 +125,12 @@
+
+
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
index 97e6322..57f4134 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -22,6 +22,12 @@ allprojects {
google()
mavenCentral()
}
+
+ configurations.configureEach {
+ // The app does not use Advertising ID. Exclude RevenueCat's transitive
+ // ads identifier dependency so Play Console does not require an AD_ID declaration.
+ exclude group: 'com.google.android.gms', module: 'play-services-ads-identifier'
+ }
}
task clean(type: Delete) {
diff --git a/capacitor.config.ts b/capacitor.config.ts
index a585c19..2cc9037 100644
--- a/capacitor.config.ts
+++ b/capacitor.config.ts
@@ -16,6 +16,22 @@ const config: CapacitorConfig = {
iconColor: '#488AFF',
sound: 'beep.wav',
},
+ CapacitorSQLite: {
+ // The offline cache opens databases with encrypted: false / no-encryption.
+ // @capacitor-community/sqlite defaults native encryption to true when this
+ // block is absent; on Android that can make plugin load fail with
+ // "CapacitorSQLitePlugin: null" before JS gets a chance to open the DB.
+ iosIsEncryption: false,
+ androidIsEncryption: false,
+ },
+ SocialLogin: {
+ providers: {
+ google: true,
+ facebook: false,
+ apple: true,
+ twitter: false,
+ },
+ },
// GoogleAuth: {
// scopes: ['profile', 'email', 'openid'],
// clientId: process.env.VITE_APP_GOOGLE_CLIENT_ID,
diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj
index bedc462..3f0a3f1 100644
--- a/ios/App/App.xcodeproj/project.pbxproj
+++ b/ios/App/App.xcodeproj/project.pbxproj
@@ -462,12 +462,12 @@
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 37;
+ CURRENT_PROJECT_VERSION = 49;
DEVELOPMENT_TEAM = 6UJJ78R3BS;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- MARKETING_VERSION = 1.2.16;
+ MARKETING_VERSION = 1.2.28;
PRODUCT_BUNDLE_IDENTIFIER = com.donetick.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
@@ -485,12 +485,12 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
PROVISIONING_PROFILE_SPECIFIER = "Donetick App Store(fastline)";
- CURRENT_PROJECT_VERSION = 37;
+ CURRENT_PROJECT_VERSION = 49;
DEVELOPMENT_TEAM = 6UJJ78R3BS;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- MARKETING_VERSION = 1.2.16;
+ MARKETING_VERSION = 1.2.28;
PRODUCT_BUNDLE_IDENTIFIER = com.donetick.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -504,12 +504,12 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = DonetickWidget/DonetickWidget.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 37;
+ CURRENT_PROJECT_VERSION = 49;
DEVELOPMENT_TEAM = 6UJJ78R3BS;
INFOPLIST_FILE = DonetickWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
- MARKETING_VERSION = 1.2.16;
+ MARKETING_VERSION = 1.2.28;
PRODUCT_BUNDLE_IDENTIFIER = com.donetick.app.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@@ -527,12 +527,12 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
PROVISIONING_PROFILE_SPECIFIER = "Donetick Widget App Store(fastline)";
- CURRENT_PROJECT_VERSION = 37;
+ CURRENT_PROJECT_VERSION = 49;
DEVELOPMENT_TEAM = 6UJJ78R3BS;
INFOPLIST_FILE = DonetickWidget/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
- MARKETING_VERSION = 1.2.16;
+ MARKETING_VERSION = 1.2.28;
PRODUCT_BUNDLE_IDENTIFIER = com.donetick.app.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
diff --git a/ios/App/Podfile.lock b/ios/App/Podfile.lock
index af25af6..3c20ae8 100644
--- a/ios/App/Podfile.lock
+++ b/ios/App/Podfile.lock
@@ -47,20 +47,10 @@ PODS:
- Capacitor
- CapgoCapacitorNfc (8.2.2):
- Capacitor
- - CapgoCapacitorSocialLogin (8.3.36):
+ - CapgoCapacitorSocialLogin (8.3.38):
- Alamofire (~> 5.10.2)
- Capacitor
- - FBSDKCoreKit (~> 18.0)
- - FBSDKLoginKit (~> 18.0)
- GoogleSignIn (~> 9.0.0)
- - FBAEMKit (18.1.0):
- - FBSDKCoreKit_Basics (= 18.1.0)
- - FBSDKCoreKit (18.1.0):
- - FBAEMKit (= 18.1.0)
- - FBSDKCoreKit_Basics (= 18.1.0)
- - FBSDKCoreKit_Basics (18.1.0)
- - FBSDKLoginKit (18.1.0):
- - FBSDKCoreKit (= 18.1.0)
- FirebaseCore (12.15.0):
- FirebaseCoreInternal (~> 12.15.0)
- GoogleUtilities/Environment (~> 8.1)
@@ -178,10 +168,6 @@ SPEC REPOS:
- Alamofire
- AppAuth
- AppCheckCore
- - FBAEMKit
- - FBSDKCoreKit
- - FBSDKCoreKit_Basics
- - FBSDKLoginKit
- FirebaseCore
- FirebaseCoreInternal
- FirebaseInstallations
@@ -267,11 +253,7 @@ SPEC CHECKSUMS:
CapacitorStatusBar: 01d5763b4ed720de5ce2edbc938de6a98f4c8f32
CapgoCapacitorDocumentScanner: 7ad9e8ed9c054d551bfc948660d995b9545723d8
CapgoCapacitorNfc: 8ea158143c441e1cf6d231a971e375511558d027
- CapgoCapacitorSocialLogin: adffda2bec52a765bdaebf3fa9a6083bd9341d60
- FBAEMKit: 64088ff0380f50e4a56e2ee07d984f7f1aef7595
- FBSDKCoreKit: 8390ea3a8fac186f444275f31d7512e760b47cba
- FBSDKCoreKit_Basics: 3a0005c78b355388bf2df5c6dbe6381b77ea4be3
- FBSDKLoginKit: d8e2711a3a03b0026703735c8d28a2b5a0e1f4fd
+ CapgoCapacitorSocialLogin: 5de0c9295188cbd9116d13e4581494f3bbaa3414
FirebaseCore: 2e86a4ea1684d4381707069e4a6d89ac808e901e
FirebaseCoreInternal: 6ab6a02c94446c026d2cf35cf5383842ebaa4992
FirebaseInstallations: eb29ccbf64eaedf86fd5b2ccc7fabde567660b52
diff --git a/package-lock.json b/package-lock.json
index f496011..65b97fb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "donetick",
- "version": "1.2.16",
+ "version": "1.2.27",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "donetick",
- "version": "1.2.16",
+ "version": "1.2.27",
"hasInstallScript": true,
"dependencies": {
"@capacitor-community/speech-recognition": "^7.0.1",
@@ -26,7 +26,7 @@
"@capacitor/status-bar": "^8.0.0",
"@capgo/capacitor-document-scanner": "^8.4.0",
"@capgo/capacitor-nfc": "^8.0.0",
- "@capgo/capacitor-social-login": "^8.0.0",
+ "@capgo/capacitor-social-login": "^8.3.38",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
@@ -2157,9 +2157,9 @@
}
},
"node_modules/@capgo/capacitor-social-login": {
- "version": "8.3.36",
- "resolved": "https://registry.npmjs.org/@capgo/capacitor-social-login/-/capacitor-social-login-8.3.36.tgz",
- "integrity": "sha512-Y4HYjEJablzbHHwPBrbC2UvHVPsR0bAyCAqBzVBoUEOf0UFHAHyTP3gD9JUTKP/iERU8I+6IA2RWtLqDePCNuA==",
+ "version": "8.3.38",
+ "resolved": "https://registry.npmjs.org/@capgo/capacitor-social-login/-/capacitor-social-login-8.3.38.tgz",
+ "integrity": "sha512-SHrQ9gVJ2oCxf6dQu6Bq4PwdzYIYX7OVuUNyuGPFxLWPR8DCJM50gEhdAmunzEVd1vlty0ShH+84IOU5FEHLPg==",
"license": "MPL-2.0",
"dependencies": {
"tslib": "^2.8.1"
diff --git a/package.json b/package.json
index 4a3d849..97120d7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "donetick",
"private": true,
- "version": "1.2.16",
+ "version": "1.2.28",
"type": "module",
"engines": {
"node": ">=20.0.0",
@@ -56,7 +56,7 @@
"@capacitor/status-bar": "^8.0.0",
"@capgo/capacitor-document-scanner": "^8.4.0",
"@capgo/capacitor-nfc": "^8.0.0",
- "@capgo/capacitor-social-login": "^8.0.0",
+ "@capgo/capacitor-social-login": "^8.3.38",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
diff --git a/src/views/Chores/MyChores.jsx b/src/views/Chores/MyChores.jsx
index 05a1150..98955d5 100644
--- a/src/views/Chores/MyChores.jsx
+++ b/src/views/Chores/MyChores.jsx
@@ -103,7 +103,7 @@ const MyChores = () => {
const searchInputRef = useRef(null)
const [searchInputFocus, setSearchInputFocus] = useState(0)
const [selectedChoreSection, setSelectedChoreSection] = useState(
- localStorage.getItem('selectedChoreSection') || 'due_date',
+ localStorage.getItem('selectedChoreSection') || 'default',
)
const [openChoreSections, setOpenChoreSections] = useState(() => {
try {
diff --git a/src/views/Chores/components/ChoreToolbarPrototype.jsx b/src/views/Chores/components/ChoreToolbarPrototype.jsx
index 9b2c3d1..0c5b5d1 100644
--- a/src/views/Chores/components/ChoreToolbarPrototype.jsx
+++ b/src/views/Chores/components/ChoreToolbarPrototype.jsx
@@ -61,6 +61,7 @@ import FilterBuilderContent, {
} from './FilterBuilderContent'
import SearchBar from './SearchBar'
import ProjectSelector from '../../components/ProjectSelector'
+import CustomFilterChips from './CustomFilterChips'
// ─── sub-components for the Display sheet ────────────────────────────────────
@@ -481,13 +482,6 @@ const ChoreToolbar = ({
// ── display sheet helpers ────────────────────────────────────────────────────
const filterActive = activeFilterId != null || tempConditionCount > 0
- const projectActive = selectedProject && selectedProject.id !== 'default' ? 1 : 0
- const assigneeActive = selectedAssigneeFilter !== 'anyone' ? 1 : 0
- const displayActive =
- selectedGroupBy !== 'default' ||
- viewMode !== 'default' ||
- projectActive > 0 ||
- assigneeActive > 0
const groupByOptions = [
{ value: 'default', label: 'Smart' },
@@ -559,8 +553,8 @@ const ChoreToolbar = ({
{/* Display button — View + Group combined */}
setDisplaySheetOpen(true)}
@@ -599,8 +593,24 @@ const ChoreToolbar = ({
- {/* ── Row 2: active filter chips ──────────────────────────────────────── */}
- {hasAnyActive && (
+ {/* ── Row 2: pinned filters quick access ──────────────────────────────── */}
+ {/* Stays visible even while a saved filter is active (it self-highlights)
+ so switching between pinned filters never requires clearing first.
+ Only steps aside for an ad-hoc/temp condition set, which has no
+ matching pinned identity to show. */}
+ {tempConditionCount === 0 && (
+
+ )}
+
+ {/* ── Row 3: active (temp/ad-hoc) filter chips ──────────────────────────── */}
+ {tempConditionCount > 0 && (
{
- if (a.isPinned && !b.isPinned) return -1
- if (!a.isPinned && b.isPinned) return 1
- return (b.usageCount || 0) - (a.usageCount || 0)
- })
- if (sortedFilters.filter(f => f.isPinned).length === 0) return null
+ const pinnedFilters = filters
+ .filter(f => f.isPinned)
+ .sort((a, b) => (b.usageCount || 0) - (a.usageCount || 0))
+
+ if (pinnedFilters.length === 0) return null
return (
-
- {sortedFilters.map(filter => {
- const isActive = activeFilterId === filter.id
- const hasWarning = !filter.isValid
- const hasCustomColor = !!filter.color && !hasWarning
- const textColor = hasCustomColor
- ? getTextColorFromBackgroundColor(filter.color)
- : undefined
+
+
+ {pinnedFilters.map(filter => {
+ const isActive = activeFilterId === filter.id
+ const hasWarning = !filter.isValid
+ const badgeColor = filter.color || 'var(--joy-palette-neutral-400)'
+ const badgeTextColor = filter.color
+ ? getTextColorFromBackgroundColor(filter.color)
+ : '#ffffff'
+ const displayCount =
+ filter.count > 99 ? '99+' : (filter.count ?? 0)
- return (
- 0 ? ` (${filter.overdueCount} overdue)` : ''}`
- }
- placement='bottom'
- >
- !hasWarning && onFilterClick(filter.id)}>
+ return (
+ 0 ? ` (${filter.overdueCount} overdue)` : ''}`
+ }
+ placement='bottom'
+ >
handleContextMenu(e, filter)}
+ onClick={() => !hasWarning && onFilterClick(filter.id)}
sx={{
cursor: hasWarning ? 'not-allowed' : 'pointer',
- transition: 'all 0.2s ease',
- px: 1.0,
- py: 0.5,
+ transition: 'background-color 0.15s ease, color 0.15s ease',
+ px: 1,
height: 32,
- display: 'flex',
- alignItems: 'center',
-
- opacity: hasWarning ? 0.7 : isActive ? 1 : 0.85,
- ...(hasCustomColor && {
- backgroundColor: `${filter.color} !important`,
- color: `${textColor} !important`,
- '&:hover': {
- backgroundColor: `${filter.color} !important`,
- filter: 'brightness(0.95)',
- opacity: 1,
- },
- }),
+ flexShrink: 0,
+ fontWeight: isActive ? 600 : 500,
+ '&:hover': {
+ backgroundColor: isActive ? undefined : 'neutral.softHoverBg',
+ },
}}
startDecorator={
-
- {isActive ? (
-
- ) : (
-
- {filter.count}
-
- )}
-
+ !hasWarning && (
+
+ {isActive ? (
+
+ ) : (
+
+ {displayCount}
+
+ )}
+
+ )
+ }
+ endDecorator={
+ {
+ e.stopPropagation()
+ handleContextMenu(e, filter)
+ }}
+ sx={{
+ '--IconButton-size': '20px',
+ ml: 0.25,
+ opacity: 0.6,
+ '&:hover': { opacity: 1, backgroundColor: 'transparent' },
+ }}
+ >
+
+
}
- // endDecorator={
- //
- // {hasWarning && }
- // {!hasWarning && filter.overdueCount > 0 && (
- //
- // {filter.overdueCount}
- //
- // )}
- //
- // }
>
{filter.name}
-
-
- )
- })}
+
+ )
+ })}
- {
+ e.preventDefault()
+ e.stopPropagation()
+ navigate('/filters')
+ }}
+ >
+
+
+
+
+ {/* Fade hint that more chips are scrollable off the trailing edge */}
+ {
- e.preventDefault()
- e.stopPropagation()
- navigate('/filters')
- }}
- >
-
-
+ />