Skip to content

Development -> main#2315

Merged
andrew-bierman merged 31 commits into
mainfrom
development
Apr 26, 2026
Merged

Development -> main#2315
andrew-bierman merged 31 commits into
mainfrom
development

Conversation

@andrew-bierman

@andrew-bierman andrew-bierman commented Apr 26, 2026

Copy link
Copy Markdown
Collaborator

Description

Closes #

Type of change

  • 🐛 Bug fix
  • ✨ New feature
  • ♻️ Refactor / code improvement
  • 📝 Documentation update
  • 🔧 CI / configuration change
  • ⬆️ Dependency update
  • 🗄️ Database migration

Area(s) affected

  • Mobile app (apps/expo)
  • API / Backend (packages/api)
  • Landing page (apps/landing)
  • Guides site (apps/guides)
  • CI / CD (.github/)

Testing

  • Added / updated unit tests
  • Manually tested on iOS
  • Manually tested on Android
  • Manually tested on Web
  • API endpoints verified (e.g. curl or Postman)

Screenshots / recordings

Pre-merge checklist

  • bun format && bun lint passes with no errors
  • bun check-types passes with no errors
  • No new secrets or credentials are committed
  • Database migration included (if schema changed)
  • Feature flag added (if this is a new feature)
  • PR title follows conventional commits (feat:, fix:, chore:, etc.)

Copilot AI and others added 30 commits April 24, 2026 03:13
Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/ef4c31bb-e06b-4c3d-90d4-0756bb8ca977

Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
- Add useCFAccessIdentity() hook to cfAccess.ts backed by useQuery with
  staleTime/gcTime=Infinity and retry=false so a null result is treated as
  "not behind CF Access" rather than a transient failure
- Create lib/queryKeys.ts as a centralised registry for all admin query keys
  (admin, platform, catalogAnalytics, cfAccessIdentity)
- Refactor auth-guard.tsx: replace manual canceled-flag useEffect pattern with
  useCFAccessIdentity(); render null while pending or when neither CF session
  nor stored token exists
- Refactor login/page.tsx: replace useState<boolean|null> + raw useEffect with
  useCFAccessIdentity(); Local dev badge now keyed on !cfPending && !cfIdentity
- Update all dashboard pages (page, users, packs, catalog), edit-catalog-dialog,
  and analytics hooks to use queryKeys.* instead of inline string arrays
refactor(admin): migrate CF Access identity fetch to TanStack Query
refactor: use zod for CF Access payload validation
Uses the CF Workers built-in Rate Limiting API (TOKEN_RATE_LIMITER
binding) so brute-force attempts against the Basic→JWT token exchange
endpoint are blocked at the edge before credentials are evaluated.

Binding is optional — absent in local dev/test so nothing breaks
without a CF environment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
… store

- cfAccess.ts: suppress useMaxParams for verifyCFAccessRequest (3
  semantically distinct params, no benefit to wrapping)
- TripWeatherDetailsScreen: replace any with WeatherApiForecastResponse /
  HourWeather / ForecastDay from existing weather types
- packWeightHistory: fix unused biome-ignore by extracting endpoint
  variable so suppress is adjacent to the actual any cast

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Adds unit tests for verifyCFAccessRequest (cfAccess.ts), integration
tests for adminAuthGuard branching (CF Access configured vs. absent),
and round-trip tests for issueAdminJwt/verifyAdminJwt via HTTP.

Also fixes a pre-existing EvalError in the CF Workers vitest pool:
Elysia 1.4+ uses new Function() (AOT compilation) at module init time,
which workerd/QuickJS disallows outside a request handler. Fix mocks
the Elysia constructor via Proxy to force aot:false and stubs
CloudflareAdapter.beforeCompile to a no-op, unblocking all integration
tests from the EvalError that had been affecting the full suite.
- Replace 9 raw fetch() calls in useAuthActions with typed Treaty calls
  (login, register, google, apple, logout, forgot/reset-password,
  verify-email, resend-verification). Removes the clientEnvs URL
  dependency from auth hooks entirely; all auth flows now go through
  the same authFetcher path as the rest of the app.
