Skip to content

refactor: extract @packrat/db and @packrat/schemas from @packrat/api#2423

Merged
andrew-bierman merged 67 commits into
developmentfrom
refactor/extract-db-schemas-packages
May 17, 2026
Merged

refactor: extract @packrat/db and @packrat/schemas from @packrat/api#2423
andrew-bierman merged 67 commits into
developmentfrom
refactor/extract-db-schemas-packages

Conversation

@andrew-bierman
Copy link
Copy Markdown
Collaborator

@andrew-bierman andrew-bierman commented May 14, 2026

Summary

  • @packrat/db — new package: Drizzle table definitions, raw as const constants (no Zod), ValidationError interface, drizzle-zod generated schemas. Zero circular deps — no Zod dependency on the Drizzle side.
  • @packrat/schemas — new package: all route-level Zod schemas, depends on @packrat/db for Drizzle types and raw constants.
  • @packrat/api — untouched externally: re-export shims in src/db/schema.ts, src/db/zod-schemas.ts, src/types/constants.ts, src/types/validation.ts, and all src/schemas/*.ts mean every existing import path continues to resolve unchanged. drizzle.config.ts and the migration infra are 100% untouched.
  • @packrat/app entities now import from @packrat/schemas directly (no more leaking through the CF Workers package).
  • Duplicate ErrorResponseSchema/SuccessResponseSchema consolidated into @packrat/schemas/shared.
  • Pre-existing type mismatches surfaced and fixed: User.avatarimage ?? avatarUrl, PackPackWithItems for the compute-pack utility, test factories updated with all required Drizzle model fields.

Testing

  • bun check-types from repo root: 0 errors
  • bun lint (Biome): passes (1 pre-existing warning unrelated to this PR)
  • Pre-push hooks: no circular deps, no catalog violations, no unsafe casts

Post-Deploy Monitoring & Validation

No additional operational monitoring required: this is a pure type-system/package refactor with no runtime behavior changes. All existing import paths are preserved via re-export shims; the Drizzle schema and migrations are untouched.


Compound Engineered 🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Guide reading time now uses numeric values.
    • Avatar display reliably shows either image or initials.
  • Improvements

    • Seasonal suggestions now include location and season.
    • Upload responses include additional file metadata (object key and public URL).
    • API responses gain stricter validation and more consistent error handling.
  • Refactor

    • Shared schemas/constants extracted and consolidated for greater consistency.

Review Change Stack

andrew-bierman and others added 30 commits May 7, 2026 23:19
…ssue

The Drizzle ORM .set({ status: 'completed' }) with the neon-http driver
appears to fail silently (triggering the catch block which then sets
status='failed'), even though the identical pattern with 'failed' works.

Switch to a raw sql`UPDATE ... SET status = 'completed'::etl_job_status`
to match the pattern already used in updateEtlJobProgress, bypassing any
Drizzle/neon-http enum serialization difference.

Also isolate the completion write in its own try-catch so a transient
failure here logs the error and leaves the job 'running' (for Reset Stuck)
rather than cascading to 'failed'.

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

Co-Authored-By: Claude <noreply@anthropic.com>
The weight NOT NULL constraint on catalog_items was causing ETL job failures
for any item missing weight data (common for clothing/footwear brands). The
CatalogItemValidator explicitly marks weight as optional, but the DB would
reject the INSERT, causing processValidItemsBatch's fallback to also fail,
which propagated to the outer catch and set status='failed'.

Migration 0037 drops NOT NULL from weight and weight_unit on catalog_items.
Adds full ETL integration test suite confirming: happy path completes,
no-weight items don't fail, invalid-only runs still complete, exact/multi-batch
row counts work, and the embedding fallback doesn't throw to the outer caller.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Replaces the handwritten 0037_nullable_catalog_weight.sql with the
drizzle-kit generated equivalent — same ALTER TABLE statements but
now tracked in the drizzle journal with the proper snapshot.

Social feed table CREATE statements were stripped from the generated
output because 0033_social_feed_tables.sql was applied manually and
not tracked in the journal, causing drizzle-kit to emit duplicate DDL.

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

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

Making catalog_items.weight/weight_unit nullable caused a TypeScript error
in packService.ts — pack items require non-null weight. Fall back to 0/'g'
so the AI-generated pack flow still compiles; user can edit after generation.
… 0037 conflict

Main added 0037_trips_trail_osm_id after this branch was cut. Kept main's
journal/snapshot; will regenerate weight-nullable migration at correct number.
…ight + regenerate weight migration

CORS: admin scoped cors was silently dropping Access-Control-Allow-Origin on
preflight (two stacked cors plugins conflicted — root sets credentials:false/*,
admin sets credentials:true/specific-origin, header got dropped). Switch to
origin function so Elysia reflects the exact origin back; bypass auth guard for
OPTIONS preflights. Fixes admin app CORS errors from https://admin.packratai.com.

Migration: regenerate weight/weight_unit nullable migration as 0047 — main
merged 0037–0046 after this branch was cut.
- admin api.ts: double-cast Eden Treaty responses to PaginatedResponse<T>
  (treaty infers wide union types that don't overlap with the interface)
- packages/app user queries: remove deleted auth hooks (useLoginMutation,
  useRegisterMutation) that called Better Auth routes not in Elysia; redirect
  useCurrentUser to client.user.profile.get()
- apps/web auth page: implement login/register locally via Better Auth REST
  endpoints instead of importing from @packrat/app
- apps/trails useAuth: replace (apiClient as any).auth.* calls with typed
  trailsAuthClient (createAuthClient from better-auth/react); add auth-client.ts
- apps/trails UserInfoSchema: id is UUID string not number (Better Auth)
- apps/web types: Post.userId and PostAuthor.id are UUID strings not numbers
- apps/web data.ts: update mock post IDs to match string type

Co-Authored-By: Claude <noreply@anthropic.com>
- admin api.ts: add safe-cast annotations on Eden Treaty PaginatedResponse
  double casts; res.json() returns Promise<any> so no explicit cast needed
- web auth page: drop superfluous `as Promise<unknown>` on res.json()

Co-Authored-By: Claude <noreply@anthropic.com>
- UserSchema.id: z.number() → z.string() (Better Auth uses UUID strings; aligns with Drizzle schema's text('id') PK)
- mapToUser/applySessionUser: add missing emailVerified/createdAt/updatedAt fields; replace `as` casts with asString/asBoolean guards from @packrat/guards
- useAuthActions signInWithGoogle: replace dead apiClient/setToken/UserSchema references with authClient.signIn.social
- ItemReviews: guard nullable review.title/text/date per CatalogItemSchema
Fixes 3 TypeScript errors in nativewindui source:
- Icon/types: SymbolViewPropsWithStringName satisfies IconMapper constraint
- AdaptiveSearchHeader, LargeTitleHeader: map 'systemDefault' autoCapitalize to 'none'

Also widens expo-router peer dep to >=6.0.23 (was ~6.0.23) so bun does not
install expo-router@6.0.23 into apps/expo/node_modules, which was causing
TypeScript to resolve to the old API and produce 1184+ false positives.

2.0.3-2 is an exact pin; the root override prevents range resolution.

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

Co-Authored-By: Claude <noreply@anthropic.com>
fix(etl): use raw SQL for completion write to bypass neon-http enum issue
…dmin ops

- Expand AdminCatalogItemSchema to all 24 columns the route returns
- Fix AdminUserItemSchema/AdminPackItemSchema to match SELECT outputs
- Derive AdminUser/AdminPack/AdminCatalogItem via Static<> (drop hand-written interfaces)
- Remove 3 `as unknown as PaginatedResponse<T>` casts in admin API client
- Fix edit-catalog-dialog.tsx for nullable weightUnit ripple

ETL admin routes (catalog analytics):
- GET /etl/failure-summary: jsonb unnest aggregation of top validation errors
- GET /etl/:jobId/failures: per-job error breakdown + raw samples
- POST /etl/reset-stuck: marks running jobs stuck >30min as failed
- POST /etl/:jobId/retry: clones job record and re-queues file to Cloudflare Queue
fix(admin,etl): CORS preflight fix + ETL retry/failure ops + schema alignment
- Fix users.id schema: t.Number() → t.String() (users.id is text PK, not serial)
- Update deleteUser/hardDeleteUser/restoreUser signatures: id: number → id: string
- Add TypeBox response schemas to all 4 new ETL routes (failure-summary, job-failures,
  reset-stuck, retry) — Eden Treaty consumers were getting unknown payloads
- Derive EtlFailureSummary/EtlJobFailures types via Static<> instead of hand-written interfaces
- Parallelize independent DB queries in failure-summary and job-failures via Promise.all
- Restrict retry to failed jobs only (was accepting completed, risking duplicate ingest)
- Clean up orphaned running row on enqueue failure (mark new job failed in catch)
- Validate jobId as UUID on failures and retry endpoints
fix(admin): review follow-up — users.id string, ETL response schemas, retry guards
…list

Root cors runs first and short-circuits OPTIONS preflights before the
admin-scoped cors plugin can set Access-Control-Allow-Origin. Admin origin
was only in the admin-scoped plugin, so preflights from admin.packratai.com
got no origin header. Non-preflight requests worked because the full
middleware chain ran and the admin cors added the header to the response.

Fix: add admin.packratai.com and *.workers.dev to root cors allowlist so
OPTIONS preflights get the origin header reflected back correctly.
…wlist

fix(cors): add admin.packratai.com + *.workers.dev to root cors allow…
…w + ETL summary

Neon returns PostgreSQL BIGINT (int8) as JavaScript strings to avoid
precision loss. raw sql<number> is a TS-only hint — COUNT/SUM results
from raw SQL come back as strings at runtime. Drizzle's count() adds
::int cast internally, but sql`count(...)` and sql`sum(...)` do not.

TypeBox t.Number() rejects string values so response validation failed.
Coerce totalBrands, withEmbedding, completed, failed, totalItemsIngested.
fix(admin/analytics): coerce Neon int8 strings to Number() in overvie…
…ORM for completion

Two bugs caused phantom failures and inflated totalProcessed counts:

1. Remaining-items ordering: totalProcessed was incremented *before* the final
   valid/invalid flushes. If processValidItemsBatch or processLogsBatch threw,
   totalProcessed showed an inflated count while totalValid/totalInvalid stayed
   null — making job records misleading and harder to diagnose on retry.
   Fixed by flushing remaining items first, then updating totalProcessed.

2. Silent completion failure: the final status='completed' update used raw SQL
   with an ::etl_job_status cast that silently failed in some Neon HTTP driver
   versions. The inner try-catch swallowed the error, leaving the job in
   'running' state so the reset-stuck sweep would mark it 'failed' even though
   all items were successfully ingested.
   Fixed by using the same db.update().set({ status: 'completed' }) pattern
   that already works reliably for the 'failed' path in the outer catch.

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

Co-Authored-By: Claude <noreply@anthropic.com>
fix(etl): flush remaining items before totalProcessed + use ORM for completion status
Per-row 1ms yield created a hard wall-clock floor of N ms for N rows.
backcountry (142k rows) = 142s minimum; geartrade (210k) = 210s minimum —
both guaranteed to exceed the CF Worker time limit before any processing.
Yielding every 100 rows reduces overhead to ~1.4s for the largest files
while still giving the GC breathing room on memory-heavy batches.
…files

Without drain-wait, R2 delivers the entire file into the csv-parse
buffer before the main processing loop can drain any rows. For files
>50 MB this fills the 128 MB Worker memory limit → Worker killed
externally → outer catch never runs → job stays stuck in 'running'.

Fix: check the return value of parser.write() and await 'drain' before
pushing more chunks, exactly as the Node.js streams backpressure
contract requires.
fix(etl): prevent Worker OOM on large CSV files via stream backpressure
…ocessing

50k-row files exhaust the 300k CPU budget (~6ms CPU/row for 42-field CSV).
This extends the per-invocation limit to ~66k rows. Chunking in ScrapyD
is the long-term fix for files beyond that threshold.
… time

Instead of streaming the full file (up to 600 MB) in one Worker invocation,
the notify endpoint now HEADs each object and splits files >20 MB into
sequential byte-range messages. Each Worker invocation fetches only its
slice via a Range request (~30k rows) — well within the 400k CPU budget.

Non-first chunks fetch the header row via a separate 4 KB range request
and skip the partial row at the chunk boundary. No ScrapyD changes needed.
fix(etl): raise cpu_ms limit to 400k (CF max) for large-file queue processing
@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

cloudflare-workers-and-pages Bot commented May 14, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

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

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
packrat-admin cb4181c Commit Preview URL

Branch Preview URL
May 17 2026, 01:48 AM

- Extract shared datetimeString helper to packages/schemas/src/utils.ts
- Add inferred types to @packrat/schemas/admin so consumers import types directly
- Clean up apps/admin/lib/api.ts to re-export from schema types instead of re-inferring
- Normalize UserAvatar.tsx to single avatarUrl field; update mockData accordingly
- Use typed cast in compute-pack.test.ts instead of `as any`
- Remove unused with:{ pack/catalogItem } eager loads in trips and packs routes
- Return structured 404 with code field in user and weather routes
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 142 out of 149 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

packages/api/src/routes/trips/index.ts:188

  • This delete route now performs a soft-delete (sets trips.deleted = true) rather than deleting the row, which is a runtime behavior change from the previous hard delete. If this is intentional, it should be called out in the PR description and ideally mirrored in the route docs/response typing; if not intentional, revert to db.delete(trips) to preserve existing semantics.
    packages/api/src/routes/user/index.ts:130
  • The handler returns UpdateUserResponseSchema.parse(...), but the route options don't declare a 200 response schema. This means OpenAPI/Treaty typing can drift from the actual returned shape; add response: { 200: UpdateUserResponseSchema } (and any non-200 variants you want documented).

Comment on lines +14 to +16
"dependencies": {
"@packrat/db": "workspace:*"
},
Comment on lines 1 to 5
import { Pool as NeonPool, neon } from '@neondatabase/serverless';
import * as schema from '@packrat/api/db/schema';
import type { ValidatedEnv } from '@packrat/api/utils/env-validation';
import { getEnv } from '@packrat/api/utils/env-validation';
import * as schema from '@packrat/db/schema';
import { drizzle } from 'drizzle-orm/neon-http';
Comment on lines 671 to 685
const db = createDb();
const item = await db.query.packItems.findFirst({
where: eq(packItems.id, params.itemId),
with: { catalogItem: true, pack: true },
with: { pack: true },
});

if (!item) return status(404, { error: 'Item not found' });
if (!item) throw new NotFoundError('Item not found');

const isOwner = item.userId === user.userId;
const isPublic = item.pack.isPublic;

if (!isOwner && !isPublic) return status(403, { error: 'Unauthorized' });
if (!isOwner && !isPublic) return status(403, { error: 'Forbidden' });

return item;
return PackItemSchema.parse(item);
},
Comment on lines 441 to 444
const data = body;
if (data.weight !== undefined && data.weight !== null && data.weight <= 0) {
return status(400, { error: 'weight must be a positive number' });
throw new Error('weight must be a positive number');
}
…rectly

- Add `export type Env = ValidatedEnv` to env-validation.ts
- Delete packages/api/src/types/env.ts (was a pure re-export shim)
- Update all 13 consumers to import Env from @packrat/api/utils/env-validation
- Remove ValidationError re-export from etl.ts; consumers import from @packrat/schemas/validation
…ve dead middleware aliases

- UserAvatar prop type uses Pick<MockUser, 'name' | 'avatarUrl'> instead of local duplicate
- variables.ts: userId was number, should be string (users.id is string via Better Auth)
- Delete adminMiddleware.ts and apiKeyAuth.ts (re-export alias files with no importers)
… code regressions

- @packrat/types now re-exports z.infer<> types from @packrat/schemas instead
  of raw Drizzle InferSelectModel types (wire-safe ISO strings vs Date objects)
- Added type exports (Pack, PackItem, PackWithItems, Trip, User, CatalogItem)
  directly to their schema files
- Added PackWithItemsSchema with required items array to packs.ts
- Removed re-export block from schemas/constants.ts; packs.ts and catalog.ts
  now import WEIGHT_UNITS/PACK_CATEGORIES directly from @packrat/constants
- Fixed duplicate Trip type export in trips.ts
- Fixed status code regressions in packs and catalog routes: throw new Error()
  → return status(400/500, ...) for client/server errors
- Added ApiErrorSchema to affected route response maps so Elysia types resolve
- Fixed test fixture: createdAt/updatedAt now use ISO strings not Date objects
…chemas

Every named Zod schema now lives in packages/schemas/src/ — route and
service files only import, never define.

New schema files:
- packages/schemas/src/trails.ts — OsmMemberSchema, RouteBaseRowSchema,
  RouteSearchRowSchema, RouteDetailRowSchema
- packages/schemas/src/wildlife.ts — WildlifeIdentifyRequestSchema

Schema additions to existing files:
- trailConditions: CreateTrailConditionReportRequestSchema, UpdateTrailConditionReportRequestSchema
- admin: AnalyticsPeriodSchema
- catalog: CatalogETLSchema
- packs: AddPackItemBodySchema
- packTemplates: AIPackAnalysisSchema, AIPackAnalysisItemSchema; replace
  local datetimeString with import from ./utils

Route/service cleanup:
- All top-level const XxxSchema = z. removed from routes/ and services/
- 10 route files and services/trails.ts updated to import from @packrat/schemas

Enforcement:
- packages/checks/src/check-route-schemas.ts — new script that fails if any
  named schema is defined at module level in routes/ or services/
  (4 internal AI-parsing files allowlisted)
- Wired as check:route-schemas:strict in pre-push clean-checks
andrew-bierman and others added 2 commits May 16, 2026 15:27
Resolves conflicts in:
- packages/api/src/schemas/admin.ts: deleted (moved to @packrat/schemas)
- apps/{admin,trails,web,expo} code conflicts: take PR side (uses new @packrat/db + @packrat/schemas)
- packages/api/test/etl.test.ts (add/add): take PR side
- apps/trails/lib/auth-client.ts (add/add): take PR side (trailsAuthClient name)
- bun.lock + package.json files: regenerated via bun install

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After merging development, four files imported from paths the PR removed:
- packages/api/src/routes/passwordReset.ts: @packrat/api/schemas/auth → @packrat/schemas/auth
- packages/api/src/services/passwordResetService.ts: @packrat/api/db/schema → @packrat/db
- apps/trails/lib/apiClient.ts: authClient → trailsAuthClient (PR renamed)
- apps/trails/components/AuthGate.tsx: pass required email prop to <VerifyEmail>

bun check-types now passes with 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

cloudflare-workers-and-pages Bot commented May 16, 2026

Deploying packrat-guides with  Cloudflare Pages  Cloudflare Pages

Latest commit: cb4181c
Status: ✅  Deploy successful!
Preview URL: https://e633cefe.packrat-guides-6gq.pages.dev
Branch Preview URL: https://refactor-extract-db-schemas.packrat-guides-6gq.pages.dev

View logs

@andrew-bierman
Copy link
Copy Markdown
Collaborator Author

Rebased onto current development (2 commits pushed: merge commit + post-merge fixes).

Conflict resolution summary:

  • packages/api/src/schemas/admin.ts (modify/delete): accepted the PR's deletion — the dev-side TypeBox changes were already covered by the PR's Zod versions in @packrat/schemas/admin.ts.
  • 11 other conflicts (apps/{admin,trails,web,expo} code, packages/api/test/etl.test.ts, packages/app/package.json): took the PR's side — the PR's imports from @packrat/db and @packrat/schemas are the new home.
  • bun.lock: regenerated via bun install.

Post-merge fixes (4 files): the merge surfaced dev-side code that still used import paths the PR removed (the refactor: delete all backward-compat re-export shims commit). Fixed:

  • packages/api/src/routes/passwordReset.ts: @packrat/api/schemas/auth@packrat/schemas/auth
  • packages/api/src/services/passwordResetService.ts: @packrat/api/db/schema@packrat/db
  • apps/trails/lib/apiClient.ts: authClienttrailsAuthClient as authClient (PR renamed the export to disambiguate from the expo authClient)
  • apps/trails/components/AuthGate.tsx: passed required email prop to <VerifyEmail>

Verification:

  • bun check-types: 0 errors
  • bun check (Biome): clean
  • Pre-push lefthook (clean-checks, schema sort, route-schemas check, type-casts): all pass

CI is now running. The flaky iOS/Android E2E suites may still fail per the recent CI hygiene pattern — focus on API Unit Tests / Expo Unit Tests / api-tests / Cloudflare Pages builds for the real signal.

@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

cloudflare-workers-and-pages Bot commented May 16, 2026

Deploying packrat-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: cb4181c
Status: ✅  Deploy successful!
Preview URL: https://ac3520af.packrat-landing.pages.dev
Branch Preview URL: https://refactor-extract-db-schemas.packrat-landing.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/schemas/src/trailConditions.ts (1)

3-6: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Inconsistent datetimeString definition — import from ./utils instead.

Other schema files in this PR (trips.ts, packs.ts, packTemplates.ts) import datetimeString from ./utils. This file defines it locally, creating duplication and maintenance burden.

♻️ Proposed fix
 import { z } from 'zod';
+import { datetimeString } from './utils';

-const datetimeString = z.preprocess(
-  (v) => (v instanceof Date ? v.toISOString() : v),
-  z.string().datetime(),
-);
🤖 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 `@packages/schemas/src/trailConditions.ts` around lines 3 - 6, The file defines
a local datetimeString using z.preprocess which duplicates the shared helper;
remove the local datetimeString definition and instead import datetimeString
from ./utils (update any existing z.preprocess usage to rely on the imported
datetimeString), ensuring you delete the redundant constant and add an import
for datetimeString so this module uses the shared helper used by trips.ts,
packs.ts, and packTemplates.ts.
packages/api/src/routes/catalog/index.ts (1)

474-480: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard updatedItem before schema parsing.

update(...).returning() can yield no row in race/TOCTOU cases; parsing undefined returns a 500 instead of a not-found response.

Suggested fix
       const [updatedItem] = await db
         .update(catalogItems)
         .set(updateData)
         .where(eq(catalogItems.id, itemId))
         .returning();

+      if (!updatedItem) {
+        throw new NotFoundError('Catalog item not found');
+      }
       return CatalogItemSchema.parse(updatedItem);
🤖 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 `@packages/api/src/routes/catalog/index.ts` around lines 474 - 480, The code
calls CatalogItemSchema.parse(updatedItem) without checking that updatedItem
exists after db.update(...).returning(), which can be undefined in race/TOCTOU
cases; guard the result and return a proper 404/NotFound response when no row
was updated. In the update handler surrounding the update call (the block using
db.update(catalogItems).set(updateData).where(eq(catalogItems.id,
itemId)).returning()), check if updatedItem is falsy and early-return a
not-found error/response (instead of calling CatalogItemSchema.parse), otherwise
parse and return the updated item as before.
🤖 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 `@packages/api/src/routes/packs/index.ts`:
- Around line 223-231: This GET handler must require authentication and enforce
ownership/public access: add the authPlugin macro with isAuthenticated: true to
the route and, inside the handler (the async ({ params }) => { ... } function
that uses createDb() and db.query.packs.findFirst), after fetching pack check
that either pack.public === true or pack.ownerId === auth.user.id (or
auth.user.role === 'admin' if your app permits admin bypass); if none apply
throw a NotAuthorizedError (or equivalent) before returning
PackWithWeightsSchema.parse(computePackWeights(pack)). Ensure you reference the
existing symbols pack, computePackWeights, PackWithWeightsSchema, and
NotAuthorizedError when implementing the checks.

In `@packages/api/src/routes/trips/index.ts`:
- Around line 94-98: The GET/UPDATE/DELETE flows are not excluding soft-deleted
trips: update the queries that use db.query.trips.findFirst (and the UPDATE
mutation + its reread, and the DELETE mutation) to add eq(trips.deleted, false)
to their where clauses so soft-deleted rows are ignored; search for uses of
db.query.trips.findFirst with tripId and user.userId (and the UPDATE handler and
its subsequent read/reparse into TripSchema.parse) and add the trips.deleted
check consistently to those where predicates to prevent
fetching/updating/deleting soft-deleted trips.

In `@packages/api/src/routes/weather.ts`:
- Around line 148-158: The Zod hard-parse in
WeatherAPIForecastResponseSchema.parse is causing thrown ZodErrors and 500s;
replace the call with WeatherAPIForecastResponseSchema.safeParse and handle the
result: if safeParse succeeds return the validated data, and if it fails either
(a) attempt to normalize missing fields on the original payload (data) before
re-validating or (b) construct and return a controlled fallback object that
preserves the upstream response shape (e.g., include data.location with id:
Number(id) and sensible defaults) instead of letting the error bubble up; update
the catch to only log unexpected non-validation errors and return the existing
status(500, ...) for true runtime failures.

In `@packages/checks/src/check-route-schemas.ts`:
- Line 28: The current NAMED_SCHEMA_PATTERN only matches "const XSchema = z."
and misses exported schemas; update the regex for NAMED_SCHEMA_PATTERN so it
accepts an optional "export" (and optional leading whitespace) before the
"const" (e.g. allow "export const XSchema = z.") so exported declarations are
detected by the check-route-schemas logic.
- Around line 89-92: The current logic only calls process.exit(1) when the
strict flag is present (variable strict), which makes failures pass by default;
change check-route-schemas.ts so that process.exit(1) is invoked unconditionally
when violations are detected (remove the conditional on strict or invert it) so
policy violations cause a non-zero exit by default; update or remove the strict
flag handling (variable strict) accordingly if you want to preserve a toggle
behavior.

In `@packages/schemas/src/packTemplates.ts`:
- Around line 110-135: The templateCategory enum is hardcoded; import
PACK_CATEGORIES from `@packrat/constants` and replace the literal enum in
AIPackAnalysisSchema with a schema that derives allowed values from
PACK_CATEGORIES (e.g. use z.enum(PACK_CATEGORIES as const) or
z.union(PACK_CATEGORIES.map(c => z.literal(c))) ) so the categories stay in
sync; update the import and change the templateCategory definition in
AIPackAnalysisSchema accordingly.

In `@packages/schemas/src/wildlife.ts`:
- Around line 3-5: WildlifeIdentifyRequestSchema currently allows empty strings
for the image key; update the schema for the image property in
WildlifeIdentifyRequestSchema to reject empty values (use
z.string().nonempty(...) or z.string().min(1, ...)) so validation fails at the
boundary with a clear error message; keep the .describe(...) metadata but add a
helpful error message like "image key must be a non-empty R2 object key".

---

Outside diff comments:
In `@packages/api/src/routes/catalog/index.ts`:
- Around line 474-480: The code calls CatalogItemSchema.parse(updatedItem)
without checking that updatedItem exists after db.update(...).returning(), which
can be undefined in race/TOCTOU cases; guard the result and return a proper
404/NotFound response when no row was updated. In the update handler surrounding
the update call (the block using
db.update(catalogItems).set(updateData).where(eq(catalogItems.id,
itemId)).returning()), check if updatedItem is falsy and early-return a
not-found error/response (instead of calling CatalogItemSchema.parse), otherwise
parse and return the updated item as before.

In `@packages/schemas/src/trailConditions.ts`:
- Around line 3-6: The file defines a local datetimeString using z.preprocess
which duplicates the shared helper; remove the local datetimeString definition
and instead import datetimeString from ./utils (update any existing z.preprocess
usage to rely on the imported datetimeString), ensuring you delete the redundant
constant and add an import for datetimeString so this module uses the shared
helper used by trips.ts, packs.ts, and packTemplates.ts.
🪄 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: 6e5c4bdc-3935-4b07-bd9f-da7365bdbc43

📥 Commits

Reviewing files that changed from the base of the PR and between 5de8ff1 and 26f4f60.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock, !bun.lock
📒 Files selected for processing (61)
  • apps/admin/lib/api.ts
  • apps/expo/components/initial/UserAvatar.tsx
  • apps/expo/data/mockData.ts
  • apps/expo/lib/utils/__tests__/compute-pack.test.ts
  • apps/expo/utils/__tests__/weight.test.ts
  • apps/trails/components/AuthGate.tsx
  • apps/trails/lib/apiClient.ts
  • lefthook.yml
  • packages/api/src/containers/AppContainer.ts
  • packages/api/src/index.ts
  • packages/api/src/middleware/adminMiddleware.ts
  • packages/api/src/middleware/apiKeyAuth.ts
  • packages/api/src/routes/admin/analytics/platform.ts
  • packages/api/src/routes/admin/trails.ts
  • packages/api/src/routes/catalog/index.ts
  • packages/api/src/routes/packTemplates/index.ts
  • packages/api/src/routes/packs/index.ts
  • packages/api/src/routes/passwordReset.ts
  • packages/api/src/routes/trailConditions/reports.ts
  • packages/api/src/routes/trails/index.ts
  • packages/api/src/routes/trips/index.ts
  • packages/api/src/routes/user/index.ts
  • packages/api/src/routes/weather.ts
  • packages/api/src/routes/wildlife/index.ts
  • packages/api/src/services/aiService.ts
  • packages/api/src/services/catalogService.ts
  • packages/api/src/services/embeddingService.ts
  • packages/api/src/services/etl/CatalogItemValidator.ts
  • packages/api/src/services/etl/processCatalogEtl.ts
  • packages/api/src/services/etl/processLogsBatch.ts
  • packages/api/src/services/etl/processValidItemsBatch.ts
  • packages/api/src/services/etl/queue.ts
  • packages/api/src/services/etl/updateEtlJobProgress.ts
  • packages/api/src/services/passwordResetService.ts
  • packages/api/src/services/r2-bucket.ts
  • packages/api/src/services/trails.ts
  • packages/api/src/types/env.ts
  • packages/api/src/types/etl.ts
  • packages/api/src/types/variables.ts
  • packages/api/src/utils/__tests__/weight.test.ts
  • packages/api/src/utils/ai/logging.ts
  • packages/api/src/utils/ai/provider.ts
  • packages/api/src/utils/env-validation.ts
  • packages/checks/package.json
  • packages/checks/src/check-route-schemas.ts
  • packages/schemas/src/admin.ts
  • packages/schemas/src/auth.ts
  • packages/schemas/src/catalog.ts
  • packages/schemas/src/constants.ts
  • packages/schemas/src/index.ts
  • packages/schemas/src/packTemplates.ts
  • packages/schemas/src/packs.ts
  • packages/schemas/src/trailConditions.ts
  • packages/schemas/src/trails.ts
  • packages/schemas/src/trips.ts
  • packages/schemas/src/users.ts
  • packages/schemas/src/utils.ts
  • packages/schemas/src/wildlife.ts
  • packages/types/package.json
  • packages/types/src/index.ts
  • packages/units/src/index.ts
💤 Files with no reviewable changes (4)
  • packages/api/src/middleware/apiKeyAuth.ts
  • packages/api/src/types/env.ts
  • packages/api/src/middleware/adminMiddleware.ts
  • packages/schemas/src/auth.ts

Comment on lines 223 to +231
async ({ params }) => {
const db = createDb();
try {
const pack = await db.query.packs.findFirst({
where: eq(packs.id, params.packId),
with: { items: { where: eq(packItems.deleted, false) } },
});
const pack = await db.query.packs.findFirst({
where: eq(packs.id, params.packId),
with: { items: { where: eq(packItems.deleted, false) } },
});

if (!pack) return status(404, { error: 'Pack not found' });
return computePackWeights(pack);
} catch (error) {
console.error('Error fetching pack:', error);
return status(500, { error: 'Failed to fetch pack' });
}
if (!pack) throw new NotFoundError('Pack not found');
return PackWithWeightsSchema.parse(computePackWeights(pack));
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Add ownership/public authorization to GET /:packId.

This handler fetches by packId only, so any authenticated user can read private packs if they know/guess an ID.

Suggested fix
-    async ({ params }) => {
+    async ({ params, user }) => {
       const db = createDb();
       const pack = await db.query.packs.findFirst({
-        where: eq(packs.id, params.packId),
+        where: and(
+          eq(packs.id, params.packId),
+          or(eq(packs.userId, user.userId), eq(packs.isPublic, true)),
+          eq(packs.deleted, false),
+        ),
         with: { items: { where: eq(packItems.deleted, false) } },
       });

       if (!pack) throw new NotFoundError('Pack not found');
       return PackWithWeightsSchema.parse(computePackWeights(pack));

As per coding guidelines, "Protected routes must use the authPlugin macro (isAuthenticated: true). Verify that role/ownership checks are present where needed."

🤖 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 `@packages/api/src/routes/packs/index.ts` around lines 223 - 231, This GET
handler must require authentication and enforce ownership/public access: add the
authPlugin macro with isAuthenticated: true to the route and, inside the handler
(the async ({ params }) => { ... } function that uses createDb() and
db.query.packs.findFirst), after fetching pack check that either pack.public ===
true or pack.ownerId === auth.user.id (or auth.user.role === 'admin' if your app
permits admin bypass); if none apply throw a NotAuthorizedError (or equivalent)
before returning PackWithWeightsSchema.parse(computePackWeights(pack)). Ensure
you reference the existing symbols pack, computePackWeights,
PackWithWeightsSchema, and NotAuthorizedError when implementing the checks.

Comment on lines 94 to +98
const trip = await db.query.trips.findFirst({
where: and(eq(trips.id, tripId), eq(trips.userId, user.userId)),
with: { pack: true },
});
if (!trip) return status(404, { error: 'Trip not found' });
return trip;
if (!trip) throw new NotFoundError('Trip not found');
return TripSchema.parse(trip);
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Exclude soft-deleted trips from the read/update/delete paths.

Line 95, Line 139, Line 142, and Line 175 never check trips.deleted, so a soft-deleted trip can still be fetched, updated, and "deleted" again while bumping updatedAt on every retry. Add eq(trips.deleted, false) to the GET query, the UPDATE mutation + reread query, and the DELETE mutation.

🛠️ Proposed fix
       const trip = await db.query.trips.findFirst({
-        where: and(eq(trips.id, tripId), eq(trips.userId, user.userId)),
+        where: and(
+          eq(trips.id, tripId),
+          eq(trips.userId, user.userId),
+          eq(trips.deleted, false),
+        ),
       });

         await db
           .update(trips)
           .set(updateData)
-          .where(and(eq(trips.id, tripId), eq(trips.userId, user.userId)));
+          .where(
+            and(
+              eq(trips.id, tripId),
+              eq(trips.userId, user.userId),
+              eq(trips.deleted, false),
+            ),
+          );

         const updatedTrip = await db.query.trips.findFirst({
-          where: and(eq(trips.id, tripId), eq(trips.userId, user.userId)),
+          where: and(
+            eq(trips.id, tripId),
+            eq(trips.userId, user.userId),
+            eq(trips.deleted, false),
+          ),
         });

       const [deleted] = await db
         .update(trips)
         .set({ deleted: true, updatedAt: new Date() })
-        .where(and(eq(trips.id, tripId), eq(trips.userId, user.userId)))
+        .where(
+          and(
+            eq(trips.id, tripId),
+            eq(trips.userId, user.userId),
+            eq(trips.deleted, false),
+          ),
+        )
         .returning();

As per coding guidelines, "All user-generated content must use soft deletes (never hard DELETE)."

Also applies to: 136-146, 172-179

🤖 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 `@packages/api/src/routes/trips/index.ts` around lines 94 - 98, The
GET/UPDATE/DELETE flows are not excluding soft-deleted trips: update the queries
that use db.query.trips.findFirst (and the UPDATE mutation + its reread, and the
DELETE mutation) to add eq(trips.deleted, false) to their where clauses so
soft-deleted rows are ignored; search for uses of db.query.trips.findFirst with
tripId and user.userId (and the UPDATE handler and its subsequent read/reparse
into TripSchema.parse) and add the trips.deleted check consistently to those
where predicates to prevent fetching/updating/deleting soft-deleted trips.

Comment on lines +148 to 158
return WeatherAPIForecastResponseSchema.parse({
...data,
location: {
...data.location,
id: Number(id),
},
};
});
} catch (error) {
console.error('Error fetching weather forecast:', error);
return status(500, {
error: 'Internal server error',
code: 'WEATHER_FORECAST_ERROR',
});
return status(500, { error: 'Internal server error', code: 'WEATHER_FORECAST_ERROR' });
}
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Hard-failing schema parse is breaking /weather/forecast responses.

Line 148 throws a ZodError for real upstream payload variants, and the catch block turns it into a 500 (confirmed by CI failures). This introduced a runtime regression in a previously working endpoint.

Use safeParse and either (a) normalize missing fields before validation, or (b) return a controlled upstream-shape fallback instead of throwing.

Suggested fix sketch
-        return WeatherAPIForecastResponseSchema.parse({
+        const normalized = {
           ...data,
           location: {
             ...data.location,
             id: Number(id),
           },
-        });
+        };
+        const parsed = WeatherAPIForecastResponseSchema.safeParse(normalized);
+        if (!parsed.success) {
+          console.error('Weather forecast schema validation failed:', parsed.error.issues);
+          return normalized;
+        }
+        return parsed.data;
🧰 Tools
🪛 GitHub Actions: API Tests / 0_api-tests.txt

[error] 148-148: Weather route failed validation: ZodError (required fields missing/undefined) during GET /weather/forecast (location id). Error originates at packages/api/src/routes/weather.ts:148:49

🪛 GitHub Actions: API Tests / api-tests

[error] 148-148: ZodError while validating weather forecast response (missing required fields; e.g., location.region, location.lat/lon, current.*). Request failed with 500 in tests.

🤖 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 `@packages/api/src/routes/weather.ts` around lines 148 - 158, The Zod
hard-parse in WeatherAPIForecastResponseSchema.parse is causing thrown ZodErrors
and 500s; replace the call with WeatherAPIForecastResponseSchema.safeParse and
handle the result: if safeParse succeeds return the validated data, and if it
fails either (a) attempt to normalize missing fields on the original payload
(data) before re-validating or (b) construct and return a controlled fallback
object that preserves the upstream response shape (e.g., include data.location
with id: Number(id) and sensible defaults) instead of letting the error bubble
up; update the catch to only log unexpected non-validation errors and return the
existing status(500, ...) for true runtime failures.

const TARGET_EXTENSIONS = new Set(['.ts', '.tsx']);

// Top-level named schema: starts at column 0, no leading whitespace
const NAMED_SCHEMA_PATTERN = /^const \w+Schema\s*=\s*z\./;
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Regex misses exported schema declarations.

Line 28 only matches const ...Schema = z. and skips export const ...Schema = z., so violations can slip through.

Proposed fix
-const NAMED_SCHEMA_PATTERN = /^const \w+Schema\s*=\s*z\./;
+const NAMED_SCHEMA_PATTERN = /^(?:export\s+)?const\s+\w+Schema\s*=\s*z\./;

As per coding guidelines, "Validation schemas live in src/schemas/ (Zod)."

📝 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.

Suggested change
const NAMED_SCHEMA_PATTERN = /^const \w+Schema\s*=\s*z\./;
const NAMED_SCHEMA_PATTERN = /^(?:export\s+)?const\s+\w+Schema\s*=\s*z\./;
🤖 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 `@packages/checks/src/check-route-schemas.ts` at line 28, The current
NAMED_SCHEMA_PATTERN only matches "const XSchema = z." and misses exported
schemas; update the regex for NAMED_SCHEMA_PATTERN so it accepts an optional
"export" (and optional leading whitespace) before the "const" (e.g. allow
"export const XSchema = z.") so exported declarations are detected by the
check-route-schemas logic.

Comment on lines +89 to +92
const strict = process.argv.includes('--strict');
if (strict) {
process.exit(1);
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Default behavior is fail-open when violations exist.

Lines 89-92 only return non-zero with --strict. If the flag is omitted in CI/hooks, policy violations still pass.

Proposed fix
-const strict = process.argv.includes('--strict');
-if (strict) {
-  process.exit(1);
-}
+const warnOnly = process.argv.includes('--warn');
+if (!warnOnly) {
+  process.exit(1);
+}

As per coding guidelines, "Validation schemas live in src/schemas/ (Zod)."

📝 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.

Suggested change
const strict = process.argv.includes('--strict');
if (strict) {
process.exit(1);
}
const warnOnly = process.argv.includes('--warn');
if (!warnOnly) {
process.exit(1);
}
🤖 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 `@packages/checks/src/check-route-schemas.ts` around lines 89 - 92, The current
logic only calls process.exit(1) when the strict flag is present (variable
strict), which makes failures pass by default; change check-route-schemas.ts so
that process.exit(1) is invoked unconditionally when violations are detected
(remove the conditional on strict or invert it) so policy violations cause a
non-zero exit by default; update or remove the strict flag handling (variable
strict) accordingly if you want to preserve a toggle behavior.

Comment on lines +110 to +135
export const AIPackAnalysisItemSchema = z.object({
name: z.string(),
description: z.string(),
quantity: z.number().int().positive().default(1),
category: z.string(),
weightGrams: z.number().nonnegative().default(0),
consumable: z.boolean().default(false),
worn: z.boolean().default(false),
});

export const AIPackAnalysisSchema = z.object({
templateName: z.string(),
templateCategory: z.enum([
'hiking',
'backpacking',
'camping',
'climbing',
'winter',
'desert',
'custom',
'water sports',
'skiing',
]),
templateDescription: z.string(),
items: z.array(AIPackAnalysisItemSchema),
});
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.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider using PACK_CATEGORIES from @packrat/constants for consistency.

The templateCategory enum (lines 122-132) is hardcoded while packs.ts uses PACK_CATEGORIES from @packrat/constants. If these categories should stay in sync, import and use the constant instead.

🤖 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 `@packages/schemas/src/packTemplates.ts` around lines 110 - 135, The
templateCategory enum is hardcoded; import PACK_CATEGORIES from
`@packrat/constants` and replace the literal enum in AIPackAnalysisSchema with a
schema that derives allowed values from PACK_CATEGORIES (e.g. use
z.enum(PACK_CATEGORIES as const) or z.union(PACK_CATEGORIES.map(c =>
z.literal(c))) ) so the categories stay in sync; update the import and change
the templateCategory definition in AIPackAnalysisSchema accordingly.

Comment on lines +3 to +5
export const WildlifeIdentifyRequestSchema = z.object({
image: z.string().describe('Uploaded image key in R2'),
});
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject empty image keys in request schema.

Line 4 currently accepts '', which can pass validation and fail later during lookup/use of the R2 key. Enforce a non-empty value at the schema boundary.

Proposed fix
 export const WildlifeIdentifyRequestSchema = z.object({
-  image: z.string().describe('Uploaded image key in R2'),
+  image: z.string().min(1, 'Image key is required').describe('Uploaded image key in R2'),
 });
📝 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.

Suggested change
export const WildlifeIdentifyRequestSchema = z.object({
image: z.string().describe('Uploaded image key in R2'),
});
export const WildlifeIdentifyRequestSchema = z.object({
image: z.string().min(1, 'Image key is required').describe('Uploaded image key in R2'),
});
🤖 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 `@packages/schemas/src/wildlife.ts` around lines 3 - 5,
WildlifeIdentifyRequestSchema currently allows empty strings for the image key;
update the schema for the image property in WildlifeIdentifyRequestSchema to
reject empty values (use z.string().nonempty(...) or z.string().min(1, ...)) so
validation fails at the boundary with a clear error message; keep the
.describe(...) metadata but add a helpful error message like "image key must be
a non-empty R2 object key".

andrew-bierman and others added 4 commits May 16, 2026 16:35
CI biome check caught alphabetical ordering after the merge import fix.
Local pre-push hook missed it because it runs biome with --no-errors-on-unmatched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same root cause as 73c755a — moving an import path from @packrat/api/db/schema to @packrat/db re-sorts after the other @packrat/api imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(after #2363/#2428 landed)

Conflicts:
- packages/api/src/routes/trips/index.ts: dev added inline LocationSchema/CreateTripRequestSchema/UpdateTripRequestSchema; this PR already imports the equivalents from @packrat/schemas/trips. Kept HEAD (the extracted-schemas import + body refs to CreateTripBodySchema/UpdateTripBodySchema).
- bun.lock: regenerated via bun install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andrew-bierman andrew-bierman merged commit 7b8ae26 into development May 17, 2026
13 of 16 checks passed
@andrew-bierman andrew-bierman deleted the refactor/extract-db-schemas-packages branch May 17, 2026 02:41
andrew-bierman added a commit that referenced this pull request May 17, 2026
Resolves conflicts arising from the recent merge of #2423 (computePackWeights
return type changes, schema parsing) and #2377 (auth flow refactor).

All resolutions preserve the single-object-param convention enforced by
no-owned-max-params; absorbed dev's new return-type wrappers (Zod parse) and
the authClient-based password reset flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
andrew-bierman added a commit that referenced this pull request May 17, 2026
… landed)

Resolved 3 conflicts:
- UserAvatar.tsx: absorbed dev's MockUser/avatarUrl refactor while preserving
  HEAD's Platform.select web-image shim.
- TemplateItemsSection.tsx: dropped HEAD's redundant WeightUnit re-import
  (dev already imports from @packrat/constants); kept Platform import.
- bun.lock: regenerated from dev's lockfile via bun install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api database dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation mobile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants