Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Snapshot

- Offline-first Android password manager built with Expo SDK 57, React Native, TypeScript.
- Offline-first Android password manager built with Expo SDK 58, React Native, TypeScript.
- Security model: KS1 (AES-256-CBC + HMAC-SHA256) with Argon2/PBKDF2 key derivation.
- Android-first, iOS paused.

Expand Down
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ All notable project changes are documented here.

### Release

- Upgrades to Expo SDK 58 and React Native 0.88.1, including AGP 9, the strict TypeScript
API, Expo FileSystem's `File` API for the bundled license asset, and the matching Jest and
React test renderer versions. `bun run verify` passes with 254 tests and Expo Doctor 20/20.
- Removes the non-existent `missing_rules.txt` from `buildArtifactPaths` so EAS uploads
`mapping.txt` for release crash deobfuscation.
- Rebuilt Keysoft 3.3.2 from `main` HEAD `8c893ea` as versionCode 132 with `targetSdkVersion`
36 and submitted it to the Google Play production track; Play review is pending while
3.3.2/131 remains live.
36 and submitted it to the Google Play production track.
- Automates Google Play release submission from the version-tag EAS workflow: the build is
uploaded as a draft, then `scripts/play-release-notes.mjs` adds the Play Store release notes
and promotes it to a completed production release. Play Store notes live under
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ authors:
- family-names: Gasperini
given-names: Michael
url: 'https://github.com/TheStreamCode/keysoft'
version: '3.3.2'
version: '3.3.3'
date-released: "2026-10-23"
license: 'Apache-2.0'
9 changes: 5 additions & 4 deletions app.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = {
name: 'Keysoft',
slug: 'keysoft',
version: '3.3.2',
version: '3.3.3',
scheme: 'keysoft',
orientation: 'default', // Supports both portrait and landscape
userInterfaceStyle: 'automatic',
Expand Down Expand Up @@ -55,7 +55,7 @@ module.exports = {
backgroundColor: '#FFFFFF',
},
package: 'it.mikesoft.keysoft',
versionCode: 131,
versionCode: 135,
permissions: [
'INTERNET',
'USE_BIOMETRIC',
Expand Down Expand Up @@ -110,8 +110,9 @@ module.exports = {
'expo-build-properties',
{
android: {
// Target Android 16 (API 36) stable.
compileSdkVersion: 36,
// Compile against Android 17 (API 37) for the SDK 58/RN 0.88 toolchain;
// target Android 16 (API 36), the current Play requirement.
compileSdkVersion: 37,
targetSdkVersion: 36,
enableMinifyInReleaseBuilds: true,
enableShrinkResourcesInReleaseBuilds: true,
Expand Down
1,660 changes: 1,177 additions & 483 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Keysoft is an offline-first React Native application built with Expo. The archit

| Layer | Technology |
| --------------- | ---------------------------------- |
| App framework | Expo SDK 57 |
| UI runtime | React Native 0.86.2 |
| App framework | Expo SDK 58 |
| UI runtime | React Native 0.88.1 |
| Language | TypeScript, strict mode |
| Navigation | React Navigation v7 |
| Package manager | Bun |
Expand Down
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Use Expo Go for development and smoke testing only. Create release-test vaults i

The `withArgon2ProGuard` config plugin patches the native Argon2 dependency during prebuild and appends ProGuard keep rules for release builds, including `com.poowf.argon2` for `react-native-argon2` v4. When changing the Argon2 dependency or EAS prebuild behavior, verify that the generated Android project still contains those keep rules.

The `withAndroidReleaseOptimization` config plugin switches the generated release build to `proguard-android-optimize.txt` and writes `android.r8.optimizedResourceShrinking=true`. Together with the `expo-build-properties` minify and shrink-resources options, this enables optimized R8 and resource shrinking on the AGP version supported by Expo SDK 57. Its transformations are intentionally fail-fast and must remain covered by the config regression tests.
The `withAndroidReleaseOptimization` config plugin switches the generated release build to `proguard-android-optimize.txt` and writes `android.r8.optimizedResourceShrinking=true`. Together with the `expo-build-properties` minify and shrink-resources options, this enables optimized R8 and resource shrinking on the AGP version supported by Expo SDK 58. Its transformations are intentionally fail-fast and must remain covered by the config regression tests.

Google Play may report edge-to-edge or network-bitmap APIs that are present inside React Native, Fresco, or Glide even when application code does not call them. Verify app behavior and the generated native project before changing dependencies. Keysoft profile images are user-selected local files; do not add a network image library unless a measured application workload requires it.

Expand Down
22 changes: 11 additions & 11 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

## Release Readiness Checklist

Current source release: Keysoft 3.3.2. The local manifest mirrors versionCode 131; the Android
16 compliance rebuild produced EAS remote versionCode 132 from `main` HEAD and is in Play
review. Google Play serves 3.3.2/131 until 3.3.2/132 is approved. EAS remote versioning remains
authoritative for Android builds, and the same workflow now submits the next release to Google
Play automatically. iOS remains a cloud-simulator compatibility target and is not an App Store
Current source release: Keysoft 3.3.3, Android build target versionCode 135. Google Play serves
Keysoft 3.3.2 while Keysoft 3.3.3/135 is in review. EAS remote versioning remains authoritative
for Android builds. This source release upgrades the toolchain to Expo SDK 58, React Native
0.88.1 and AGP 9, and the version-tag workflow now submits the next release to Google Play
automatically. iOS remains a cloud-simulator compatibility target and is not an App Store
release target.

Before preparing a release:
Expand Down Expand Up @@ -42,13 +42,13 @@ Application version data is maintained in:
- `app.config.js`
- Android native configuration where applicable

For the 3.3.2 source release, `app.config.js` uses `version: "3.3.2"`,
`android.versionCode: 131`, and an iOS simulator baseline of `ios.buildNumber: "1"`. The local
For the 3.3.3 source release, `app.config.js` uses `version: "3.3.3"`,
`android.versionCode: 135`, and an iOS simulator baseline of `ios.buildNumber: "1"`. The local
value mirrors the target for manifest visibility but does not control the EAS counter. Because
EAS Update uses the `appVersion` runtime policy, this release uses native runtime 3.3.2. The
Android 16 compliance rebuild produced remote versionCode 132 from `main` HEAD; Google Play
serves 3.3.2/131 until 3.3.2/132 is approved. The production workflow increments the remote
Android value on every build and submits the matching app bundle to Google Play.
EAS Update uses the `appVersion` runtime policy, this release uses native runtime 3.3.3. The
3.3.3 build used remote versionCode 135. Google Play serves 3.3.2 while 3.3.3/135 is in review.
The production workflow increments the remote Android value on every build and submits the
matching app bundle to Google Play.

Read the current remote value with
`bunx eas-cli build:version:get --platform android --profile production --non-interactive`
Expand Down
56 changes: 56 additions & 0 deletions docs/releases/3.3.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Keysoft 3.3.3

Source release: 2026-11-01.

Android production target: versionCode 135.

This release upgrades the runtime toolchain and addresses the release quality findings from
Google Play. It does not change the KS1/KS1-PW1 payload layouts, storage keys, existing vault
KDF parameters, Android permissions or the offline-only operating model.

## Toolchain

- Upgrades Expo SDK 57 to SDK 58 and React Native 0.86 to 0.88.1.
- Aligns Expo modules, React, React DOM, Jest, Jest Expo, React Native Gesture Handler,
Reanimated, Screens, Safe Area Context and Worklets through `expo install --fix`.
- Uses Android Gradle Plugin 9 through the SDK 58 toolchain. This addresses the Play Console
recommendation to upgrade AGP for R8 optimization.
- Migrates the bundled license read from the legacy `readAsStringAsync` call to the Expo
FileSystem `File` API.
- Updates the strict TypeScript API baseline introduced by React Native 0.87/0.88.

## Release quality findings

- The edge-to-edge deprecation warning originates in React Native 0.88 and
`expo-image-picker` internals. The app already suppresses the deprecated status-bar color
and translucency props on Android 15 and later. No app-level workaround is applied because
the remaining calls are dependency code.
- The network-bitmap warning is caused by Fresco in React Native and Glide in
`expo-image-loader`/`expo-image-picker`, not by a remote image in Keysoft. Profile avatars
remain local files.
- The R8 warning is addressed by the AGP 9 toolchain upgrade. The previous build already used
R8 full mode with shrinking, optimization and obfuscation enabled.

## Localization review

- The Italian and English dictionaries contain 664 aligned keys with matching interpolation
placeholders and no empty entries.
- Removed mixed-language UI labels such as `Logout`, `Reset App` and `Privacy Policy`; aligned
clipboard and encryption terminology.
- Added a regression test that rejects English-only values for the main Italian account and
privacy labels.

## Verification

- `bun install`
- `bun run verify`: formatting, strict TypeScript, ESLint, 36 Jest suites / 254 tests, Expo
Doctor 20/20
- `bun x expo export --platform android`: passed; Hermes bundle 5 MB
- Expo web development smoke test: onboarding, PIN creation, password and note creation,
settings, language switching and privacy screen; no runtime errors, only the intentional
unencrypted-mock warning. The production web export remains blocked by the existing
cleartext-mock guard.
- EAS production build verified as versionCode 135, `compileSdkVersion` 37,
`targetSdkVersion` 36, R8 9.2.14 in full mode, with `mapping.txt` uploaded.
- Submitted to the production track as a draft, then promoted to completed with the 3.3.3
Italian and English Play Store notes. Play review is pending.
3 changes: 1 addition & 2 deletions eas.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@
},
"channel": "production",
"buildArtifactPaths": [
"android/app/build/outputs/mapping/release/mapping.txt",
"android/app/build/outputs/mapping/release/missing_rules.txt"
"android/app/build/outputs/mapping/release/mapping.txt"
]
}
},
Expand Down
2 changes: 1 addition & 1 deletion jest.preload.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Preload setup for Jest bootstrap compatibility with Expo SDK 57.
* Preload setup for Jest bootstrap compatibility with Expo SDK 58.
*
* This file runs BEFORE jest-expo's setup.js to install the Expo global polyfill
* and provide a no-op ExpoModulesCoreJSLogger module. This prevents async warnings
Expand Down
80 changes: 40 additions & 40 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"name": "Michael Gasperini",
"url": "https://mikesoft.it"
},
"version": "3.3.2",
"version": "3.3.3",
"description": "Private, offline-first password manager for Android. No accounts, ads, tracking, or cloud vault.",
"keywords": [
"android",
Expand Down Expand Up @@ -42,65 +42,65 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@expo/metro-runtime": "~57.0.17",
"@expo/metro-runtime": "~58.0.16",
"@expo/vector-icons": "^15.1.1",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-community/slider": "5.2.0",
"@react-native-community/slider": "5.2.1",
"@react-navigation/bottom-tabs": "^7.18.16",
"@react-navigation/native": "^7.3.16",
"@react-navigation/native-stack": "^7.18.8",
"@react-navigation/stack": "^7.10.22",
"crypto-js": "^4.2.0",
"expo": "~57.0.31",
"expo-asset": "~57.0.20",
"expo-build-properties": "~57.0.22",
"expo-clipboard": "~57.0.2",
"expo-constants": "~57.0.22",
"expo-crypto": "~57.0.4",
"expo-document-picker": "~57.0.3",
"expo-file-system": "~57.0.7",
"expo-font": "~57.0.4",
"expo-image-picker": "~57.0.20",
"expo-local-authentication": "~57.0.3",
"expo-localization": "~57.0.2",
"expo-navigation-bar": "~57.0.3",
"expo-notifications": "~57.0.24",
"expo-screen-capture": "~57.0.4",
"expo-secure-store": "~57.0.4",
"expo-sharing": "~57.0.24",
"expo-status-bar": "~57.0.1",
"expo-system-ui": "~57.0.4",
"expo-updates": "~57.0.26",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.86.3",
"expo": "~58.0.22",
"expo-asset": "~58.0.19",
"expo-build-properties": "~58.0.12",
"expo-clipboard": "~58.0.4",
"expo-constants": "~58.0.14",
"expo-crypto": "~58.0.7",
"expo-document-picker": "~58.0.4",
"expo-file-system": "~58.0.13",
"expo-font": "~58.0.11",
"expo-image-picker": "~58.0.10",
"expo-local-authentication": "~58.0.2",
"expo-localization": "~58.0.5",
"expo-navigation-bar": "~58.0.4",
"expo-notifications": "~58.1.7",
"expo-screen-capture": "~58.0.6",
"expo-secure-store": "~58.0.2",
"expo-sharing": "~58.0.21",
"expo-status-bar": "~58.0.4",
"expo-system-ui": "~58.0.7",
"expo-updates": "~58.0.20",
"react": "19.3.0",
"react-dom": "19.3.0",
"react-native": "0.88.1",
"react-native-argon2": "^4.0.0",
"react-native-gesture-handler": "~2.32.0",
"react-native-gesture-handler": "~3.3.0",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "~4.26.2",
"react-native-web": "^0.21.2",
"react-native-worklets": "0.10.1",
"react-native-reanimated": "4.7.1",
"react-native-safe-area-context": "~5.10.1",
"react-native-screens": "~4.30.1",
"react-native-web": "^0.21.4",
"react-native-worklets": "0.13.0",
"typescript": "~6.0.3",
"zod": "^4.4.3"
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@babel/plugin-transform-export-namespace-from": "^7.29.7",
"@react-native/jest-preset": "0.86.2",
"@react-native/jest-preset": "~0.88.1",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "29.5.14",
"@types/react": "~19.2.18",
"@types/jest": "30.0.0",
"@types/react": "~19.3.0",
"babel-plugin-module-resolver": "^5.0.3",
"babel-preset-expo": "~57.0.6",
"babel-preset-expo": "~58.0.0",
"eslint": "^9.39.5",
"eslint-config-expo": "~57.0.2",
"eslint-config-expo": "~58.0.4",
"globals": "^16.5.0",
"jest": "^29.7.0",
"jest-expo": "~57.0.6",
"jest": "~30.5.2",
"jest-expo": "~58.0.13",
"prettier": "^3.9.6",
"react-test-renderer": "19.2.3"
"react-test-renderer": "19.3.0"
},
"overrides": {
"shell-quote": "^1.11.0"
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/config/ExpoAndroidConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('Expo Android native configuration', () => {
| undefined;

expect(buildProperties?.[1].android).toEqual({
compileSdkVersion: 36,
compileSdkVersion: 37,
targetSdkVersion: 36,
enableMinifyInReleaseBuilds: true,
enableShrinkResourcesInReleaseBuilds: true,
Expand Down
14 changes: 14 additions & 0 deletions src/__tests__/i18n/Translations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@ describe('translations', () => {
expect(italianKeys).toEqual(englishKeys);
});

it('does not leave English-only UI labels in Italian', () => {
const localizedKeys = [
'logout',
'reset_app',
'privacy_policy',
'privacy_title',
'privacy_section9_method1_title',
];

for (const key of localizedKeys) {
expect(italianEntries.get(key)).not.toBe(englishEntries.get(key));
}
});

it('defines every statically referenced translation key in both languages', () => {
const usedKeys = extractUsedTranslationKeys();

Expand Down
2 changes: 1 addition & 1 deletion src/contexts/LanguageContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const LanguageProvider = ({ children }: { children: React.ReactNode }) =>

const handleAppStateChange = async (nextAppState: AppStateStatus) => {
// Quando l'app torna in foreground dopo essere stata in background
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
if (appState.current?.match(/inactive|background/) && nextAppState === 'active') {
Logger.debug('🌍 App tornata attiva, verifico lingua di sistema...');

const newSysLang = resolveSystemLanguage();
Expand Down
Loading
Loading