diff --git a/apps/mobile/src/app/(tabs)/fertilizers.tsx b/apps/mobile/src/app/(tabs)/fertilizers.tsx index 8c11f16..bfb904f 100644 --- a/apps/mobile/src/app/(tabs)/fertilizers.tsx +++ b/apps/mobile/src/app/(tabs)/fertilizers.tsx @@ -274,11 +274,11 @@ export function FertilizersScreen() { return ( - - Fertilizers - + + Fertilizers + { diff --git a/apps/mobile/src/app/(tabs)/plants.tsx b/apps/mobile/src/app/(tabs)/plants.tsx index da76f91..804287c 100644 --- a/apps/mobile/src/app/(tabs)/plants.tsx +++ b/apps/mobile/src/app/(tabs)/plants.tsx @@ -404,11 +404,11 @@ export function PlantsScreen() { return ( - - Plants - + + Plants + { @@ -447,7 +447,7 @@ export function PlantsScreen() { )) || ( - + diff --git a/apps/mobile/src/app/chores/[id].tsx b/apps/mobile/src/app/chores/[id].tsx index 0012d51..5ddfb24 100644 --- a/apps/mobile/src/app/chores/[id].tsx +++ b/apps/mobile/src/app/chores/[id].tsx @@ -1,11 +1,12 @@ import React, { useEffect, useMemo } from 'react' -import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native' +import { Text, TouchableOpacity, View } from 'react-native' import { useNavigation, useLocalSearchParams, useRouter } from 'expo-router' import { AddEditChoreModal } from '../../components/AddEditChoreModal' import { BarChart } from '../../components/BarChart' import { IconSnooze } from '../../components/IconSnooze' import { LoadingSkeleton, LoadingSkeletonLine } from '../../components/LoadingSkeleton' +import { ScreenTitle } from '../../components/ScreenTitle' import { ScreenWrapper } from '../../components/ScreenWrapper' import { SegmentedControl } from '../../components/SegmentedControl' import { SnoozeChoreModal } from '../../components/SnoozeChoreModal' @@ -16,7 +17,7 @@ import { useRefreshOnFocus } from '../../hooks/useRefetchOnFocus' import { trpc } from '../../trpc' -import { palette, styles } from '../../styles' +import { styles } from '../../styles' import { histogramDataFromChoreLogs } from '../../utils/histogram' import { formatLifecycleWithIcon, type PlantLifecycle } from '../../utils/lifecycle' @@ -206,27 +207,34 @@ export default function ChoreDetailScreen() { }, [chore?.logs]) return ( - + + + Chore + + {(isLoading && ( ( - - - - - - - - - - - - - - - + + + + + + + + + + + + + )} @@ -244,211 +252,209 @@ export default function ChoreDetailScreen() { )) || (chore && ( - - - { - setIsEditing(false) - setEditFormData({ - description: '', - lifecycles: [], - fertilizers: [], - recurAmount: '', - recurUnit: '', - notes: '', - }) - }} - onSubmit={handleEditSubmit} - mutation={updateMutation} - fertilizersData={fertilizersData} - /> - - <> - - + + { + setIsEditing(false) + setEditFormData({ + description: '', + lifecycles: [], + fertilizers: [], + recurAmount: '', + recurUnit: '', + notes: '', + }) + }} + onSubmit={handleEditSubmit} + mutation={updateMutation} + fertilizersData={fertilizersData} + /> - - handleEditClick(chore)} - > - ✏️ - - handleDeleteClick(chore)} - disabled={deleteMutation.isPending} - > - - + <> + + + + + Plant:{' '} + {chore.plant?._id ? ( + router.push(`/(tabs)/plants?expandPlantId=${chore.plant?._id}`)} + activeOpacity={0.7} + style={{ margin: 4 }} + > + + {chore.plant.name || 'Unknown'} + + + ) : ( + Unknown + )} + - - - - Plant:{' '} - {chore.plant?._id ? ( - router.push(`/(tabs)/plants?expandPlantId=${chore.plant?._id}`)} - activeOpacity={0.7} - style={{ margin: 4 }} - > - - {chore.plant.name || 'Unknown'} - - - ) : ( - Unknown - )} - - - {chore.fertilizers && chore.fertilizers.length > 0 && ( - <> - Fertilizers: - {chore.fertilizers.map((fert, index) => { - const fertilizer = typeof fert.fertilizer === 'object' ? fert.fertilizer : null - const fertilizerName = fertilizer?.name || '' - const fertilizerId = fertilizer?._id || (typeof fert.fertilizer === 'string' ? fert.fertilizer : null) - return ( - - {fertilizerId ? ( - router.push(`/(tabs)/fertilizers?expandFertilizerId=${fertilizerId}`)} - activeOpacity={0.7} - > - - {fertilizerName} - - - ) : ( - {fertilizerName} - )} - {fert.amount && ( - ({fert.amount}) - )} - - ) - })} - - )} - - Description: {chore.description || ''} - - {chore.recurAmount && chore.recurUnit && ( - - Recurrence: Every {chore.recurAmount} {chore.recurUnit}{chore.recurAmount === 1 ? '' : 's'} - - )} - {chore.lifecycles && Array.isArray(chore.lifecycles) && chore.lifecycles.length > 0 && ( + {chore.fertilizers && chore.fertilizers.length > 0 && ( + <> + Fertilizers: + {chore.fertilizers.map((fert, index) => { + const fertilizer = typeof fert.fertilizer === 'object' ? fert.fertilizer : null + const fertilizerName = fertilizer?.name || '' + const fertilizerId = fertilizer?._id || (typeof fert.fertilizer === 'string' ? fert.fertilizer : null) + return ( + + {fertilizerId ? ( + router.push(`/(tabs)/fertilizers?expandFertilizerId=${fertilizerId}`)} + activeOpacity={0.7} + > + + {fertilizerName} + + + ) : ( + {fertilizerName} + )} + {fert.amount && ( + ({fert.amount}) + )} + + ) + })} + + )} - Lifecycle: {chore.lifecycles.map(l => formatLifecycleWithIcon(l)).join(', ')} + Description: {chore.description || ''} - )} - {chore.isSnoozed && chore.snoozeUntil ? ( - + {chore.recurAmount && chore.recurUnit && ( - Snoozed Until: {new Date(chore.snoozeUntil).toLocaleDateString('en-US')} + Recurrence: Every {chore.recurAmount} {chore.recurUnit}{chore.recurAmount === 1 ? '' : 's'} - { - // Find the active snooze log to delete - const today = new Date() - today.setHours(0, 0, 0, 0) - const snoozedLogs = (chore.logs || []).filter(log => { - if (log.action !== 'snoozed' || !log.snoozeUntil) return false - - const snoozeDate = new Date(log.snoozeUntil) - snoozeDate.setHours(0, 0, 0, 0) - - return snoozeDate.getTime() >= today.getTime() - }) - - if (snoozedLogs.length === 0) { - alert('Error', 'Unable to find snooze log to undo.') - return - } - - // Get the most recent active snooze - const mostRecentSnooze = snoozedLogs.sort((a, b) => { - const dateA = new Date(a.snoozeUntil!).getTime() - const dateB = new Date(b.snoozeUntil!).getTime() - - return dateB - dateA - })[0] - - alert( - 'Undo Snooze?', - 'Are you sure you want to undo this snooze?', - [ - { - text: 'No', - style: 'cancel', - }, - { - text: 'Yes', - style: 'destructive', - onPress: () => { - deleteChoreLogMutation.mutate({ id: mostRecentSnooze._id }) - }, - }, - ] - ) - }} - disabled={deleteChoreLogMutation.isPending} - /> - - ) : chore.nextDate && ( - + )} + {chore.lifecycles && Array.isArray(chore.lifecycles) && chore.lifecycles.length > 0 && ( - Next Date: {new Date(chore.nextDate).toLocaleDateString('en-US')} + Lifecycle: {chore.lifecycles.map(l => formatLifecycleWithIcon(l)).join(', ')} - { - setSnoozeModalVisible(true) - }} - /> - - )} - {chore.notes && ( - - Notes: {chore.notes} - - )} - - - setTimeRange(value)} - /> - - {histogramData.length > 0 && ( - - + + Snoozed Until: {new Date(chore.snoozeUntil).toLocaleDateString('en-US')} + + { + // Find the active snooze log to delete + const today = new Date() + today.setHours(0, 0, 0, 0) + const snoozedLogs = (chore.logs || []).filter(log => { + if (log.action !== 'snoozed' || !log.snoozeUntil) return false + + const snoozeDate = new Date(log.snoozeUntil) + snoozeDate.setHours(0, 0, 0, 0) + + return snoozeDate.getTime() >= today.getTime() + }) + + if (snoozedLogs.length === 0) { + alert('Error', 'Unable to find snooze log to undo.') + return + } + + // Get the most recent active snooze + const mostRecentSnooze = snoozedLogs.sort((a, b) => { + const dateA = new Date(a.snoozeUntil!).getTime() + const dateB = new Date(b.snoozeUntil!).getTime() + + return dateB - dateA + })[0] + + alert( + 'Undo Snooze?', + 'Are you sure you want to undo this snooze?', + [ + { + text: 'No', + style: 'cancel', + }, + { + text: 'Yes', + style: 'destructive', + onPress: () => { + deleteChoreLogMutation.mutate({ id: mostRecentSnooze._id }) + }, + }, + ] + ) + }} + disabled={deleteChoreLogMutation.isPending} + /> + + ) : chore.nextDate && ( + + + Next Date: {new Date(chore.nextDate).toLocaleDateString('en-US')} + + { + setSnoozeModalVisible(true) + }} /> )} + {chore.notes && ( + + Notes: {chore.notes} + + )} - - + + + handleEditClick(chore)} + > + ✏️ + + handleDeleteClick(chore)} + disabled={deleteMutation.isPending} + > + Delete + + + + + + + setTimeRange(value)} + /> + + {histogramData.length > 0 && ( + + + + )} + + (null) @@ -252,13 +254,13 @@ export function BarChart({ // Initialize translateX to show the most recent period React.useEffect(() => { // Slide the chart to the the most recent - const initialTranslateX = getFarthestRightX(bars, barWidth) + const initialTranslateX = getFarthestRightX(bars, barWidth, CHART_WIDTH) translateX.setValue(initialTranslateX) gestureStartX.current = initialTranslateX }, [bars, barWidth, timeRange]) const scrollChart = (delta: number, velocity?: number) => { - const newX = calculateNewX(bars, barWidth, gestureStartX.current, delta) + const newX = calculateNewX(bars, barWidth, CHART_WIDTH, gestureStartX.current, delta) // If velocity is provided and significant, use momentum scrolling if (velocity !== undefined && Math.abs(velocity) > 0.5) { @@ -267,7 +269,7 @@ export function BarChart({ gestureStartX.current = newX // Calculate bounds - const overflowRightLimit = getFarthestRightX(bars, barWidth) + const overflowRightLimit = getFarthestRightX(bars, barWidth, CHART_WIDTH) const leftLimit = 0 // Add listener to check bounds during decay @@ -289,7 +291,7 @@ export function BarChart({ translateX.removeListener(listenerId) translateX.stopAnimation(() => { - const finalX = calculateNewX(bars, barWidth, boundedValue, 0) + const finalX = calculateNewX(bars, barWidth, CHART_WIDTH, boundedValue, 0) translateX.setValue(finalX) Animated.spring(translateX, { @@ -316,7 +318,7 @@ export function BarChart({ if (finished) { // Ensure we're within bounds after momentum translateX.stopAnimation((finalValue) => { - const finalX = calculateNewX(bars, barWidth, finalValue, 0) + const finalX = calculateNewX(bars, barWidth, CHART_WIDTH, finalValue, 0) Animated.spring(translateX, { toValue: finalX, @@ -363,7 +365,7 @@ export function BarChart({ }, onPanResponderMove: (_, gesture) => { // Provide visual feedback during swipe - const newX = calculateNewX(bars, barWidth, gestureStartX.current, gesture.dx, { allowOverflow: true }) + const newX = calculateNewX(bars, barWidth, CHART_WIDTH, gestureStartX.current, gesture.dx, { allowOverflow: true }) translateX.setValue(newX) }, onPanResponderRelease: (_, gesture) => { @@ -583,10 +585,10 @@ export function BarChart({ ) } -const calculateNewX = (bars: Bar[], barWidth: number, gestureStartX: number, delta: number, { allowOverflow } = { allowOverflow: false }) => { +const calculateNewX = (bars: Bar[], barWidth: number, chartWidth: number,gestureStartX: number, delta: number, { allowOverflow } = { allowOverflow: false }) => { let newX = gestureStartX + delta - const overflowRightLimit = getFarthestRightX(bars, barWidth) + const overflowRightLimit = getFarthestRightX(bars, barWidth, chartWidth) if (newX < overflowRightLimit) { newX = overflowRightLimit @@ -608,8 +610,8 @@ const calculateNewX = (bars: Bar[], barWidth: number, gestureStartX: number, del return newX } -const getFarthestRightX = (bars: Bar[], barWidth: number) => { - return (-bars.length * (barWidth + BAR_SPACING)) + CHART_WIDTH +const getFarthestRightX = (bars: Bar[], barWidth: number, chartWidth: number) => { + return (-bars.length * (barWidth + BAR_SPACING)) + chartWidth } const barChartStyles = StyleSheet.create({ diff --git a/apps/mobile/src/components/ScreenWrapper.tsx b/apps/mobile/src/components/ScreenWrapper.tsx index 4f5d10c..c8fc3d4 100644 --- a/apps/mobile/src/components/ScreenWrapper.tsx +++ b/apps/mobile/src/components/ScreenWrapper.tsx @@ -1,14 +1,15 @@ import React from 'react' -import { ScrollView } from 'react-native' +import { ScrollView, StyleProp, StyleSheet, ViewStyle } from 'react-native' import { usePullDownToRefresh } from '../hooks/usePullDownToRefresh' -import { styles } from '../styles' +import { palette } from '../styles' type ScreenWrapperProps = { children: React.ReactNode, onRefresh?: () => Promise | void, scrollViewRef?: React.RefObject, + style?: StyleProp, } type ScrollControlContextValue = { @@ -25,7 +26,12 @@ export function useScreenScrollControl() { return React.useContext(ScrollControlContext) } -export function ScreenWrapper({ children, onRefresh, scrollViewRef }: ScreenWrapperProps) { +export function ScreenWrapper({ + children, + onRefresh, + scrollViewRef, + style, +}: ScreenWrapperProps) { const { refreshControl } = usePullDownToRefresh(onRefresh) const [scrollEnabled, setScrollEnabled] = React.useState(true) const disableCountRef = React.useRef(0) @@ -55,7 +61,7 @@ export function ScreenWrapper({ children, onRefresh, scrollViewRef }: ScreenWrap ) } + +export const styles = StyleSheet.create({ + screenWrapper: { + flex: 1, + backgroundColor: palette.background, + }, + screenContentContainer: { + padding: 24, + rowGap: 24, + minHeight: '100%', + }, +}) diff --git a/apps/mobile/src/styles.ts b/apps/mobile/src/styles.ts index 1172f37..e263999 100644 --- a/apps/mobile/src/styles.ts +++ b/apps/mobile/src/styles.ts @@ -38,15 +38,6 @@ export const styles = StyleSheet.create({ flex: 1, backgroundColor: palette.background, }, - screenContainer: { - flex: 1, - backgroundColor: palette.background, - }, - screenContentContainer: { - padding: 24, - rowGap: 24, - minHeight: '100%', - }, appTitle: { fontSize: 30, fontWeight: '700', diff --git a/package-lock.json b/package-lock.json index bd1f33d..d07d9a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -873,7 +873,6 @@ "node_modules/@babel/core": { "version": "7.28.5", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -4491,7 +4490,6 @@ "node_modules/@jest/transform": { "version": "29.7.0", "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -4516,7 +4514,6 @@ "node_modules/@jest/types": { "version": "29.6.3", "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -5113,7 +5110,6 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.21.tgz", "integrity": "sha512-mhpAewdivBL01ibErr91FUW9bvKhfAF6Xv/yr6UOJtDhv0jU6iUASUcA3i3T8VJCOB/vxmoke7VDp8M+wBFs/Q==", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/core": "^7.13.2", "escape-string-regexp": "^4.0.0", @@ -5825,7 +5821,6 @@ "node_modules/@tanstack/react-query": { "version": "5.90.7", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/query-core": "5.90.7" }, @@ -5863,7 +5858,6 @@ "integrity": "sha512-wIn/lB1FjV2N4Q7i9PWVRck3Ehwq5pkhAef5X5/bmQ78J/NoOsGbVY2/DG5Y9Lxw+RfE+GvSEh/fe5Tz6sKSvw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "jest-matcher-utils": "^29.7.0", "pretty-format": "^29.7.0", @@ -5897,7 +5891,6 @@ "https://trpc.io/sponsor" ], "license": "MIT", - "peer": true, "peerDependencies": { "@trpc/server": "11.7.1", "typescript": ">=5.7.2" @@ -5924,7 +5917,6 @@ "https://trpc.io/sponsor" ], "license": "MIT", - "peer": true, "peerDependencies": { "typescript": ">=5.7.2" } @@ -6167,7 +6159,6 @@ "node_modules/@types/node": { "version": "20.19.24", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -6197,7 +6188,6 @@ "version": "19.1.17", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -6390,7 +6380,6 @@ "node_modules/acorn": { "version": "8.15.0", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6743,7 +6732,6 @@ "node_modules/babel-jest": { "version": "29.7.0", "license": "MIT", - "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -7200,7 +7188,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8892,7 +8879,6 @@ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -9102,7 +9088,6 @@ "version": "9.39.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9402,7 +9387,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.25.tgz", "integrity": "sha512-+iSeBJfHRHzNPnHMZceEXhSGw4t5bNqFyd/5xMUoGfM+39rO7F72wxiLRpBKj0M6+0GQtMaEs+eTbcCrO7XyJQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.16", @@ -9479,7 +9463,6 @@ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.12.tgz", "integrity": "sha512-WzcKYMVNRRu4NcSzfIVRD5aUQFnSpTZgXFrlWmm19xJoDa4S3/PQNi6PNTBRc49xz9h8FT7HMxRKaC8lr0gflA==", "license": "MIT", - "peer": true, "dependencies": { "@expo/config": "~12.0.12", "@expo/env": "~2.0.8" @@ -9572,7 +9555,6 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.9.tgz", "integrity": "sha512-xCoQbR/36qqB6tew/LQ6GWICpaBmHLhg/Loix5Rku/0ZtNaXMJv08M9o1AcrdiGTn/Xf/BnLu6DgS45cWQEHZg==", "license": "MIT", - "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -9603,7 +9585,6 @@ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.9.tgz", "integrity": "sha512-a0UHhlVyfwIbn8b1PSFPoFiIDJeps2iEq109hVH3CHd0CMKuRxFfNio9Axe2BjXhiJCYWR4OV1iIyzY/GjiVkQ==", "license": "MIT", - "peer": true, "dependencies": { "expo-constants": "~18.0.10", "invariant": "^2.2.4" @@ -11693,7 +11674,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -12392,7 +12372,6 @@ "node_modules/jest-util": { "version": "29.7.0", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -15926,7 +15905,6 @@ "node_modules/react": { "version": "19.1.0", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15942,7 +15920,6 @@ "node_modules/react-dom": { "version": "19.1.0", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -15975,7 +15952,6 @@ "node_modules/react-native": { "version": "0.81.5", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -16041,7 +16017,6 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -16052,7 +16027,6 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", "license": "MIT", - "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", @@ -16130,7 +16104,6 @@ "node_modules/react-refresh": { "version": "0.14.2", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16210,7 +16183,6 @@ "integrity": "sha512-jXkSl3CpvPYEF+p/eGDLB4sPoDX8pKkYvRl9+rR8HxLY0X04vW7hCm1/0zHoUSjPZ3bDa+wXWNTDVIw/R8aDVw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "react-is": "^19.1.0", "scheduler": "^0.26.0" @@ -17732,7 +17704,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18024,7 +17995,6 @@ "node_modules/typescript": { "version": "5.9.3", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19090,7 +19060,6 @@ "node_modules/zod": { "version": "4.1.12", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }