Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
91320b7
fix(swift): show real auth errors instead of "could not be loaded"
mikib0 Aug 9, 2026
bb84872
Merge branch 'development' into worktree-swift-auth-error-copy
mikib0 Aug 9, 2026
a1c24ba
Merge pull request #2674 from PackRat-AI/worktree-swift-auth-error-copy
mikib0 Aug 9, 2026
51e4b9a
feat(swift): add offline write outbox so local writes reach the server
mikib0 Aug 9, 2026
97b8bb1
fix(swift): pass modelContext at the write call sites that queue muta…
mikib0 Aug 9, 2026
acdcabf
Merge pull request #2673 from PackRat-AI/worktree-issue-2672-offline-…
mikib0 Aug 9, 2026
5a85d4a
fix(swift): flush the outbox from macOS standalone Pack/Trip windows
mikib0 Aug 9, 2026
14e352e
feat(mcp): add outputSchema to catalog, pack-item and guide tools
mikib0 Aug 9, 2026
e9d3c81
Merge pull request #2676 from PackRat-AI/fix/swift-macos-outbox-flush
mikib0 Aug 10, 2026
1c88252
Merge pull request #2677 from PackRat-AI/feat/mcp-output-schemas
mikib0 Aug 10, 2026
30279a9
fix(guides): distinguish a failed guides fetch from an empty result
mikib0 Aug 10, 2026
f1ac160
feat(landing): add /support page
mikib0 Aug 10, 2026
671d0e7
fix(swift/guides): decode the real /api/guides response so guides render
mikib0 Aug 10, 2026
cbe557c
fix(swift/guides): stop the category filter showing its selection twice
mikib0 Aug 10, 2026
b80a27f
fix(swift/guides): inset the category filter row horizontally
mikib0 Aug 10, 2026
a4b1d95
feat(landing): host the ChatGPT connector demo recording
mikib0 Aug 10, 2026
530cb33
Merge pull request #2684 from PackRat-AI/feat/host-demo-recording
mikib0 Aug 10, 2026
dc2c9a4
Merge pull request #2683 from PackRat-AI/feat/landing-support-page
mikib0 Aug 10, 2026
e628815
fix: address PR #2685 review comments
mikib0 Aug 10, 2026
ce90c76
Merge pull request #2687 from PackRat-AI/fix/pr-2685-review-comments
mikib0 Aug 10, 2026
3882100
fix(swift): address the open outbox review findings on PR #2685
mikib0 Aug 10, 2026
e0be7b2
Merge pull request #2689 from PackRat-AI/worktree-fix-2663-guides-emp…
mikib0 Aug 10, 2026
1037326
fix(swift): address the review comments on PR #2688
mikib0 Aug 10, 2026
dbc84ca
Merge pull request #2688 from PackRat-AI/worktree-pr-2685-review
mikib0 Aug 10, 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
56 changes: 40 additions & 16 deletions apps/expo/features/guides/screens/GuidesListScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getAppBarOptions } from '@packrat/ui/src/app-bar';
import { Button } from '@packrat/ui/src/button';
import { IosTransparentHeaderOverlapFix } from '@packrat/ui/src/ios-transparent-header-overlap-fix';
import { SearchOverlay } from '@packrat/ui/src/search-overlay';
import { Text } from '@packrat/ui/src/text';
Expand Down Expand Up @@ -29,6 +30,8 @@ export const GuidesListScreen = () => {
const {
data: guidesData,
isLoading: isLoadingGuides,
isError: isErrorGuides,
error: guidesError,
refetch: refetchGuides,
fetchNextPage: fetchNextPageGuides,
hasNextPage: hasNextPageGuides,
Expand All @@ -43,6 +46,8 @@ export const GuidesListScreen = () => {
const {
data: searchData,
isLoading: isSearching,
isError: isErrorSearch,
error: searchError,
refetch: refetchSearch,
fetchNextPage: fetchNextPageSearch,
hasNextPage: hasNextPageSearch,
Expand All @@ -58,6 +63,8 @@ export const GuidesListScreen = () => {
const isSearchMode = searchQuery.length > 0;
const data = isSearchMode ? searchData : guidesData;
const isLoading = isSearchMode ? isSearching : isLoadingGuides;
const isError = isSearchMode ? isErrorSearch : isErrorGuides;
const error = isSearchMode ? searchError : guidesError;
const refetch = isSearchMode ? refetchSearch : refetchGuides;
const fetchNextPage = isSearchMode ? fetchNextPageSearch : fetchNextPageGuides;
const hasNextPage = isSearchMode ? hasNextPageSearch : hasNextPageGuides;
Expand Down Expand Up @@ -102,17 +109,40 @@ export const GuidesListScreen = () => {
};

const renderEmpty = () => {
return (
<View className="flex-1 items-center justify-center p-8">
{isLoading ? (
if (isLoading) {
return (
<View className="flex-1 items-center justify-center p-8">
<ActivityIndicator color={colors.primary} size="large" />
) : (
<Text className="text-center text-gray-500 dark:text-gray-400">
{isSearchMode
? t('guides.noGuidesFound', { query: searchQuery })
: t('guides.noGuidesAvailable')}
</View>
);
}

// A failed request leaves `guides` empty too. Without this branch the list
// renders "No guides available", which reads as "there is no content" when
// the real problem is that the fetch failed (offline, expired session, 5xx).
if (isError) {
return (
<View className="flex-1 items-center justify-center p-8">
<Text className="mb-2 text-lg font-medium text-foreground">
{t('guides.failedToLoadGuides')}
</Text>
)}
<Text className="mb-6 text-center text-muted-foreground">
{error?.message || t('guides.pleaseTryAgain')}
</Text>
<Button variant="secondary" onPress={() => refetch()}>
<Text>{t('guides.tryAgain')}</Text>
</Button>
</View>
);
}

return (
<View className="flex-1 items-center justify-center p-8">
<Text className="text-center text-gray-500 dark:text-gray-400">
{isSearchMode
? t('guides.noGuidesFound', { query: searchQuery })
: t('guides.noGuidesAvailable')}
</Text>
</View>
);
};
Expand Down Expand Up @@ -152,13 +182,7 @@ export const GuidesListScreen = () => {
</View>
) : null
}
ListEmptyComponent={
<View className="flex-1 items-center justify-center p-8">
<Text className="text-center text-gray-500 dark:text-gray-400">
{t('guides.noGuidesFound', { query: searchQuery })}
</Text>
</View>
}
ListEmptyComponent={renderEmpty()}
ListFooterComponent={
isFetchingNextPageSearch ? (
<View className="py-4">
Expand Down
3 changes: 3 additions & 0 deletions apps/expo/lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,9 @@
"noGuidesFound": "No guides found for \"{{query}}\"",
"noGuidesAvailable": "No guides available",
"failedToLoad": "Failed to load guide",
"failedToLoadGuides": "Failed to load guides",
"pleaseTryAgain": "Please check your connection and try again.",
"tryAgain": "Try Again",
"viewAll": "View all",
"browseGuides": "Browse helpful guides and tutorials",
"by": "By",
Expand Down
138 changes: 138 additions & 0 deletions apps/landing/app/support/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { siteConfig } from 'landing-app/config/site';
import Link from 'next/link';

export const metadata = {
title: 'Support | PackRat',
description:
'Get help with PackRat — the mobile app, your account, and the Claude and ChatGPT connectors.',
};

export default function SupportPage() {
return (
<div className="container max-w-3xl py-12 px-4 md:px-6">
<div className="space-y-8">
<div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tight">Support</h1>
<p className="text-muted-foreground">
Need a hand with PackRat? Here&apos;s how to reach us and where to find answers.
</p>
</div>

<section className="space-y-4">
<h2 className="text-2xl font-semibold tracking-tight">Contact us</h2>

<div className="rounded-lg border p-6 bg-card space-y-4">
<div>
<h3 className="text-xl font-medium mb-1">Email</h3>
<p>
For any question, bug report, or feedback, email{' '}
<a className="underline" href={siteConfig.support.mailto}>
{siteConfig.support.email}
</a>
. We aim to reply within two business days.
</p>
</div>

<div className="border-t pt-4">
<h3 className="text-xl font-medium mb-1">What to include</h3>
<p className="mb-2">To help us resolve things on the first reply, please tell us:</p>
<ul className="list-disc pl-6 space-y-1">
<li>What you were trying to do, and what happened instead</li>
<li>Where it happened — the iOS app, the web app, or a connector</li>
<li>The email address on your PackRat account</li>
<li>A screenshot, if the problem is something you can see</li>
</ul>
</div>
</div>
</section>

<section className="space-y-4">
<h2 className="text-2xl font-semibold tracking-tight">Connectors</h2>
<p>
PackRat works inside Claude and ChatGPT, so you can plan trips, build packing lists, and
search gear without leaving the conversation.
</p>
<ul className="list-disc pl-6 space-y-1">
<li>
<Link className="underline" href="/mcp">
Connector setup and documentation
</Link>{' '}
— how to connect, what it can do, and the tools it exposes
</li>
<li>
Connecting requires a free PackRat account. If sign-in fails, first confirm the email
and password work in the app itself, then email us.
</li>
<li>
A connector only ever reads and writes your own PackRat data plus the public gear
catalog. Nothing is shared with other users.
</li>
</ul>
</section>

<section className="space-y-4">
<h2 className="text-2xl font-semibold tracking-tight">Common questions</h2>

<div className="space-y-4">
<div>
<h3 className="text-xl font-medium mb-1">I forgot my password</h3>
<p>
Use <strong>Forgot password</strong> on the sign-in screen to get a reset link. If
it doesn&apos;t arrive, check your spam folder before contacting us.
</p>
</div>

<div>
<h3 className="text-xl font-medium mb-1">My packs aren&apos;t syncing</h3>
<p>
PackRat saves changes locally first and syncs when you&apos;re back online. Confirm
you have a connection and that you&apos;re signed in to the same account on both
devices. If a pack is still missing after that, email us and we&apos;ll investigate.
</p>
</div>

<div>
<h3 className="text-xl font-medium mb-1">Weather looks wrong or missing</h3>
<p>
Forecasts cover the near term, so dates far in the future won&apos;t return a
forecast. For a trip months out, expect seasonal guidance rather than a daily
forecast.
</p>
</div>

<div>
<h3 className="text-xl font-medium mb-1">A gear item is wrong or missing</h3>
<p>
The catalog is large and sourced from manufacturers and retailers, so specs can
drift. Email us the product name and what&apos;s incorrect and we&apos;ll get it
fixed.
</p>
</div>
</div>
</section>

<section className="space-y-4">
<h2 className="text-2xl font-semibold tracking-tight">Account and privacy</h2>
<ul className="list-disc pl-6 space-y-1">
<li>
<Link className="underline" href="/account-deletion">
Delete your account
</Link>{' '}
— steps and what happens to your data
</li>
<li>
<Link className="underline" href="/privacy-policy">
Privacy policy
</Link>
</li>
<li>
<Link className="underline" href="/terms-of-service">
Terms of service
</Link>
</li>
</ul>
</section>
</div>
</div>
);
}
5 changes: 3 additions & 2 deletions apps/landing/config/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,9 @@ export const siteConfig = {
],
},

// Support contact — surfaced from MCP /health, the login page, and the connector listing.
// Email is the canonical channel; we don't run a separate support web page yet.
// Support contact — surfaced from MCP /health, the login page, the connector
// listing, and the /support page. Email is the canonical channel; render
// these values rather than hardcoding an address.
support: {
email: 'hello@packratai.com',
mailto: 'mailto:hello@packratai.com',
Expand Down
8 changes: 8 additions & 0 deletions apps/landing/public/_headers
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

# Demo recordings linked from the Claude / OpenAI connector submissions.
# Serve inline so a reviewer can play the video in-browser instead of
# downloading it, and cache hard since the filenames are versioned by content.
/demo/*
Content-Type: video/mp4
Content-Disposition: inline
Cache-Control: public, max-age=31536000, immutable
Binary file added apps/landing/public/demo/packrat-chatgpt-demo.mp4
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SwiftUI
import NukeUI
import SwiftData

struct CatalogView: View {
@Environment(AppState.self) private var appState
Expand Down Expand Up @@ -254,6 +255,7 @@ struct AddCatalogItemToPackSheet: View {
let item: CatalogItem
let packsViewModel: PacksViewModel
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext

@State private var selectedPackId: String?
@State private var quantity = 1
Expand Down Expand Up @@ -325,7 +327,8 @@ struct AddCatalogItemToPackSheet: View {
category: item.categories?.first,
consumable: false,
worn: false,
notes: nil
notes: nil,
context: modelContext
)
success = true
Task {
Expand Down
Loading
Loading