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
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Runtime & Framework
- Expo SDK `~54.0.25`, React `19.1.0`, React Native `0.81.5`, web via `react-native-web ~0.21.0`.
- `expo-router ~6.0.15` entry (`main: expo-router/entry`), React Navigation 7.x stack libs present.

## Routing & Navigation
- Root stack in `app/_layout.tsx`; tabs group in `app/(tabs)/_layout.tsx` uses custom bottom bar.
- Tabs: Home, Favorites, Kopi (center FAB-style), Orders, Me. Custom tab bar has rounded background, inset, and lifted Kopi button sized via constants at top of the file.
- Modal example removed (deleted `app/modal.tsx` and stack registration).

## Styling
- NativeWind `^4.2.1` with Babel `jsxImportSource: "nativewind"` + `nativewind/babel`; Metro wrapped with `withNativeWind(config, { input: "./global.css" })`.
- Tailwind `^3.4.18`; `tailwind.config.js` scans `./App.tsx`, `./app/**/*`, `./components/**/*`. Colors extended to coffee palette via CSS variables in `global.css` (currently only light-mode values).

## State & Domain
- Zustand store `stores/kopiMakerStore.ts`: holds kopi selection (`milkiness`, `sweetness`, `strength`, `temperature`), setters/reset, derived `baseName/displayName/phrase` via `utils/kopiInfer`.
- Domain types `types/kopi.ts`: option groups, option lists, `KopiSelection`, default selection. Temperature replaces earlier “state” naming.
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence is unclear: "Temperature replaces earlier 'state' naming." This seems to reference previous code state that reviewers cannot see, making the documentation confusing for someone new to the codebase.

Recommendation: Either clarify what this means or remove this historical reference:

- Domain types `types/kopi.ts`: option groups, option lists, `KopiSelection`, default selection.
Suggested change
- Domain types `types/kopi.ts`: option groups, option lists, `KopiSelection`, default selection. Temperature replaces earlier “state” naming.
- Domain types `types/kopi.ts`: option groups, option lists, `KopiSelection`, default selection.

Copilot uses AI. Check for mistakes.
- `utils/kopiInfer.ts`: maps selection to kopi naming parts.

## UI Pages
- Tab screens (`app/(tabs)/*.tsx`) are placeholders using Tailwind color tokens; Home/Kopi/Favorites/Orders/Me minimal content.
- Template components `hello-wave`, `parallax-scroll-view`, `external-link` removed. `haptic-tab.tsx` remains for tab haptics; `themed-text`/`themed-view` only used in deleted modal.

## Tooling
- TypeScript `~5.9.2`, strict with `@/*` alias. ESLint `^9.25.0` + `eslint-config-expo ~10.0.0`; `prettier-plugin-tailwindcss ^0.5.14`.
- Metro customized only for NativeWind; Babel preset is `babel-preset-expo`.

## Platform Config
- app.json: slug/name `kopi-shop`, scheme `kopishop`, portrait, automatic UI mode, static web output.
28 changes: 0 additions & 28 deletions AGENTS.md

This file was deleted.

111 changes: 108 additions & 3 deletions app/(tabs)/kopi.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,114 @@
import { Text, View } from 'react-native';
import { KopiCupPreview, OptionSelector } from "@/components/kopi";
import { useKopiMakerStore } from "@/stores/kopiMakerStore";
import { Colors } from "@/constants/theme";
import { useColorScheme } from "@/hooks/use-color-scheme";
import {
MILKINESS_OPTIONS,
STRENGTH_OPTIONS,
SWEETNESS_OPTIONS,
TEMPERATURE_OPTIONS,
} from "@/types/kopi";
import { Dimensions, Pressable, ScrollView, Text, View } from "react-native";
import Svg, { Path } from "react-native-svg";

const { width } = Dimensions.get("window");
const CURVE_HEIGHT = 80;
const CURVE_DEPTH = 50;
const SVG_H = CURVE_HEIGHT + CURVE_DEPTH;

export default function KopiScreen() {
Comment on lines +11 to 19
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling Dimensions.get("window") at module level can cause issues during initial render and doesn't respond to dimension changes (e.g., device rotation, window resize on web).

Recommendation: Move this inside the component and use useState with an effect to handle dimension changes, or use useWindowDimensions() hook from React Native:

import { useWindowDimensions } from 'react-native';

export default function KopiScreen() {
  const { width } = useWindowDimensions();
  // ... rest of code
Suggested change
import { Dimensions, Pressable, ScrollView, Text, View } from "react-native";
import Svg, { Path } from "react-native-svg";
const { width } = Dimensions.get("window");
const CURVE_HEIGHT = 80;
const CURVE_DEPTH = 50;
const SVG_H = CURVE_HEIGHT + CURVE_DEPTH;
export default function KopiScreen() {
import { Pressable, ScrollView, Text, View, useWindowDimensions } from "react-native";
import Svg, { Path } from "react-native-svg";
const CURVE_HEIGHT = 80;
const CURVE_DEPTH = 50;
const SVG_H = CURVE_HEIGHT + CURVE_DEPTH;
export default function KopiScreen() {
const { width } = useWindowDimensions();

Copilot uses AI. Check for mistakes.
const milkiness = useKopiMakerStore((s) => s.milkiness);
const sweetness = useKopiMakerStore((s) => s.sweetness);
const strength = useKopiMakerStore((s) => s.strength);
const temperature = useKopiMakerStore((s) => s.temperature);
const setMilkiness = useKopiMakerStore((s) => s.setMilkiness);
const setSweetness = useKopiMakerStore((s) => s.setSweetness);
const setStrength = useKopiMakerStore((s) => s.setStrength);
const setTemperature = useKopiMakerStore((s) => s.setTemperature);
const phrase = useKopiMakerStore((s) => s.phrase());

const colorScheme = useColorScheme() ?? "light";
const topBg = Colors[colorScheme].background;

return (
<View className="flex-1 justify-center items-center bg-milk-coffee">
<Text className="text-xl font-bold text-dark-coffee">Kopi Screen</Text>
<View className="flex-1 bg-warm-beige">
{/* Top Section: Preview (40%) */}
<View style={{ flex: 0.4, zIndex: 2, backgroundColor: topBg }}>
<KopiCupPreview name={phrase} />

{/* 微笑曲线分界 */}
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is in Chinese. Code comments should be in English for consistency and accessibility.

Suggestion: Replace with an English comment like:

{/* Smile curve divider */}
Suggested change
{/* 微笑曲线分界 */}
{/* Smile curve divider */}

Copilot uses AI. Check for mistakes.

<View
pointerEvents="none"
style={{
position: "absolute",
left: 0,
right: 0,
// 关键:往下压 CURVE_DEPTH,让“曲线边”贴在分界处
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inline comment is in Chinese. Code comments should be in English for consistency and accessibility.

Suggestion: Replace with an English comment like:

// Key: Push down by CURVE_DEPTH so the "curve edge" aligns at the boundary
Suggested change
// 关键:往下压 CURVE_DEPTH,让“曲线边”贴在分界处
// Key: Push down by CURVE_DEPTH so the "curve edge" aligns at the boundary

Copilot uses AI. Check for mistakes.
bottom: -CURVE_DEPTH,
zIndex: 999,
elevation: 999, // 安卓需要
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inline comment is in Chinese. Code comments should be in English for consistency and accessibility.

Suggestion: Replace with an English comment like:

elevation: 999, // Needed for Android
Suggested change
elevation: 999, // 安卓需要
elevation: 999, // Needed for Android

Copilot uses AI. Check for mistakes.
}}
>
<Svg width={width} height={SVG_H} viewBox={`0 0 ${width} ${SVG_H}`}>
<Path
d={`
M 0 0
L ${width} 0
L ${width} ${CURVE_HEIGHT}
Q ${width / 2} ${CURVE_HEIGHT + CURVE_DEPTH} 0 ${CURVE_HEIGHT}
Z
`}
fill={topBg}
/>
</Svg>
</View>
</View>

{/* Bottom Section: Controls (60%) */}
<View className="flex-[0.6] bg-warm-beige pt-6" style={{ zIndex: 1 }}>
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingBottom: 120, paddingHorizontal: 0 }}
>
<OptionSelector
label="Milk"
options={MILKINESS_OPTIONS}
value={milkiness}
onChange={setMilkiness}
/>
<OptionSelector
label="Sugar"
options={SWEETNESS_OPTIONS}
value={sweetness}
onChange={setSweetness}
/>
<OptionSelector
label="Strength"
options={STRENGTH_OPTIONS}
value={strength}
onChange={setStrength}
/>
<OptionSelector
label="Temperature"
options={TEMPERATURE_OPTIONS}
value={temperature}
onChange={setTemperature}
/>
</ScrollView>
</View>

{/* Brew Button */}
<View className="absolute bottom-8 left-0 right-0 px-6">
<Pressable
className="bg-dark-coffee py-4 rounded-2xl items-center shadow-lg active:opacity-90"
onPress={() => console.log("Brewing:", phrase)}
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Brew" button lacks accessibility properties, which makes it less accessible to users with screen readers.

Recommendation: Add accessibilityLabel and accessibilityRole:

<Pressable
  className="bg-dark-coffee py-4 rounded-2xl items-center shadow-lg active:opacity-90"
  onPress={() => console.log("Brewing:", phrase)}
  accessibilityLabel={`Brew ${phrase}`}
  accessibilityRole="button"
>
Suggested change
onPress={() => console.log("Brewing:", phrase)}
onPress={() => console.log("Brewing:", phrase)}
accessibilityLabel={`Brew ${phrase}`}
accessibilityRole="button"

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a console.log statement in the production code. While this might be a placeholder for future functionality, console logs should generally be removed from production code or replaced with proper logging/handling.

Recommendation: Either implement the actual brew functionality or remove the console log and add a TODO comment:

onPress={() => {
  // TODO: Implement brew functionality
}}
Suggested change
onPress={() => console.log("Brewing:", phrase)}
onPress={() => {
// TODO: Implement brew functionality
}}

Copilot uses AI. Check for mistakes.
>
<Text className="text-white text-xl font-bold tracking-wider">
BREW
</Text>
</Pressable>
</View>
</View>
);
}
1 change: 0 additions & 1 deletion app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export default function RootLayout() {
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
</Stack>
<StatusBar style="auto" />
</ThemeProvider>
Expand Down
29 changes: 0 additions & 29 deletions app/modal.tsx

This file was deleted.

25 changes: 0 additions & 25 deletions components/external-link.tsx

This file was deleted.

19 changes: 0 additions & 19 deletions components/hello-wave.tsx

This file was deleted.

26 changes: 26 additions & 0 deletions components/kopi/KopiCupPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { Dimensions, Text, View } from 'react-native';

interface KopiCupPreviewProps {
name: string;
}

const { width } = Dimensions.get('window');
const curveRadius = width; // big radius to form a smooth smile at the bottom edge

export const KopiCupPreview = ({ name }: KopiCupPreviewProps) => {
Comment on lines +2 to +11
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the main Kopi screen, calling Dimensions.get("window") at module level doesn't respond to dimension changes and can cause layout issues during rotation or window resize.

Recommendation: Use useWindowDimensions() hook inside the component:

import { useWindowDimensions, Text, View } from 'react-native';

export const KopiCupPreview = ({ name }: KopiCupPreviewProps) => {
  const { width } = useWindowDimensions();
  const curveRadius = width;
  // ... rest of code
Suggested change
import { Dimensions, Text, View } from 'react-native';
interface KopiCupPreviewProps {
name: string;
}
const { width } = Dimensions.get('window');
const curveRadius = width; // big radius to form a smooth smile at the bottom edge
export const KopiCupPreview = ({ name }: KopiCupPreviewProps) => {
import { useWindowDimensions, Text, View } from 'react-native';
interface KopiCupPreviewProps {
name: string;
}
export const KopiCupPreview = ({ name }: KopiCupPreviewProps) => {
const { width } = useWindowDimensions();
const curveRadius = width; // big radius to form a smooth smile at the bottom edge

Copilot uses AI. Check for mistakes.
return (
<View
className="w-full h-full bg-cream items-center justify-center overflow-hidden"
style={{
borderBottomLeftRadius: curveRadius,
borderBottomRightRadius: curveRadius,
}}>
<View className="items-center mb-6 px-6">
<Text className="text-5xl font-bold text-dark-coffee text-center leading-tight">
{name}
</Text>
</View>
</View>
);
};
50 changes: 50 additions & 0 deletions components/kopi/OptionSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { OptionItem } from '@/types/kopi';
import React from 'react';
import { Pressable, Text, View } from 'react-native';

interface OptionSelectorProps<T extends string> {
label: string;
options: OptionItem<T>[];
value: T;
onChange: (value: T) => void;
}

export const OptionSelector = <T extends string>({
label,
options,
value,
onChange,
}: OptionSelectorProps<T>) => {
return (
<View className="mb-6">
<Text className="text-lg font-bold text-dark-coffee mb-3 px-4">{label}</Text>
<View
style={{
flexDirection: 'row',
flexWrap: 'wrap',
gap: 10,
rowGap: 10,
paddingHorizontal: 16,
}}>
{options.map((option) => {
const isSelected = option.code === value;
return (
<Pressable
key={option.code}
onPress={() => onChange(option.code)}
className={`px-4 py-2 rounded-md border ${
isSelected
? 'bg-dark-coffee border-dark-coffee'
: 'bg-cream border-grey'
}`}
Comment on lines +32 to +39
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Pressable components lack accessibility labels, which makes the UI less accessible to users with screen readers. Each option button should have an accessible label describing its purpose.

Recommendation: Add accessibilityLabel and accessibilityRole:

<Pressable
  key={option.code}
  onPress={() => onChange(option.code)}
  accessibilityLabel={`${label}: ${option.label}${option.hint ? ' ' + option.hint : ''}`}
  accessibilityRole="button"
  accessibilityState={{ selected: isSelected }}
  className={...}
>

Copilot uses AI. Check for mistakes.
>
<Text className={`text-base font-semibold ${isSelected ? 'text-white' : 'text-dark-coffee'}`}>
{option.label}
</Text>
</Pressable>
);
})}
</View>
</View>
);
};
2 changes: 2 additions & 0 deletions components/kopi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './KopiCupPreview';
export * from './OptionSelector';
Loading
Loading