Skip to content
Merged

Dev #52

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
6 changes: 3 additions & 3 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
"expo": {
"name": "DearDiary",
"slug": "dear-diary",
"version": "1.0.3",
"version": "1.0.4",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "deardiary",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"buildNumber": "3",
"buildNumber": "4",
"supportsTablet": true
},
"android": {
Expand All @@ -21,7 +21,7 @@
],
"icon": "./assets/images/icon.png",
"package": "com.aryan.deardiary",
"versionCode": 3,
"versionCode": 4,
"adaptiveIcon": {
"backgroundColor": "#FFDDE8",
"foregroundImage": "./assets/images/icon.png"
Expand Down
2 changes: 2 additions & 0 deletions app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import "../global.css";

import { ClerkProvider, useAuth } from "@clerk/expo";
import { resourceCache } from "@clerk/expo/resource-cache";
Comment thread
aryansoni-dev marked this conversation as resolved.
import { tokenCache } from "@clerk/expo/token-cache";
import { Stack, type ErrorBoundaryProps } from "expo-router";
import { useEffect } from "react";
Expand Down Expand Up @@ -30,6 +31,7 @@ export default function RootLayout() {
return (
<ClerkProvider
publishableKey={publicEnvironmentResult.environment.clerkPublishableKey}
__experimental_resourceCache={resourceCache}
tokenCache={tokenCache}
>
<ConnectivityProvider>
Expand Down
35 changes: 33 additions & 2 deletions components/auth/auth-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import {
View,
} from "react-native";

import { OfflineNotice } from "@/components/connectivity/OfflineNotice";
import { images } from "@/constants/images";
import { useAppDialog } from "@/hooks/useAppDialog";
import { useConnectivity } from "@/hooks/useConnectivity";
import { useOnboardingStore } from "@/store/onboarding-store";

import { AuthTextField } from "./auth-text-field";
Expand Down Expand Up @@ -92,10 +94,15 @@ export function AuthScreen({
const { fetchStatus: signUpFetchStatus, signUp } = useSignUp();
const { startSSOFlow } = useSSO();
const { showDialog } = useAppDialog();
const connectivity = useConnectivity();
const resetOnboarding = useOnboardingStore((state) => state.resetOnboarding);
const isFetching =
signInFetchStatus === "fetching" || signUpFetchStatus === "fetching";
const isOffline = connectivity.status === "offline";
const isClerkReady = isAuthLoaded && !isFetching;
const isAuthActionDisabled = !isClerkReady || isOffline || isSubmitting;
const isSocialActionDisabled =
!isClerkReady || isOffline || socialStrategy !== null;
const emailFeedback = getEmailFeedback(email);
const passwordFeedback = getPasswordFeedback(password, isLogin);

Expand All @@ -104,6 +111,11 @@ export function AuthScreen({
}

async function handlePrimaryPress() {
if (isOffline) {
showError("No Internet Access. Connect to the internet to continue.");
return;
}

if (!isClerkReady || isSubmitting) {
return;
}
Expand Down Expand Up @@ -317,6 +329,11 @@ export function AuthScreen({
}

async function handleSocialPress(strategy: "oauth_google" | "oauth_apple") {
if (isOffline) {
showError("No Internet Access. Connect to the internet to continue.");
return;
}

if (!isClerkReady || socialStrategy) {
return;
}
Expand Down Expand Up @@ -401,6 +418,16 @@ export function AuthScreen({
}}
>
<View className="gap-4">
{isOffline ? (
<OfflineNotice
message={
isLogin
? "No Internet Access. Connect to the internet to sign in."
: "No Internet Access. Connect to the internet to create your account."
}
/>
) : null}

{!isLogin ? (
<AuthTextField
iconName="user"
Expand Down Expand Up @@ -450,11 +477,13 @@ export function AuthScreen({

<Pressable
accessibilityRole="button"
accessibilityState={{ disabled: isAuthActionDisabled }}
className="mt-5 h-14 items-center justify-center rounded-full bg-[#ff2056]"
disabled={!isClerkReady || isSubmitting}
disabled={isAuthActionDisabled}
onPress={handlePrimaryPress}
style={{
boxShadow: "0 12px 28px -9px rgba(255, 32, 86, 0.7)",
opacity: isAuthActionDisabled ? 0.55 : 1,
Comment thread
aryansoni-dev marked this conversation as resolved.
}}
>
<View className="flex-row items-center justify-center gap-2">
Expand All @@ -471,7 +500,7 @@ export function AuthScreen({

<View className="mt-16">
<SocialButtons
disabled={!isClerkReady || socialStrategy !== null}
disabled={isSocialActionDisabled}
loadingStrategy={socialStrategy}
onApplePress={() => void handleSocialPress("oauth_apple")}
onGooglePress={() => void handleSocialPress("oauth_google")}
Expand Down Expand Up @@ -596,6 +625,7 @@ function SocialButtons({
className="h-10 flex-row items-center justify-center gap-2 rounded-full border border-zinc-200 bg-zinc-50"
disabled={disabled}
onPress={onGooglePress}
style={{ opacity: disabled ? 0.55 : 1 }}
>
<View className="size-5 items-center justify-center">
{isGoogleLoading ? (
Expand All @@ -620,6 +650,7 @@ function SocialButtons({
className="h-10 flex-row items-center justify-center gap-2 rounded-full border border-zinc-200 bg-zinc-50"
disabled={disabled}
onPress={onApplePress}
style={{ opacity: disabled ? 0.55 : 1 }}
>
<View className="size-5 items-center justify-center">
{isAppleLoading ? (
Expand Down
10 changes: 6 additions & 4 deletions components/home/home-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ export function HomeScreen({ avatarUrl, firstName }: HomeScreenProps) {

function openDailyReflection() {
if (!reflectionPrompt) {
router.push({
pathname: "/journal/new",
params: { source: "home" },
});
return;
}

Expand Down Expand Up @@ -328,12 +332,10 @@ export function HomeScreen({ avatarUrl, firstName }: HomeScreenProps) {
accessibilityLabel={
reflectionPrompt
? `Start writing about: ${reflectionPrompt}`
: "Preparing today's reflection prompt"
: "Start writing a journal entry"
}
accessibilityRole="button"
accessibilityState={{ disabled: !reflectionPrompt }}
className="mt-auto h-12 items-center justify-center rounded-[17px] bg-[#FF2056]"
disabled={!reflectionPrompt}
onPress={openDailyReflection}
>
<Text className="text-[19px] font-semibold leading-6 text-white">
Expand Down Expand Up @@ -438,7 +440,7 @@ export function HomeScreen({ avatarUrl, firstName }: HomeScreenProps) {
<RecentEntriesEmptyState
body="Write your first entry and give today a place to live."
ctaLabel="Write an entry"
onCtaPress={reflectionPrompt ? openDailyReflection : undefined}
onCtaPress={openDailyReflection}
title="Your journal begins here"
/>
) : (
Expand Down
55 changes: 46 additions & 9 deletions supabase/functions/generate-insight-report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ type AIInsightReportRow = {
user_id: string;
};

type ChatCompletionRequestBody = {
max_tokens: number;
messages: {
content: string;
role: "system" | "user";
}[];
model: string;
response_format?: { type: "json_object" };
temperature: number;
};

class AIProviderError extends Error {
constructor(
message: string,
Expand Down Expand Up @@ -813,16 +824,22 @@ async function callAIProvider(finalPrompt: string) {
const timeout = setTimeout(() => controller.abort(), 45000);

try {
const requestBody: ChatCompletionRequestBody = {
messages: [
{ content: systemPrompt, role: "system" },
{ content: finalPrompt, role: "user" },
],
max_tokens: maxNarrativeTokens,
model,
temperature: 0.25,
};

if (supportsJsonObjectResponseFormat(baseUrl)) {
requestBody.response_format = { type: "json_object" };
}

const response = await fetch(`${baseUrl}/chat/completions`, {
body: JSON.stringify({
messages: [
{ content: systemPrompt, role: "system" },
{ content: finalPrompt, role: "user" },
],
max_tokens: maxNarrativeTokens,
model,
temperature: 0.45,
}),
body: JSON.stringify(requestBody),
Comment thread
aryansoni-dev marked this conversation as resolved.
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
Expand Down Expand Up @@ -926,6 +943,26 @@ async function getProviderErrorCode(response: Response) {
return `provider_http_${response.status}`;
}

function supportsJsonObjectResponseFormat(baseUrl: string) {
const hostname = getProviderHostname(baseUrl);

return (
hostname === "api.openai.com" ||
hostname === "api.groq.com" ||
hostname === "api.openrouter.ai" ||
hostname === "openrouter.ai" ||
hostname.endsWith(".openai.azure.com")
);
}

function getProviderHostname(baseUrl: string) {
try {
return new URL(baseUrl).hostname.toLowerCase();
} catch {
return "";
}
}

function getProviderMessage(body: unknown) {
if (!isRecord(body) || !Array.isArray(body.choices)) {
return null;
Expand Down
80 changes: 80 additions & 0 deletions testsprite-deardiary-prd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# DearDiary Test PRD

## Product overview

DearDiary is a local-first AI-powered journaling application built with React Native, Expo Router, TypeScript, Clerk, Supabase, Zustand, and AsyncStorage.

The application enables users to create private journal entries, log moods, receive AI reflections, view AI-generated insights and reports, and synchronize their data across sessions.

## Critical privacy requirements

- Data must be isolated by authenticated Clerk user.
- One user must never see another user’s entries, moods, AI messages, reports, or settings.
- No private content may flash during authentication, account switching, or App Lock.
- Account deletion must remove the Clerk account, Supabase data, local storage, SecureStore data, and scheduled reminders.
- Tests must use disposable accounts and non-sensitive content.

## Critical user flows

1. First launch and onboarding
2. Sign up and verification
3. Login and session restoration
4. Home dashboard
5. Mood logging
6. Dynamic morning, afternoon, and evening reflection prompts
7. Journal creation, editing, autosave, deletion, and restoration
8. Entry tags and AI-generated themes
9. Journal History, search, filtering, and Calendar
10. AI Chat
11. Per-entry AI reflection
12. Weekly and monthly AI reports
13. Insights and achievements
14. Offline creation and reconnect synchronization
15. App Lock with PIN and biometrics
16. Local reminder creation, editing, and cancellation
17. Export, backup, and restore where implemented
18. Sign out and account switching
19. Complete account deletion
20. Privacy Policy and Terms

## AI content requirements

- Long AI responses must never be visually clipped.
- Markdown paragraphs, headings, lists, links, blockquotes, and code blocks must remain readable.
- Streaming must not drop or overwrite chunks.
- Failed generation must preserve previous successful content.
- AI-generated themes may become entry tags only when the entry still has no tags.
- Dynamic daily prompts must remain stable for the same user, date, and time period.

## Navigation requirements

- All Expo Router routes must remain reachable.
- Back navigation must work.
- Bottom-tab state must remain stable.
- Deep links must not bypass authentication or App Lock.
- Premium screen transitions must not duplicate routes or lose state.
- Transition behavior must not clip dynamic content or interfere with gestures.

## Reliability requirements

- Offline entries must persist locally.
- Reconnection must not create duplicate records.
- App restart must preserve data.
- Failed network requests must show sanitized errors.
- Loading, empty, partial, offline, and retry states must remain usable.
- No raw Clerk, Supabase, or AI-provider errors may be exposed.

## Accessibility requirements

- Large fonts must not clip text.
- Interactive controls must have accessible labels.
- AI text must remain readable by screen readers.
- Reduced-motion preferences must be respected.
- Core navigation must not depend only on gestures.

## Test restrictions

- Use Preview services only.
- Do not use production accounts or data.
- Do not expose or record secrets.
- Do not modify application code automatically before the initial report is reviewed.