From c095c039a5c3de6994deec1db31e190652d0f4e8 Mon Sep 17 00:00:00 2001
From: Ibrahim Isa Jajere
Date: Sat, 18 Apr 2026 21:11:12 +0100
Subject: [PATCH 001/233] fix(expo/ai): resolve apple intelligence availability
check error
---
apps/expo/features/ai/lib/localModelManager.ts | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/apps/expo/features/ai/lib/localModelManager.ts b/apps/expo/features/ai/lib/localModelManager.ts
index 76e4c4395e..68a4281553 100644
--- a/apps/expo/features/ai/lib/localModelManager.ts
+++ b/apps/expo/features/ai/lib/localModelManager.ts
@@ -20,7 +20,6 @@ import {
} from '../atoms/aiModeAtoms';
import { LLAMA_MODEL_ID, LLAMA_MODEL_SIZE_BYTES } from './constants';
-import { createLocalTools } from './tools';
const LLAMA_MODEL_FILENAME = 'SmolLM3-Q4_K_M.gguf';
const LLAMA_MODELS_DIR = `${RNBlobUtil.fs.dirs.DocumentDir}/llama-models`;
@@ -57,9 +56,12 @@ function getAppleModule() {
if (appleModule) return appleModule;
try {
- appleModule = import('@react-native-ai/apple');
+ // require() is synchronous — import() returns a Promise, which breaks
+ // the synchronous callers (isAppleIntelligenceAvailable, etc.)
+ appleModule = require('@react-native-ai/apple');
return appleModule;
- } catch {
+ } catch (err) {
+ console.error('Failed to load Apple module:', err);
return null;
}
}
@@ -246,7 +248,7 @@ async function _initAppleModel(): Promise {
if (!mod) throw new Error('Apple module not available');
appleModel = mod.apple();
- appleModel.updateTools(createLocalTools());
+ // appleModel.updateTools(createLocalTools());
store.set(localModelStatusAtom, 'ready');
} catch {
store.set(localModelStatusAtom, 'error');
From 0c44ac4fe7df59425b41abdc8536c093877dbd16 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Apr 2026 01:01:11 +0000
Subject: [PATCH 002/233] fix: improve Lighthouse LCP scores for guides and
landing apps
Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/396e79f1-4bef-4512-802b-b8f182cd9250
Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
---
apps/guides/app/page.tsx | 117 ++----------------
apps/guides/components/guides-content.tsx | 113 +++++++++++++++++
.../components/providers/query-provider.tsx | 8 +-
.../components/sections/landing-hero.tsx | 21 ++--
apps/landing/components/ui/device-mockup.tsx | 9 +-
5 files changed, 130 insertions(+), 138 deletions(-)
create mode 100644 apps/guides/components/guides-content.tsx
diff --git a/apps/guides/app/page.tsx b/apps/guides/app/page.tsx
index 199b11581c..e9da6afc03 100644
--- a/apps/guides/app/page.tsx
+++ b/apps/guides/app/page.tsx
@@ -1,55 +1,14 @@
-'use client';
-
import { Button } from '@packrat/web-ui/components/button';
-import CategoryFilter from 'guides-app/components/category-filter';
-import FeaturedGuides from 'guides-app/components/featured-guides';
-import GuideCard from 'guides-app/components/guide-card';
-import { getAllCategories } from 'guides-app/lib/categories';
-import { featuresConfig } from 'guides-app/lib/config';
-import { getAllPosts } from 'guides-app/lib/mdx-static';
+import GuidesContent from 'guides-app/components/guides-content';
import Link from 'next/link';
-import { useSearchParams } from 'next/navigation';
import { Suspense } from 'react';
-function HomeContent() {
- const searchParams = useSearchParams() ?? new URLSearchParams();
- const category = searchParams.get('category');
- const search = searchParams.get('search');
-
- const allPosts = getAllPosts();
- const categories = getAllCategories();
-
- // Filter posts by category if provided
- let filteredPosts = allPosts;
-
- if (category) {
- filteredPosts = allPosts.filter((post) => post.categories?.includes(category as string));
- } else if (search) {
- const searchQuery = search.toLowerCase();
- filteredPosts = allPosts.filter((post) => {
- const searchContent = `${post.title} ${
- post.description
- } ${post.categories?.join(' ')}`.toLowerCase();
- return searchContent.includes(searchQuery);
- });
- }
-
- // Get featured guides (most recent 3)
- const featuredGuides = allPosts.slice(0, 3);
-
- // Determine the page title based on search params
- let pageTitle = 'All Guides';
- if (search) {
- pageTitle = `Search Results for "${search}"`;
- } else if (category) {
- pageTitle = `${category.charAt(0).toUpperCase() + category.slice(1)} Guides`;
- }
-
+export default function Home() {
return (
- {/* Hero Section - Apple style */}
+ {/* Hero Section — server-rendered for fast LCP */}
-
+
PackRat Guides
@@ -72,70 +31,10 @@ function HomeContent() {
- {/* Features Section - Apple style */}
- {!category && !search && (
- <>
-
-
-
- {featuresConfig.map((feature) => (
-
-
-
-
-
{feature.title}
-
{feature.description}
-
- ))}
-
-
-
-
- {/* Featured Guides - Apple style */}
-
-
-
- Featured Guides
-
-
-
-
- >
- )}
-
- {/* All Guides - Apple style */}
-
-
-
{pageTitle}
-
-
-
-
-
-
- {filteredPosts.length > 0 ? (
- filteredPosts.map((post) =>
)
- ) : (
-
-
- No guides found. Try a different search or category.
-
-
-
- )}
-
-
-
+ {/* Dynamic content — rendered client-side (uses useSearchParams) */}
+
+
+
);
}
-
-export default function Home() {
- return (
-
-
-
- );
-}
diff --git a/apps/guides/components/guides-content.tsx b/apps/guides/components/guides-content.tsx
new file mode 100644
index 0000000000..0a296eb06a
--- /dev/null
+++ b/apps/guides/components/guides-content.tsx
@@ -0,0 +1,113 @@
+'use client';
+
+import { Button } from '@packrat/web-ui/components/button';
+import CategoryFilter from 'guides-app/components/category-filter';
+import FeaturedGuides from 'guides-app/components/featured-guides';
+import GuideCard from 'guides-app/components/guide-card';
+import { getAllCategories } from 'guides-app/lib/categories';
+import { featuresConfig } from 'guides-app/lib/config';
+import { getAllPosts } from 'guides-app/lib/mdx-static';
+import Link from 'next/link';
+import { useSearchParams } from 'next/navigation';
+import { Suspense } from 'react';
+
+function GuidesContentInner() {
+ const searchParams = useSearchParams() ?? new URLSearchParams();
+ const category = searchParams.get('category');
+ const search = searchParams.get('search');
+
+ const allPosts = getAllPosts();
+ const categories = getAllCategories();
+
+ let filteredPosts = allPosts;
+
+ if (category) {
+ filteredPosts = allPosts.filter((post) => post.categories?.includes(category as string));
+ } else if (search) {
+ const searchQuery = search.toLowerCase();
+ filteredPosts = allPosts.filter((post) => {
+ const searchContent = `${post.title} ${
+ post.description
+ } ${post.categories?.join(' ')}`.toLowerCase();
+ return searchContent.includes(searchQuery);
+ });
+ }
+
+ const featuredGuides = allPosts.slice(0, 3);
+
+ let pageTitle = 'All Guides';
+ if (search) {
+ pageTitle = `Search Results for "${search}"`;
+ } else if (category) {
+ pageTitle = `${category.charAt(0).toUpperCase() + category.slice(1)} Guides`;
+ }
+
+ return (
+ <>
+ {/* Features Section - Apple style */}
+ {!category && !search && (
+ <>
+
+
+
+ {featuresConfig.map((feature) => (
+
+
+
+
+
{feature.title}
+
{feature.description}
+
+ ))}
+
+
+
+
+ {/* Featured Guides - Apple style */}
+
+
+
+ Featured Guides
+
+
+
+
+ >
+ )}
+
+ {/* All Guides - Apple style */}
+
+
+
{pageTitle}
+
+
+
+
+
+
+ {filteredPosts.length > 0 ? (
+ filteredPosts.map((post) =>
)
+ ) : (
+
+
+ No guides found. Try a different search or category.
+
+
+
+ )}
+
+
+
+ >
+ );
+}
+
+export default function GuidesContent() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/guides/components/providers/query-provider.tsx b/apps/guides/components/providers/query-provider.tsx
index bd57484629..735dff1770 100644
--- a/apps/guides/components/providers/query-provider.tsx
+++ b/apps/guides/components/providers/query-provider.tsx
@@ -1,7 +1,6 @@
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { type ReactNode, useState } from 'react';
export function QueryProvider({ children }: { children: ReactNode }) {
@@ -17,10 +16,5 @@ export function QueryProvider({ children }: { children: ReactNode }) {
}),
);
- return (
-
- {children}
-
-
- );
+ return {children};
}
diff --git a/apps/landing/components/sections/landing-hero.tsx b/apps/landing/components/sections/landing-hero.tsx
index 18ea36eed7..d8e55aede2 100644
--- a/apps/landing/components/sections/landing-hero.tsx
+++ b/apps/landing/components/sections/landing-hero.tsx
@@ -18,9 +18,8 @@ export default function LandingHero() {
};
const containerVariants = {
- hidden: { opacity: 0 },
+ hidden: {},
visible: {
- opacity: 1,
transition: { staggerChildren: 0.1, delayChildren: 0.2 },
},
};
@@ -56,24 +55,18 @@ export default function LandingHero() {
- {/* Heading */}
-
+ {/* Heading — rendered immediately for LCP */}
+
{siteConfig.hero.titleLine1}
{siteConfig.hero.titleLine2}
-
+
- {/* Subtitle */}
-
+ {/* Subtitle — rendered immediately for LCP */}
+
{siteConfig.hero.subtitle}
-
+
{/* CTA buttons */}
{
- setMounted(true);
- }, []);
-
return (
// biome-ignore lint/a11y/noStaticElementInteractions: ignore
setIsHovered(true)}
From 05d4da60d8bffe4b4107cd2c37454ee1bca4d47b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Apr 2026 02:31:00 +0000
Subject: [PATCH 003/233] refactor: replace Framer Motion with Tailwind CSS
animations, remove unnecessary 'use client' directives
Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/4c56e6bb-bbc6-4033-9d86-d72ec9deb619
Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
---
apps/guides/components/footer.tsx | 9 +-
apps/guides/components/header.tsx | 7 +-
apps/guides/styles/globals.css | 5 +-
apps/landing/components/app-preview.tsx | 28 ++---
.../components/sections/landing-hero.tsx | 101 ++++++------------
apps/landing/components/site-footer.tsx | 15 ---
apps/landing/components/ui/device-mockup.tsx | 29 +----
apps/landing/package.json | 2 -
apps/landing/styles/globals.css | 5 +-
apps/landing/tailwind.config.ts | 20 ++++
10 files changed, 76 insertions(+), 145 deletions(-)
diff --git a/apps/guides/components/footer.tsx b/apps/guides/components/footer.tsx
index cad9e5bc62..0103181539 100644
--- a/apps/guides/components/footer.tsx
+++ b/apps/guides/components/footer.tsx
@@ -1,6 +1,3 @@
-'use client';
-
-import { useQuery } from '@tanstack/react-query';
import { assertDefined } from 'guides-app/lib/assertDefined';
import { getAllCategories } from 'guides-app/lib/categories';
import { footerConfig, siteConfig } from 'guides-app/lib/config';
@@ -8,11 +5,7 @@ import { Backpack, Globe } from 'lucide-react';
import Link from 'next/link';
export default function Footer() {
- // Fetch categories using TanStack Query
- const { data: categories = [] } = useQuery({
- queryKey: ['categories'],
- queryFn: getAllCategories,
- });
+ const categories = getAllCategories();
const company = footerConfig.mainSections[1];
assertDefined(company);
diff --git a/apps/guides/components/header.tsx b/apps/guides/components/header.tsx
index 988e6405e7..a2c508011b 100644
--- a/apps/guides/components/header.tsx
+++ b/apps/guides/components/header.tsx
@@ -8,7 +8,6 @@ import {
} from '@packrat/web-ui/components/dropdown-menu';
import { Sheet, SheetContent, SheetTrigger } from '@packrat/web-ui/components/sheet';
import { cn } from '@packrat/web-ui/lib/utils';
-import { useQuery } from '@tanstack/react-query';
import { getAllCategories } from 'guides-app/lib/categories';
import { navigationConfig, siteConfig } from 'guides-app/lib/config';
import { Backpack, ChevronDown } from 'lucide-react';
@@ -20,11 +19,7 @@ import { ThemeToggle } from './theme-toggle';
export default function Header() {
const [scrolled, setScrolled] = useState(false);
- // Fetch categories using TanStack Query
- const { data: categories = [] } = useQuery({
- queryKey: ['categories'],
- queryFn: getAllCategories,
- });
+ const categories = getAllCategories();
// Add scroll listener
useEffect(() => {
diff --git a/apps/guides/styles/globals.css b/apps/guides/styles/globals.css
index ac6844236c..b23e1032a2 100644
--- a/apps/guides/styles/globals.css
+++ b/apps/guides/styles/globals.css
@@ -13,7 +13,10 @@ body {
}
@layer base {
- :root {
+ html {
+ scroll-behavior: smooth;
+ }
+
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
diff --git a/apps/landing/components/app-preview.tsx b/apps/landing/components/app-preview.tsx
index eb48935d54..a3c1b7eaa5 100644
--- a/apps/landing/components/app-preview.tsx
+++ b/apps/landing/components/app-preview.tsx
@@ -1,7 +1,5 @@
'use client';
-import { AnimatePresence, motion } from 'framer-motion';
-import { assertDefined } from 'landing-app/lib/typeAssertions';
import Image from 'next/image';
import { useEffect, useState } from 'react';
@@ -29,28 +27,24 @@ export default function AppPreview() {
return () => clearInterval(interval);
}, [screens.length]);
- assertDefined(screens[currentScreen]);
-
return (
<>
-
- (
+
-
-
+
+ ))}
{screens.map((_, index) => (
{/* Device mockup column */}
-
{/* Floating cards – Apple style: clean card with subtle shadow */}
-
@@ -167,13 +130,11 @@ export default function LandingHero() {
-
+
-
@@ -186,8 +147,8 @@ export default function LandingHero() {
-
-
+
+
diff --git a/apps/landing/components/site-footer.tsx b/apps/landing/components/site-footer.tsx
index 0e52ad3ddc..efc8e9e153 100644
--- a/apps/landing/components/site-footer.tsx
+++ b/apps/landing/components/site-footer.tsx
@@ -1,21 +1,9 @@
-'use client';
-
import { siteConfig } from 'landing-app/config/site';
import { LucideIcon, TikTokIcon } from 'landing-app/lib/icons';
import { Backpack } from 'lucide-react';
import Link from 'next/link';
-import type React from 'react';
export default function SiteFooter() {
- const scrollToSection = (e: React.MouseEvent, href: string) => {
- e.preventDefault();
- const targetId = href.substring(1);
- const element = document.getElementById(targetId);
- if (element) {
- element.scrollIntoView({ behavior: 'smooth' });
- }
- };
-
return (