- Add extractAuthError helper to surface server error messages from
  Treaty error.value (preserves user-facing messages from API responses).
- Fix analyze_pack_weight in MCP tools: replace inline 'as { items?:...'
  cast with a named PackDetailResponse interface, making the shape
  explicit and reusable.

https://claude.ai/code/session_01YNwXsXiw34xFb75bnWDcig
…arsing

Replace every `as object[] | null`, `as object | null`, `as any`, and
`as unknown as T` cast with validated Zod schema parsing sourced from the
API package's canonical schemas.

Schema changes (packages/api/src/schemas/):
- packs.ts: add localCreatedAt/localUpdatedAt/templateId to PackSchema;
  export CreatePackBodySchema, UpdatePackBodySchema,
  PackWeightHistoryResponseSchema, CreatePackWeightHistoryBodySchema
- trips.ts (new): TripSchema, TripLocationSchema, create/update body schemas
- trailConditions.ts (new): TrailConditionReportSchema with enum types

Legend-State stores — all `as object[]`/`as object | null` casts removed:
- packs.ts: explicit body mapping for create/update; parse with
  PackWithWeightsSchema (fixes three `as any` casts)
- packItems.ts: parse list with PackWithWeightsSchema.array().flatMap();
  parse create/update responses with PackItemSchema
- packWeightHistory.ts: explicit body mapping, parse with
  PackWeightHistoryResponseSchema (fixes `as any` body cast)
- trips.ts: parse with TripSchema
- trailConditionReports.ts: parse with TrailConditionReportSchema
- packTemplates.ts: parse with PackTemplateWithItemsSchema / PackTemplateSchema
- packTemplateItems.ts: parse with PackTemplateWithItemsSchema.flatMap /
  PackTemplateItemSchema (removes two `as unknown as PackTemplateItem` casts)

Hooks and services — all `as unknown as T` casts removed:
- usePackDetailsFromApi: PackWithWeightsSchema.parse
- usePackItemDetailsFromApi: PackItemSchema.parse
- weatherService: LocationSearchResponseSchema.parse + WeatherAPIForecastResponseSchema.parse;
  drop local WeatherApiForecastResponse type in favour of schema-derived type
- useCatalogItems: CatalogItemsResponseSchema.parse (drops local PaginatedCatalogItemsResponse import)
- useWildlifeIdentification: zodGuard from @packrat/guards validates
  { results: unknown[] } shape before casting items to IdentificationResult[]

https://claude.ai/code/session_01YNwXsXiw34xFb75bnWDcig
- Move @packrat/api/schemas/packs import before expo-app/* in packItems.ts
- Break long import lines in packTemplates.ts and packTemplateItems.ts
- Break long line in useWildlifeIdentification.ts (error throw)
- Break long line in useAuthActions.ts (register.post call)

https://claude.ai/code/session_01YNwXsXiw34xFb75bnWDcig
- Break .array().parse().flatMap() chain in packItems and packTemplateItems
  (Biome wraps method chains that exceed 100 chars)
- Reorder named imports in weatherService to alphabetical order by identifier
  (type WeatherAPIForecastResponse between L and WeatherAPIForecastResponseSchema)

https://claude.ai/code/session_01YNwXsXiw34xFb75bnWDcig
Switch from PROVIDER_GOOGLE to platform-specific provider: Google Maps
on Android, Apple Maps (default) on iOS. No package change needed —
react-native-maps 1.20.1 is already the Expo SDK 54 pinned version.
feat(expo): use Apple Maps on iOS for trip map views
POST /api/alltrails/preview scrapes og:title, og:description, og:image
from AllTrails URLs server-side. MCP tool preview_alltrails_url exposes
this to agents for trip/pack enrichment.
13 tests covering request validation, OG metadata extraction, timeout (504),
network failure (502), upstream 4xx (502), missing og:title (422), and
redirect-outside-alltrails.com (400).
feat(api): AllTrails OG preview endpoint and MCP tool
…ypes-Kke59

refactor(admin): migrate CF Access identity fetch to TanStack Query
…stallation

chore: switch Dependabot to native `bun` ecosystem
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@andrew-bierman has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 56 minutes and 0 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 56 minutes and 0 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45774fb0-6d88-4c75-8d78-3dae6cf3e261

📥 Commits

Reviewing files that changed from the base of the PR and between 68aa6cc and c2ffc98.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (52)
  • .github/dependabot.yml
  • apps/admin/app/dashboard/catalog/page.tsx
  • apps/admin/app/dashboard/packs/page.tsx
  • apps/admin/app/dashboard/page.tsx
  • apps/admin/app/dashboard/users/page.tsx
  • apps/admin/app/login/page.tsx
  • apps/admin/components/auth-guard.tsx
  • apps/admin/components/edit-catalog-dialog.tsx
  • apps/admin/hooks/use-catalog-analytics.ts
  • apps/admin/hooks/use-platform-analytics.ts
  • apps/admin/lib/cfAccess.ts
  • apps/admin/lib/queryKeys.ts
  • apps/expo/app/(app)/trip/location-search.tsx
  • apps/expo/features/auth/hooks/useAuthActions.ts
  • apps/expo/features/catalog/hooks/useCatalogItems.ts
  • apps/expo/features/pack-templates/store/packTemplateItems.ts
  • apps/expo/features/pack-templates/store/packTemplates.ts
  • apps/expo/features/packs/hooks/usePackDetailsFromApi.ts
  • apps/expo/features/packs/hooks/usePackItemDetailsFromApi.ts
  • apps/expo/features/packs/store/packItems.ts
  • apps/expo/features/packs/store/packWeightHistory.ts
  • apps/expo/features/packs/store/packs.ts
  • apps/expo/features/trail-conditions/store/trailConditionReports.ts
  • apps/expo/features/trips/screens/TripDetailScreen.tsx
  • apps/expo/features/trips/screens/TripWeatherDetailsScreen.tsx
  • apps/expo/features/trips/store/trips.ts
  • apps/expo/features/weather/hooks/useLocationRefresh.ts
  • apps/expo/features/weather/hooks/useLocationSearch.ts
  • apps/expo/features/weather/lib/weatherService.ts
  • apps/expo/features/weather/screens/LocationPreviewScreen.tsx
  • apps/expo/features/wildlife/hooks/useWildlifeIdentification.ts
  • packages/api/src/__test-stubs__/elysia-env.ts
  • packages/api/src/middleware/__tests__/cfAccess.test.ts
  • packages/api/src/middleware/cfAccess.ts
  • packages/api/src/routes/admin/index.ts
  • packages/api/src/routes/alltrails.ts
  • packages/api/src/routes/index.ts
  • packages/api/src/schemas/packs.ts
  • packages/api/src/schemas/trailConditions.ts
  • packages/api/src/schemas/trips.ts
  • packages/api/src/schemas/weather.ts
  • packages/api/src/utils/env-validation.ts
  • packages/api/test/admin-auth-guard.test.ts
  • packages/api/test/admin-jwt.test.ts
  • packages/api/test/alltrails.test.ts
  • packages/api/test/setup.ts
  • packages/api/test/utils/test-helpers.ts
  • packages/api/vitest.config.ts
  • packages/api/wrangler.jsonc
  • packages/mcp/src/index.ts
  • packages/mcp/src/tools/packs.ts
  • packages/mcp/src/tools/trails.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file api ci/cd mobile labels Apr 26, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Deploying packrat-guides with  Cloudflare Pages  Cloudflare Pages

Latest commit: c2ffc98
Status: ✅  Deploy successful!
Preview URL: https://6fd6bf7c.packrat-guides-6gq.pages.dev
Branch Preview URL: https://development.packrat-guides-6gq.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
packrat-api-dev c2ffc98 Apr 26 2026, 09:02 PM

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for API Unit Tests Coverage (./packages/api)

Status Category Percentage Covered / Total
🔵 Lines 74.9% 582 / 777
🔵 Statements 74.9% (🎯 65%) 582 / 777
🔵 Functions 95.91% 47 / 49
🔵 Branches 88.23% 270 / 306
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/api/src/__test-stubs__/elysia-env.ts 0% 100% 100% 0% 15
Generated in workflow #806 for commit c2ffc98 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for Expo Unit Tests Coverage (./apps/expo)

Status Category Percentage Covered / Total
🔵 Lines 81.38% 516 / 634
🔵 Statements 81.38% (🎯 75%) 516 / 634
🔵 Functions 92.85% 52 / 56
🔵 Branches 92.55% 199 / 215
File CoverageNo changed files found.
Generated in workflow #806 for commit c2ffc98 by the Vitest Coverage Report Action

@cloudflare-workers-and-pages

Copy link
Copy Markdown
Contributor

Deploying packrat-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: c2ffc98
Status: ✅  Deploy successful!
Preview URL: https://abb11c60.packrat-landing.pages.dev
Branch Preview URL: https://development.packrat-landing.pages.dev

View logs

@andrew-bierman andrew-bierman marked this pull request as ready for review April 26, 2026 21:14
Copilot AI review requested due to automatic review settings April 26, 2026 21:14
@andrew-bierman andrew-bierman merged commit 0b0e786 into main Apr 26, 2026
17 of 19 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds stronger admin authentication + rate limiting, introduces an AllTrails OpenGraph preview endpoint/tool, and refactors clients to use shared Zod schemas (plus substantial test infrastructure updates).

Changes:

  • Add /api/alltrails/preview route + MCP tool to fetch AllTrails OG metadata, with validation and tests.
  • Harden admin auth by verifying Cloudflare Access JWTs, removing spoofable header reliance, adding token endpoint rate limiting, and adding integration/unit tests.
  • Refactor Expo/Admin clients to rely more on shared @packrat/api Zod schemas and centralized query keys; adjust test setup to avoid Elysia AOT/eval in workerd.

Reviewed changes

Copilot reviewed 52 out of 53 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/mcp/src/tools/trails.ts Adds MCP tool for AllTrails preview endpoint.
packages/mcp/src/tools/packs.ts Replaces inline response typing with a named response interface + typed API call.
packages/mcp/src/index.ts Registers the new trail tools.
packages/api/wrangler.jsonc Adds rate limiting binding configuration (incl. dev env).
packages/api/vitest.config.ts Adds test alias stub for Elysia env/AOT behavior in workerd.
packages/api/test/utils/test-helpers.ts Adds helper to build CF Access-like JWTs for tests.
packages/api/test/setup.ts Adds global mocks to prevent eval/AOT + globally mocks CF Access verifier.
packages/api/test/alltrails.test.ts Adds tests for AllTrails preview endpoint (validation, success, upstream errors).
packages/api/test/admin-jwt.test.ts Adds round-trip tests for admin JWT issuance/verification via HTTP layer.
packages/api/test/admin-auth-guard.test.ts Adds integration tests for adminAuthGuard branching (CF Access vs fallback).
packages/api/src/utils/env-validation.ts Adds optional TOKEN_RATE_LIMITER binding to env validation/types.
packages/api/src/schemas/weather.ts Tightens WeatherAPI schemas (lat/lon numbers, required fields).
packages/api/src/schemas/trips.ts Adds shared Trip schemas/types for clients.
packages/api/src/schemas/trailConditions.ts Adds shared Trail Condition Report schemas/types for clients.
packages/api/src/schemas/packs.ts Extends pack schemas and adds request/response schemas for packs + weight history.
packages/api/src/routes/index.ts Registers AllTrails routes.
packages/api/src/routes/alltrails.ts Implements AllTrails OG preview scraping endpoint with redirect/domain checks.
packages/api/src/routes/admin/index.ts Adds token rate limiting; removes HTML admin UI; adds search params for list endpoints.
packages/api/src/middleware/cfAccess.ts Switches CF Access verification to schema-validated payload and stronger typing.
packages/api/src/middleware/tests/cfAccess.test.ts Adds unit tests for CF Access JWT verification without network JWKS fetch.
packages/api/src/test-stubs/elysia-env.ts Provides Elysia env stub to force ELYSIA_AOT=false in tests.
bun.lock Bumps workspace package versions and adds guards dependency to admin app.
apps/expo/features/wildlife/hooks/useWildlifeIdentification.ts Adds runtime response guard for wildlife identify endpoint.
apps/expo/features/weather/screens/LocationPreviewScreen.tsx Uses formatted weather output but now relies on type assertions.
apps/expo/features/weather/lib/weatherService.ts Parses API responses with shared schemas; adjusts formatting and icon typing.
apps/expo/features/weather/hooks/useLocationSearch.ts Uses formatted weather output but now relies on type assertions.
apps/expo/features/weather/hooks/useLocationRefresh.ts Uses formatted weather output but now relies on type assertions.
apps/expo/features/trips/store/trips.ts Validates trip responses with shared TripSchema.
apps/expo/features/trips/screens/TripWeatherDetailsScreen.tsx Replaces any weather state with shared API type.
apps/expo/features/trips/screens/TripDetailScreen.tsx Removes explicit Google provider from maps.
apps/expo/features/trail-conditions/store/trailConditionReports.ts Validates trail condition report responses with shared schema.
apps/expo/features/packs/store/packs.ts Validates pack responses and explicitly shapes create/update request bodies.
apps/expo/features/packs/store/packWeightHistory.ts Validates weight history responses and explicitly shapes create request body.
apps/expo/features/packs/store/packItems.ts Validates pack item responses with shared schemas.
apps/expo/features/packs/hooks/usePackItemDetailsFromApi.ts Validates pack item response with shared schema.
apps/expo/features/packs/hooks/usePackDetailsFromApi.ts Validates pack details response with shared schema.
apps/expo/features/pack-templates/store/packTemplates.ts Validates pack template responses with shared schemas.
apps/expo/features/pack-templates/store/packTemplateItems.ts Validates pack template item responses with shared schemas.
apps/expo/features/catalog/hooks/useCatalogItems.ts Validates catalog responses with shared schema.
apps/expo/features/auth/hooks/useAuthActions.ts Refactors auth actions to use apiClient instead of manual fetch + adds error extraction.
apps/expo/app/(app)/trip/location-search.tsx Removes explicit Google provider from maps.
apps/admin/lib/queryKeys.ts Adds centralized TanStack Query key registry.
apps/admin/lib/cfAccess.ts Adds Zod validation + TanStack Query hook for CF Access identity.
apps/admin/hooks/use-platform-analytics.ts Migrates platform analytics hooks to centralized queryKeys.
apps/admin/hooks/use-catalog-analytics.ts Migrates catalog analytics hooks to centralized queryKeys.
apps/admin/components/edit-catalog-dialog.tsx Uses centralized queryKeys for cache invalidation.
apps/admin/components/auth-guard.tsx Switches CF Access detection to TanStack Query hook.
apps/admin/app/login/page.tsx Switches CF Access detection to TanStack Query hook.
apps/admin/app/dashboard/users/page.tsx Uses centralized queryKeys (and adds search param-based keying).
apps/admin/app/dashboard/page.tsx Uses centralized queryKeys for dashboard queries.
apps/admin/app/dashboard/packs/page.tsx Uses centralized queryKeys (and adds search param-based keying).
apps/admin/app/dashboard/catalog/page.tsx Uses centralized queryKeys (and adds search param-based keying).
.github/dependabot.yml Switches Dependabot update entries from npm to bun ecosystems.
Comments suppressed due to low confidence (1)

apps/expo/features/weather/lib/weatherService.ts:64

  • WeatherAPILocationSchema.localtime is optional, but formatWeatherData always calls toLocaleTimeString on a Date derived from it. If localtime is missing/empty, new Date(...) becomes an invalid date and toLocaleTimeString will throw a RangeError. Either make localtime required in the shared schema (if the API guarantees it) or add a safe fallback (e.g., use localtime_epoch, or new Date() when missing).
  const localTime = new Date(location.localtime ?? '');
  const formattedTime = localTime.toLocaleTimeString('en-US', {
    hour: 'numeric',
    minute: '2-digit',
    hour12: true,
  });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

mutationFn: (data: Parameters<typeof updateCatalogItem>[1]) => updateCatalogItem(item.id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'catalog'] });
queryClient.invalidateQueries({ queryKey: queryKeys.admin.catalog() });

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

This invalidation uses queryKeys.admin.catalog() which currently yields ['admin','catalog', undefined] and won't match catalog queries keyed with queryKeys.admin.catalog(q) / catalog(5). That can leave the list stale after edits. Prefer invalidating the root key (e.g. ['admin','catalog']) or make queryKeys.admin.catalog omit the third element when undefined.

Suggested change
queryClient.invalidateQueries({ queryKey: queryKeys.admin.catalog() });
queryClient.invalidateQueries({ queryKey: ['admin', 'catalog'] });

Copilot uses AI. Check for mistakes.
Comment on lines 84 to 86
temp: Math.round(hour.temp_f),
icon: getIconNameFromCode(hour.condition.code, hour.is_day),
icon: getIconNameFromCode(hour.condition.code, hour.is_day) as string,
weatherCode: hour.condition.code,

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

getWeatherIconName already returns MaterialIconName, but these as string casts widen the type and make the formatWeatherData return value incompatible with WeatherLocation (which is why downstream code is forced to use as unknown as WeatherLocation). Dropping the casts and typing formatWeatherData to return WeatherLocation would let the compiler verify shape compatibility and eliminate the unsafe assertions in screens/hooks.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to 69
const newLocation = formattedData as unknown as WeatherLocation;

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

This as unknown as WeatherLocation cast bypasses compile-time verification of the formatWeatherData output. If WeatherLocation is the intended shape, consider updating formatWeatherData to return WeatherLocation (and aligning any field/icon types) so this can be a normal assignment without assertions.

Copilot uses AI. Check for mistakes.
Comment on lines 19 to 22
const formattedData = formatWeatherData(weatherData);

updateLocation(locationId, {
temperature: formattedData.temperature,
condition: formattedData.condition,
time: formattedData.time,
highTemp: formattedData.highTemp,
lowTemp: formattedData.lowTemp,
alerts: formattedData.alerts,
details: formattedData.details,
hourlyForecast: formattedData.hourlyForecast,
dailyForecast: formattedData.dailyForecast,
});
updateLocation(locationId, formattedData as unknown as Partial<WeatherLocation>);

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

formattedData as unknown as Partial<WeatherLocation> bypasses compile-time checking of the refresh payload shape. Prefer typing formatWeatherData to return WeatherLocation (or a well-defined subset) so updateLocation receives a type-safe object without assertions.

Copilot uses AI. Check for mistakes.
mutationFn: () => deletePack(pack.id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'packs'] });
queryClient.invalidateQueries({ queryKey: queryKeys.admin.packs() });

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

This invalidation uses queryKeys.admin.packs() which currently yields ['admin','packs', undefined]. That won't invalidate queries keyed with queryKeys.admin.packs(q) (or the dashboard packs(5)), so pack lists can remain stale after deletion. Use a root key (e.g. ['admin','packs']) or adjust queryKeys.admin.packs to omit the third element when undefined.

Suggested change
queryClient.invalidateQueries({ queryKey: queryKeys.admin.packs() });
queryClient.invalidateQueries({ queryKey: ['admin', 'packs'] });

Copilot uses AI. Check for mistakes.
mutationFn: () => deleteCatalogItem(item.id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'catalog'] });
queryClient.invalidateQueries({ queryKey: queryKeys.admin.catalog() });

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

This invalidation uses queryKeys.admin.catalog() which currently yields ['admin','catalog', undefined]. That won't invalidate catalog queries keyed with queryKeys.admin.catalog(q) / catalog(5), so the table may not refresh after a delete. Use a root key (e.g. ['admin','catalog']) or adjust queryKeys.admin.catalog to omit the third element when undefined.

Suggested change
queryClient.invalidateQueries({ queryKey: queryKeys.admin.catalog() });
queryClient.invalidateQueries({ queryKey: ['admin', 'catalog'] });

Copilot uses AI. Check for mistakes.
Comment on lines +59 to 60
setWeatherData(formattedData as unknown as WeatherLocation);

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

The as unknown as WeatherLocation assertion hides mismatches between formatWeatherData's return type and the WeatherLocation interface, so type drift here won't be caught at compile time. Prefer making formatWeatherData explicitly return WeatherLocation (and fixing any field/icon type mismatches) so this assignment is type-safe without assertions.

Copilot uses AI. Check for mistakes.
Comment on lines 48 to 51
const formattedData = formatWeatherData(weatherData);

updateLocation(location.id, {
temperature: formattedData.temperature,
condition: formattedData.condition,
time: formattedData.time,
highTemp: formattedData.highTemp,
lowTemp: formattedData.lowTemp,
alerts: formattedData.alerts,
details: formattedData.details,
hourlyForecast: formattedData.hourlyForecast,
dailyForecast: formattedData.dailyForecast,
});
updateLocation(location.id, formattedData as unknown as Partial<WeatherLocation>);
}

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

Same as above: the as unknown as Partial<WeatherLocation> cast prevents the compiler from catching shape drift between formatWeatherData and what updateLocation expects. Consider returning/deriving a typed Partial<WeatherLocation> instead of asserting through unknown.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +17
users: (limitOrQuery?: number | string) => ['admin', 'users', limitOrQuery] as const,
packs: (limitOrQuery?: number | string) => ['admin', 'packs', limitOrQuery] as const,
catalog: (limitOrQuery?: number | string) => ['admin', 'catalog', limitOrQuery] as const,

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

users/packs/catalog query keys always include a third element, so calling queryKeys.admin.users() (or packs/catalog) produces ['admin','users', undefined]. That key will not match queries keyed as ['admin','users','search'] or ['admin','users',5] during invalidateQueries, so list pages can stay stale after mutations. Consider returning ['admin','users'] when the argument is undefined (or add separate usersRoot/packsRoot/catalogRoot constants for invalidation).

Suggested change
users: (limitOrQuery?: number | string) => ['admin', 'users', limitOrQuery] as const,
packs: (limitOrQuery?: number | string) => ['admin', 'packs', limitOrQuery] as const,
catalog: (limitOrQuery?: number | string) => ['admin', 'catalog', limitOrQuery] as const,
users: (limitOrQuery?: number | string) =>
limitOrQuery === undefined ? (['admin', 'users'] as const) : (['admin', 'users', limitOrQuery] as const),
packs: (limitOrQuery?: number | string) =>
limitOrQuery === undefined ? (['admin', 'packs'] as const) : (['admin', 'packs', limitOrQuery] as const),
catalog: (limitOrQuery?: number | string) =>
limitOrQuery === undefined ? (['admin', 'catalog'] as const) : (['admin', 'catalog', limitOrQuery] as const),

Copilot uses AI. Check for mistakes.
mutationFn: () => deleteUser(user.id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });
queryClient.invalidateQueries({ queryKey: queryKeys.admin.users() });

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

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

This invalidation uses queryKeys.admin.users() which currently yields ['admin','users', undefined]. That won't invalidate existing user list queries keyed with queryKeys.admin.users(q) (or the dashboard users(5)), so the UI may not refresh after deleting a user. Use a root key (e.g. ['admin','users']) or adjust queryKeys.admin.users to omit the third element when undefined.

Suggested change
queryClient.invalidateQueries({ queryKey: queryKeys.admin.users() });
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] });

Copilot uses AI. Check for mistakes.
andrew-bierman added a commit that referenced this pull request May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api ci/cd dependencies Pull requests that update a dependency file mobile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants