Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
23b0498
chore(crypto): patch react-native-libsodium
leeroybrun Jan 17, 2026
af1411a
test(deps): add @types/react-test-renderer
leeroybrun Jan 17, 2026
f5a4727
chore(config): harden app variant defaults
leeroybrun Jan 17, 2026
a2c26be
fix(auth): harden tokenStorage web persistence
leeroybrun Jan 17, 2026
438b221
feat(sync): add permission mode types and mapping
leeroybrun Jan 17, 2026
8d235e7
feat(persistence): scope storage and validate draft modes
leeroybrun Jan 17, 2026
fabb336
fix(session): clamp configurable model modes
leeroybrun Jan 17, 2026
b7b412a
fix(tools): harden ACP tool parsing and titles
leeroybrun Jan 17, 2026
8d1d69f
refactor(sync): centralize outgoing message metadata
leeroybrun Jan 17, 2026
bb67dfe
feat(storage): persist session model modes
leeroybrun Jan 17, 2026
9fc5713
fix(settings): make parsing tolerant for profiles
leeroybrun Jan 17, 2026
74f4860
fix(dev): gate CLI detection logging
leeroybrun Jan 17, 2026
4866acd
fix(command-palette): include navigate dependency
leeroybrun Jan 17, 2026
87bcfc1
feat(env): add env var template parsing
leeroybrun Jan 17, 2026
89d6cf5
fix(env): improve remote env resolution and previews
leeroybrun Jan 17, 2026
d9b6089
feat(env): update env var list, cards, and preview modal
leeroybrun Jan 17, 2026
c311c2b
refactor(i18n): separate translation types and content
leeroybrun Jan 17, 2026
16dfe95
fix(new-session): restore standard modal flow
leeroybrun Jan 17, 2026
243c530
fix(profiles): harden routing, grouping, and editing
leeroybrun Jan 17, 2026
5cbf1f1
refactor(ui): unify list selectors and modal primitives
leeroybrun Jan 17, 2026
9e5a551
feat(cli-detection): add daemon detect-cli RPC support
leeroybrun Jan 17, 2026
10f06df
fix(agent-input): use compact permission badges
leeroybrun Jan 17, 2026
04495c5
chore(test): define __DEV__ for vitest
leeroybrun Jan 18, 2026
e2bba66
feat(settings): add api keys and experiment toggles
leeroybrun Jan 18, 2026
3620ec5
feat(api-keys): add saved API keys UI
leeroybrun Jan 18, 2026
55bbe91
feat(machine): surface detected CLI status
leeroybrun Jan 18, 2026
7453089
feat(profiles): add API key requirements flow
leeroybrun Jan 18, 2026
e30e6bb
refactor(i18n): update translations and tooling
leeroybrun Jan 18, 2026
b71b975
fix(autocomplete): remove debug suggestion logs
leeroybrun Jan 18, 2026
1bd2422
feat(settings): expose per-experiment toggles
leeroybrun Jan 18, 2026
257fed3
feat(agent-input): add configurable action bar layout
leeroybrun Jan 18, 2026
d3b5b51
fix(modal): prevent stacked modal touch-blocking on iOS
leeroybrun Jan 18, 2026
7385301
feat(new-session): add api key selection and wizard extraction
leeroybrun Jan 18, 2026
b99d746
perf(sync): debounce pending settings writes
leeroybrun Jan 18, 2026
4ac60fc
fix(ui): localize error alerts
leeroybrun Jan 18, 2026
3a2d439
refactor(i18n): replace remaining UI literals
leeroybrun Jan 18, 2026
fff72b4
refactor(ui): improve item rendering and action menu timing
leeroybrun Jan 18, 2026
cc6c0af
feat(experiments): gate Zen, file viewer, and voice auth flow
leeroybrun Jan 18, 2026
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
70 changes: 35 additions & 35 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,42 +23,42 @@ This allows you to test production-like builds with real users before releasing

```bash
# Development variant (default)
npm run ios:dev
yarn ios:dev

# Preview variant
npm run ios:preview
yarn ios:preview

# Production variant
npm run ios:production
yarn ios:production
```

### Android Development

```bash
# Development variant
npm run android:dev
yarn android:dev

# Preview variant
npm run android:preview
yarn android:preview

# Production variant
npm run android:production
yarn android:production
```

### macOS Desktop (Tauri)

```bash
# Development variant - run with hot reload
npm run tauri:dev
yarn tauri:dev

# Build development variant
npm run tauri:build:dev
yarn tauri:build:dev

# Build preview variant
npm run tauri:build:preview
yarn tauri:build:preview

# Build production variant
npm run tauri:build:production
yarn tauri:build:production
```

**How Tauri Variants Work:**
Expand All @@ -71,13 +71,13 @@ npm run tauri:build:production

```bash
# Start dev server for development variant
npm run start:dev
yarn start:dev

# Start dev server for preview variant
npm run start:preview
yarn start:preview

# Start dev server for production variant
npm run start:production
yarn start:production
```

## Visual Differences
Expand All @@ -95,7 +95,7 @@ This makes it easy to distinguish which version you're testing!

1. **Build development variant:**
```bash
npm run ios:dev
yarn ios:dev
```

2. **Make your changes** to the code
Expand All @@ -104,19 +104,19 @@ This makes it easy to distinguish which version you're testing!

4. **Rebuild if needed** for native changes:
```bash
npm run ios:dev
yarn ios:dev
```

### Testing Preview (Pre-Release)

1. **Build preview variant:**
```bash
npm run ios:preview
yarn ios:preview
```

2. **Test OTA updates:**
```bash
npm run ota # Publishes to preview branch
yarn ota # Publishes to preview branch
```

3. **Verify** the preview build works as expected
Expand All @@ -125,17 +125,17 @@ This makes it easy to distinguish which version you're testing!

1. **Build production variant:**
```bash
npm run ios:production
yarn ios:production
```

2. **Submit to App Store:**
```bash
npm run submit
yarn submit
```

3. **Deploy OTA updates:**
```bash
npm run ota:production
yarn ota:production
```

## All Variants Simultaneously
Expand All @@ -144,9 +144,9 @@ You can install all three variants on the same device:

```bash
# Build all three variants
npm run ios:dev
npm run ios:preview
npm run ios:production
yarn ios:dev
yarn ios:preview
yarn ios:production
```

All three apps appear on your device with different icons and names!
Expand Down Expand Up @@ -195,12 +195,12 @@ You can connect different variants to different Happy CLI instances:

```bash
# Development app → Dev CLI daemon
npm run android:dev
# Connect to CLI running: npm run dev:daemon:start
yarn android:dev
# Connect to CLI running: yarn dev:daemon:start

# Production app → Stable CLI daemon
npm run android:production
# Connect to CLI running: npm run stable:daemon:start
yarn android:production
# Connect to CLI running: yarn stable:daemon:start
```

Each app maintains separate authentication and sessions!
Expand All @@ -210,7 +210,7 @@ Each app maintains separate authentication and sessions!
To test with a local Happy server:

```bash
npm run start:local-server
yarn start:local-server
```

This sets:
Expand All @@ -227,21 +227,21 @@ This shouldn't happen - each variant has a unique bundle ID. If it does:
1. Check `app.config.js` - verify `bundleId` is set correctly for the variant
2. Clean build:
```bash
npm run prebuild
npm run ios:dev # or whichever variant
yarn prebuild
yarn ios:dev # or whichever variant
```

### App not updating after changes

1. **For JS changes**: Hot reload should work automatically
2. **For native changes**: Rebuild the variant:
```bash
npm run ios:dev # Force rebuild
yarn ios:dev # Force rebuild
```
3. **For config changes**: Clean and prebuild:
```bash
npm run prebuild
npm run ios:dev
yarn prebuild
yarn ios:dev
```

### All three apps look the same
Expand All @@ -258,7 +258,7 @@ If they're all the same name, the variant might not be set correctly. Verify:
echo $APP_ENV

# Or look at the build output
npm run ios:dev # Should show "Happy (dev)" as the name
yarn ios:dev # Should show "Happy (dev)" as the name
```

### Connected device not found
Expand All @@ -270,7 +270,7 @@ For iOS connected device testing:
xcrun devicectl list devices

# Run on specific connected device
npm run ios:connected-device
yarn ios:connected-device
```

