feat(web): wire gear-inventory and ai-chat screens to real API#2398
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (25)
WalkthroughThis PR enforces deterministic Bun dependency installation across CI workflows using frozen lockfiles, and refactors two web screens to integrate live data sources and the ai-sdk library for chat functionality. ChangesCI Workflow Determinism
Web Screen Feature Integration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR wires the web app’s Gear Inventory and AI Chat screens to real backend data/services instead of hardcoded mocks, aligning the web experience with the existing PackRat API and chat streaming infrastructure.
Changes:
- Replace
GearInventoryScreenmock inventory with items aggregated fromusePacks()(loading skeleton + empty state + derived category filters). - Replace
AIScreenmock responses with@ai-sdk/react+DefaultChatTransportstreaming against the/api/chatendpoint. - Add
@ai-sdk/reactandaitoapps/webdependencies.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| apps/web/package.json | Adds AI SDK dependencies needed for the real chat transport. |
| apps/web/components/screens/gear-inventory-screen.tsx | Switches inventory to real pack items, adds loading/empty UI, computes totals, and derives categories. |
| apps/web/components/screens/ai-screen.tsx | Switches chat to real streaming transport and updates message rendering to the UIMessage model. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for (const pack of packsRaw) { | ||
| for (const item of pack.items ?? []) { | ||
| if (!seen.has(item.id) && !item.deleted) { | ||
| seen.add(item.id); | ||
| items.push(item); | ||
| } | ||
| } |
| const totalWeight = useMemo( | ||
| () => filtered.reduce((sum, item) => sum + item.weight, 0), | ||
| () => filtered.reduce((sum, item) => sum + item.weight * item.quantity, 0), | ||
| [filtered], |
| const categories = useMemo(() => { | ||
| const cats = new Set<string>(); | ||
| for (const item of allItems) { | ||
| if (item.category) cats.add(item.category); | ||
| } | ||
| return ['All', ...Array.from(cats).sort()]; | ||
| }, [allItems]); |
| "That's a great question! I can help you plan, optimize, and gear up for any adventure. Try asking me to build a specific kit, optimize a pack, or recommend gear for particular conditions.", | ||
| }; | ||
| function getTextContent(msg: UIMessage): string { | ||
| return msg.parts.find((p): p is TextUIPart => p.type === 'text')?.text ?? ''; |
| const transport = new DefaultChatTransport({ | ||
| api: `${API_BASE}/api/chat`, | ||
| headers: () => ({ Authorization: `Bearer ${Cookies.get('access_token') ?? ''}` }), | ||
| body: () => ({ date: new Date().toISOString() }), | ||
| }); | ||
|
|
||
| const { messages, sendMessage, status } = useChat({ transport }); |
| const transport = new DefaultChatTransport({ | ||
| api: `${API_BASE}/api/chat`, | ||
| headers: () => ({ Authorization: `Bearer ${Cookies.get('access_token') ?? ''}` }), | ||
| body: () => ({ date: new Date().toISOString() }), | ||
| }); |
| {messages.map((msg) => ( | ||
| <MessageBubble key={msg.id} message={msg} fw={fw} /> | ||
| <MessageBubble | ||
| key={msg.id} | ||
| role={msg.role as 'user' | 'assistant'} | ||
| content={getTextContent(msg)} | ||
| /> |
Deploying packrat-guides with
|
| Latest commit: |
39dcd0b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://7b041b18.packrat-guides-6gq.pages.dev |
| Branch Preview URL: | https://feat-web-wire-remaining-scre.packrat-guides-6gq.pages.dev |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
packrat-admin | 34064aa | Commit Preview URL Branch Preview URL |
May 12 2026, 04:19 PM |
- GearInventoryScreen: replaces mock data with usePacks(); deduplicates items across packs, filters deleted, derives categories dynamically - AIScreen: replaces MOCK_RESPONSES with DefaultChatTransport hitting /api/chat; uses sendMessage/status from @ai-sdk/react v3 API - Adds @ai-sdk/react and ai to apps/web deps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
setup-bun v2 does not accept a 'cache' input; valid options are 'no-cache', 'bun-version', etc. Removes the invalid input from all affected workflow files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
39dcd0b to
43a08a7
Compare
Coverage Report for API Unit Tests Coverage (./packages/api)
File CoverageNo changed files found. |
Coverage Report for Expo Unit Tests Coverage (./apps/expo)
File CoverageNo changed files found. |
gear-inventory: - Skip soft-deleted packs (pack.deleted) in item aggregation - Use toGrams(weight, weightUnit) for correct unit conversion - Capitalize category labels for display (first-aid → First Aid) ai-screen: - Memoize DefaultChatTransport to prevent per-render reinitialization - Concatenate all text parts instead of only the first one - Filter to user/assistant roles before rendering bubbles 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
| () => | ||
| inventory.filter((item) => { | ||
| allItems.filter((item) => { | ||
| const matchCat = selectedCategory === 'All' || item.category === selectedCategory; |
| > | ||
| <div className="flex-1 min-w-0"> | ||
| <p className="font-semibold text-sm">{item.name}</p> | ||
| <div className="flex items-center gap-2 mt-1.5"> |
| function getTextContent(msg: UIMessage): string { | ||
| return msg.parts | ||
| .filter((p): p is TextUIPart => p.type === 'text') |
| const [input, setInput] = useState(''); | ||
| const [isTyping, setIsTyping] = useState(false); | ||
| const [activeTools, setActiveTools] = useState<string[]>([]); | ||
| const bottomRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| const transport = useMemo( | ||
| () => | ||
| new DefaultChatTransport({ | ||
| api: `${API_BASE}/api/chat`, | ||
| headers: () => ({ Authorization: `Bearer ${Cookies.get('access_token') ?? ''}` }), |
| const bottomRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| const transport = useMemo( |
Ensures lockfile drift is caught in CI. Lighthouse already had this; adds it to checks, unit-tests, api-tests, migrations, sync-guides-r2, and e2e-tests. Skips copilot-setup-steps (needs to write lockfile). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/components/screens/gear-inventory-screen.tsx (1)
73-78:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd accessible label for search input.
The search input lacks an accessible label. Screen readers require either a
<label>element oraria-labelattribute to announce the input's purpose.♿ Proposed fix to add aria-label
<input + aria-label="Search gear" value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search gear…"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/components/screens/gear-inventory-screen.tsx` around lines 73 - 78, The search input currently lacks an accessible label; update the input in the GearInventoryScreen so screen readers can announce it by adding an appropriate accessible label (either add a visually-hidden <label> tied to the input or add aria-label="Search gear" directly on the input element that uses value={search} and onChange={(e) => setSearch(e.target.value)}); ensure the label text clearly describes the field (e.g., "Search gear") and that it references the same input (or use aria-labelledby if you create a separate label element).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/components/screens/ai-screen.tsx`:
- Line 36: The headers factory currently always sends Authorization: Bearer
<empty> when Cookies.get('access_token') is missing; update the headers function
(the headers: () => { ... } entry) to check Cookies.get('access_token') first
and only include the Authorization header if a non-empty token exists, otherwise
return an empty headers object so no Authorization header is sent.
---
Outside diff comments:
In `@apps/web/components/screens/gear-inventory-screen.tsx`:
- Around line 73-78: The search input currently lacks an accessible label;
update the input in the GearInventoryScreen so screen readers can announce it by
adding an appropriate accessible label (either add a visually-hidden <label>
tied to the input or add aria-label="Search gear" directly on the input element
that uses value={search} and onChange={(e) => setSearch(e.target.value)});
ensure the label text clearly describes the field (e.g., "Search gear") and that
it references the same input (or use aria-labelledby if you create a separate
label element).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 92d8172d-cc2a-45b1-b118-ec09b03ae4bf
📒 Files selected for processing (12)
.github/workflows/api-tests.yml.github/workflows/checks.yml.github/workflows/copilot-setup-steps.yml.github/workflows/e2e-tests.yml.github/workflows/lighthouse.yml.github/workflows/migrations.yml.github/workflows/release-ios.yml.github/workflows/sync-guides-r2.yml.github/workflows/unit-tests.ymlapps/web/components/screens/ai-screen.tsxapps/web/components/screens/gear-inventory-screen.tsxapps/web/package.json
💤 Files with no reviewable changes (3)
- .github/workflows/lighthouse.yml
- .github/workflows/release-ios.yml
- .github/workflows/copilot-setup-steps.yml
| () => | ||
| new DefaultChatTransport({ | ||
| api: `${API_BASE}/api/chat`, | ||
| headers: () => ({ Authorization: `Bearer ${Cookies.get('access_token') ?? ''}` }), |
There was a problem hiding this comment.
Skip the Authorization header when there's no access token.
When the access_token cookie is missing or cleared, this still sends Authorization: Bearer (with an empty token). That's a guaranteed-401 request and forces an extra preflight on cross-origin (API_BASE may differ from the web origin). Cleaner to omit the header entirely so the server can fall back to anonymous handling (or return a more meaningful unauthenticated response).
🔧 Proposed fix
- headers: () => ({ Authorization: `Bearer ${Cookies.get('access_token') ?? ''}` }),
+ headers: () => {
+ const token = Cookies.get('access_token');
+ return token ? { Authorization: `Bearer ${token}` } : {};
+ },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| headers: () => ({ Authorization: `Bearer ${Cookies.get('access_token') ?? ''}` }), | |
| headers: () => { | |
| const token = Cookies.get('access_token'); | |
| return token ? { Authorization: `Bearer ${token}` } : {}; | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/components/screens/ai-screen.tsx` at line 36, The headers factory
currently always sends Authorization: Bearer <empty> when
Cookies.get('access_token') is missing; update the headers function (the
headers: () => { ... } entry) to check Cookies.get('access_token') first and
only include the Authorization header if a non-empty token exists, otherwise
return an empty headers object so no Authorization header is sent.
…hemas - Replace legacy JWT auth in apps/trails with better-auth/react client (authClient.signIn/signUp/signOut/requestPasswordReset) - Add apps/trails/lib/auth-client.ts with nextCookies() SSR plugin - Rewrite AuthGate/VerifyEmail/useAuth to use Better Auth session - Fix AdminUserItem/Pack/CatalogItem TypeBox schemas to match DB selects (remove fields not selected, add nullable fields that are selected) - Align apps/admin/lib/api.ts interfaces with corrected TypeBox schemas - Fix UserSchema.id: z.number() → z.string() (Better Auth uses UUID text pk) - Fix mapToUser/applySessionUser to include emailVerified/createdAt/updatedAt - Fix ItemReviews.tsx null guards for nullable review fields - Fix web/lib/types.ts id fields: number → string throughout - Fix web/lib/data.ts mock IDs to match string type - Add queryKeys.osm used by admin trails dashboard page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Remove useLoginMutation/useRegisterMutation from packages/app (legacy JWT endpoints no longer exist; auth is handled by Better Auth) - Add better-auth to apps/web dependencies - Create apps/web/lib/auth-client.ts with nextCookies() SSR plugin - Rewrite apps/web/app/auth/page.tsx to use authClient.signIn/signUp - Update apps/web/lib/api.ts to get session token from Better Auth - Replace clearTokens() in profile-screen with authClient.signOut() - Fix adminFetch as Promise<T> cast (res.json() returns any, no cast needed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
… state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Package ships source .tsx files with type errors that skipLibCheck can't suppress (only applies to .d.ts files). Add // @ts-nocheck to the 3 affected files via bun patch. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
feat(web): wire gear-inventory and ai-chat screens to real API
Summary
mockInventorywith real data fromusePacks(); deduplicates items across packs by ID, filters deleted items, derives categories dynamically, shows skeleton loading + empty stateMOCK_RESPONSESwithDefaultChatTransportfromaihitting/api/chat; usessendMessage/statusfrom@ai-sdk/reactv3 API; text extracted fromUIMessage.parts@ai-sdk/reactandaitoapps/webdependenciesShopping list has no backend API — left as local state (confirmed by grepping API routes).
Post-Deploy Monitoring & Validation
No additional operational monitoring required: both changes are pure frontend; they replace mock data with real API calls using existing endpoints already monitored in production.
Summary by CodeRabbit
New Features
Improvements