## Tips
Expand Down
27 changes: 21 additions & 6 deletions app.config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
const variant = process.env.APP_ENV || 'development';
const name = {

// Allow opt-in overrides for local dev tooling without changing upstream defaults.
const nameOverride = (process.env.EXPO_APP_NAME || '').trim();
const bundleIdOverride = (process.env.EXPO_APP_BUNDLE_ID || '').trim();

const namesByVariant = {
development: "Happy (dev)",
preview: "Happy (preview)",
production: "Happy"
}[variant];
const bundleId = {
};
const bundleIdsByVariant = {
development: "com.slopus.happy.dev",
preview: "com.slopus.happy.preview",
production: "com.ex3ndr.happy"
}[variant];
};

// If APP_ENV is unknown, fall back to development-safe defaults to avoid generating
// an invalid Expo config with undefined name/bundle id.
const name = nameOverride || namesByVariant[variant] || namesByVariant.development;
const bundleId = bundleIdOverride || bundleIdsByVariant[variant] || bundleIdsByVariant.development;
// NOTE:
// The URL scheme is used for deep linking *and* by the Expo development client launcher flow.
// Keep the default stable for upstream users, but allow opt-in overrides for local dev variants
// (e.g. to avoid iOS scheme collisions between multiple installs).
const scheme = (process.env.EXPO_APP_SCHEME || '').trim() || "happy";

export default {
expo: {
Expand All @@ -18,7 +33,7 @@ export default {
runtimeVersion: "18",
orientation: "default",
icon: "./sources/assets/images/icon.png",
scheme: "happy",
scheme,
userInterfaceStyle: "automatic",
newArchEnabled: true,
notification: {
Expand Down Expand Up @@ -174,4 +189,4 @@ export default {
},
owner: "bulkacorp"
}
};
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
"@material/material-color-utilities": "^0.3.0",
"@stablelib/hex": "^2.0.1",
"@types/react": "~19.1.10",
"@types/react-test-renderer": "^19.1.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"cross-env": "^10.1.0",
"patch-package": "^8.0.0",
Expand Down
20 changes: 20 additions & 0 deletions patches/@more-tech+react-native-libsodium+1.5.5.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/node_modules/@more-tech/react-native-libsodium/react-native-libsodium.podspec b/node_modules/@more-tech/react-native-libsodium/react-native-libsodium.podspec
index 5dbd9f1..bc3da26 100644
--- a/node_modules/@more-tech/react-native-libsodium/react-native-libsodium.podspec
+++ b/node_modules/@more-tech/react-native-libsodium/react-native-libsodium.podspec
@@ -30,7 +30,14 @@ Pod::Spec.new do |s|
}
s.dependency "React-Codegen"
if ENV['RCT_USE_RN_DEP'] != '1'
- s.dependency 'RCT-Folly', folly_version
+ # `folly_version` is not always defined during podspec evaluation
+ # (e.g. Expo/RN >= 0.81), so fall back to an unpinned dependency.
+ folly_ver = defined?(folly_version) ? folly_version : nil
+ if folly_ver
+ s.dependency 'RCT-Folly', folly_ver
+ else
+ s.dependency 'RCT-Folly'
+ end
end
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
28 changes: 22 additions & 6 deletions sources/-session/SessionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { isRunningOnMac } from '@/utils/platform';
import { useDeviceType, useHeaderHeight, useIsLandscape, useIsTablet } from '@/utils/responsive';
import { formatPathRelativeToHome, getSessionAvatarId, getSessionName, useSessionStatus } from '@/utils/sessionUtils';
import { isVersionSupported, MINIMUM_CLI_VERSION } from '@/utils/versionUtils';
import type { ModelMode } from '@/sync/permissionTypes';
import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
import * as React from 'react';
Expand Down Expand Up @@ -175,6 +176,7 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session:
const sessionUsage = useSessionUsage(sessionId);
const alwaysShowContextSize = useSetting('alwaysShowContextSize');
const experiments = useSetting('experiments');
const expFileViewer = useSetting('expFileViewer');

// Use draft hook for auto-saving message drafts
const { clearDraft } = useDraft(sessionId, message, setMessage);
Expand All @@ -196,10 +198,24 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session:
storage.getState().updateSessionPermissionMode(sessionId, mode);
}, [sessionId]);

const CONFIGURABLE_MODEL_MODES = [
'default',
'gemini-2.5-pro',
'gemini-2.5-flash',
'gemini-2.5-flash-lite',
] as const;
type ConfigurableModelMode = (typeof CONFIGURABLE_MODEL_MODES)[number];
const isConfigurableModelMode = React.useCallback((mode: ModelMode): mode is ConfigurableModelMode => {
return (CONFIGURABLE_MODEL_MODES as readonly string[]).includes(mode);
}, []);

// Function to update model mode (for Gemini sessions)
const updateModelMode = React.useCallback((mode: 'default' | 'gemini-2.5-pro' | 'gemini-2.5-flash' | 'gemini-2.5-flash-lite') => {
storage.getState().updateSessionModelMode(sessionId, mode);
}, [sessionId]);
const updateModelMode = React.useCallback((mode: ModelMode) => {
// Only Gemini model modes are configurable from the UI today.
if (isConfigurableModelMode(mode)) {
storage.getState().updateSessionModelMode(sessionId, mode);
}
}, [isConfigurableModelMode, sessionId]);

// Memoize header-dependent styles to prevent re-renders
const headerDependentStyles = React.useMemo(() => ({
Expand Down Expand Up @@ -280,8 +296,8 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session:
sessionId={sessionId}
permissionMode={permissionMode}
onPermissionModeChange={updatePermissionMode}
modelMode={modelMode as any}
onModelModeChange={updateModelMode as any}
modelMode={modelMode}
onModelModeChange={updateModelMode}
metadata={session.metadata}
connectionStatus={{
text: sessionStatus.statusText,
Expand All @@ -301,7 +317,7 @@ function SessionViewLoaded({ sessionId, session }: { sessionId: string, session:
isMicActive={micButtonState.isMicActive}
onAbort={() => sessionAbort(sessionId)}
showAbortButton={sessionStatus.state === 'thinking' || sessionStatus.state === 'waiting'}
onFileViewerPress={experiments ? () => router.push(`/session/${sessionId}/files`) : undefined}
onFileViewerPress={(experiments && expFileViewer) ? () => router.push(`/session/${sessionId}/files`) : undefined}
// Autocomplete configuration
autocompletePrefixes={['@', '/']}
autocompleteSuggestions={(query) => getSuggestions(sessionId, query)}
Expand Down
5 changes: 3 additions & 2 deletions sources/-zen/ZenAdd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Typography } from '@/constants/Typography';
import { addTodo } from '@/-zen/model/ops';
import { useAuth } from '@/auth/AuthContext';
import { t } from '@/text';

export const ZenAdd = React.memo(() => {
const router = useRouter();
Expand Down Expand Up @@ -38,7 +39,7 @@ export const ZenAdd = React.memo(() => {
borderBottomColor: theme.colors.divider,
}
]}
placeholder="What needs to be done?"
placeholder={t('zen.add.placeholder')}
placeholderTextColor={theme.colors.textSecondary}
value={text}
onChangeText={setText}
Expand Down Expand Up @@ -71,4 +72,4 @@ const styles = StyleSheet.create((theme) => ({
paddingHorizontal: 4,
...Typography.default(),
},
}));
}));
5 changes: 3 additions & 2 deletions sources/-zen/ZenHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { toggleTodo as toggleTodoSync, reorderTodos as reorderTodosSync } from '
import { useAuth } from '@/auth/AuthContext';
import { useShallow } from 'zustand/react/shallow';
import { VoiceAssistantStatusBar } from '@/components/VoiceAssistantStatusBar';
import { t } from '@/text';

export const ZenHome = () => {
const insets = useSafeAreaInsets();
Expand Down Expand Up @@ -103,7 +104,7 @@ export const ZenHome = () => {
{undoneTodos.length === 0 ? (
<View style={{ padding: 20, alignItems: 'center' }}>
<Text style={{ color: theme.colors.textSecondary, fontSize: 16 }}>
No tasks yet. Tap + to add one.
{t('zen.home.noTasksYet')}
</Text>
</View>
) : (
Expand All @@ -114,4 +115,4 @@ export const ZenHome = () => {
</ScrollView>
</>
);
};
};
Loading