# `any` Usage Audit — what each one should be **Branch:** `DEAD_CODE_CLEAN` **Scope:** all `any` keyword usages in `apps/*` and `packages/*` source (`.ts`/`.tsx`), excluding `node_modules`, `dist`, `.wrangler`, `.output`, `.expo`, `.turbo`, `.tanstack`, dumps, `*.sql`, and generated route trees (`routeTree.gen.ts`). **Nature:** documentation only — no code changed. ## Totals (type-level `any`, excluding prose/comments/generated) | Area | Count | Dominant cause | |---|---|---| | `apps/backend` | ~85 | `as any` casts on JWT/Drizzle/error shapes; `: any` where a shared type exists | | `packages/db_helper_sqlite` + `_postgres` | ~105 | `(x: any)` on already-typed Drizzle results; `Promise` helpers | | `apps/admin-ui` | ~161 | `onError/catch (e: any)`; `router.push(... as any)`; entity `useState` | | `apps/user-ui` + `apps/fallback-ui` | ~137 | `catch/onError`; Expo/TanStack `as any`; catalog/cart `item: any` | | `apps/web-ui` | ~138 | `Record`/`(p: any)` product+slot callbacks; `onError` | | `apps/admin-web` | ~128 | `onError/catch` (36); domain rows; `navigate({ to: ... as any })` | | `packages/shared` + `web-components` + `ui` + `migrator` | ~28 | untyped component props; RN style/asset props; migrator rows | | **Total** | **~780** | | Regex-based raw totals are a bit higher because the word "any" also appears in comments/prose and `tsconfig` files; those are excluded here. ## The replacement playbook ("what it should have been") 1. **`catch (e: any)` / `onError: (e: any)` → `unknown`** (and narrow with `e instanceof Error ? e.message : '…'`). This is the single largest mechanical category (~100 sites across the repo). 2. **`onError: (error: any)` on tRPC hooks → delete the annotation.** tRPC v11 already infers `TRPCClientError`; `.message` keeps working. 3. **Callback params on typed Drizzle/array results → delete `: any`.** Because `db_index.ts` passes the full `schema` to `drizzle(...)`, `db.query.*` and `.select()` results are fully inferred; `(x: any) => …` is pure noise. 4. **`(item: any)` / `(product: any)` in catalog/cart lists → the real entity type:** `MergedProduct` (catalog), `CartItem` (carts), `AdminSku`, `AdminSlotWithProducts`, `AdminOrderDetails`, `UserOrderSummary`, etc. 5. **`router.push(… as any)` → typed routes** (`Href` from `expo-router`; `FileRouteTypes['to']`/generated union for TanStack Router). Static routes can simply drop the cast. 6. **`Record` → `Record`** for heterogeneous bags. 7. **`z.any()` → `z.unknown()`** in Zod schemas (or a precise union). 8. **RN/Expo props → their real types:** `StyleProp` / `StyleProp`, `NativeSyntheticEvent`, `TextStyle['fontWeight']`, `ImagePicker.ImagePickerAsset`. 9. **Icon `name={x as any}` → `React.ComponentProps['name']`** (the glyph-key union). 10. **`Promise` db-helper returns → `InferSelectModel` / explicit `Pick`.** ## Highest-leverage shared fixes (fix once, removes many) These untyped *shared* declarations force `any` at dozens of call sites — tighten them first: | Fix | File | Impact | |---|---|---| | `value: any` → a `ConstValueType \| ConstValueType[]` union (union already defined at line 18) | `packages/shared/types/const.types.ts:8` | Removes `value.map((id: any) => …)` in admin-ui **and** admin-web reorder screens | | `ListItemProps.item: any`, `CompactProductCardProps.item: any`, `OffersSectionProps.products: any[]`, `AddedToCartProduct.product: any` → hoist a shared structural product type (or make the props generic) | `packages/shared/types/app-common.types.ts:60,78,87,96` | Caps how far web-ui/user-ui flash/offers/complaints props can be tightened | | `queryParams?: Record` → `Record` | `packages/shared/types/app-common.types.ts:20` | Consumer only does `String(value)` | | `setFile: (file: any) => void` → `ImagePickerAsset \| ImagePickerAsset[] \| null` | `packages/ui/src/components/use-pick-image.tsx:13` | Type flows into admin-ui `setFile: (assets: any)` (3 sites) and `profile-image`/`ImageUploaderNeo` | | Export app-local entity types: `MergedProduct`, `CartItem`, `SlotInfo`/`ProductSlotMap` (web-ui/user-ui); `ProductFormData`, `Variant`, `Attribute`, `CouponFormValues` (admin-ui/admin-web) | respective hook/component files | Removes dozens of `values: any` / `item: any` in mutations and list callbacks | | Backend procedures returning `any[]` (coupon getAll/getReservedCoupons, admin `getUserIncidents`, store) → concrete shared types | `apps/backend/.../admin-apis/apis/{coupon,user,store}.ts` | Removes the downstream `Coupon`/`UserIncident` `any`s in both admin frontends | --- ## apps/backend ### Summary - **85** `any` keywords across **83 lines / 31 files**. - **Top files:** `coupon.ts` & `const-store.ts` (7 each), `admin/user.ts`, `post-order-handler.ts`, `app.ts` (6 each), `admin/product.ts` (5), `auth.ts`, `cache-creator.ts` (4). - **Dominant patterns:** (1) `x as any` around JWT payloads, Drizzle `json` columns, and untyped db-helper returns; (2) `: any` params/locals where a concrete shared type exists (`OrderWithFullData`, `AdminVendorSnippet`, `TagBasicData`, `StaffRole`); (3) generic defaults `` and catch clauses. - **10 of the 85 are inside commented-out "Old implementation" blocks** → delete, don't retype. | Location | Current code | Should be | Why | |---|---|---|---| | `app.ts:57`, `middleware/auth.middleware.ts:19` | `const decoded = payload as any` | `AppJwtPayload` = `{ userId?: number; staffId?: number; name?: string }` | jose `JWTPayload`; tokens are signed as `SignJWT({ userId })` / `SignJWT({ staffId, name })` | | `app.ts:131,132` | `(err as any).statusCode` | `err instanceof ApiError ? err.statusCode` | `ApiError` is the only error with `statusCode` | | `app.ts:134,135` | `(err as any).status` | `err instanceof HTTPException ? err.status` | Hono `HTTPException` | | `app.ts:141` | `c.json({ message }, status as any)` | `status as StatusCode` | Hono status union | | `types/hono.d.ts:5`, `trpc/trpc-index.ts:7` | `user?: any` | `AppJwtPayload` | consumed as `ctx.user?.userId` | | `trpc/trpc-index.ts:37` | `const err = error as any` | `unknown` + narrow | catch var; reads name/message/code/stack/meta/sql | | `jobs/cache-creator.ts:10,13` | `state: any` | `DurableObjectState` | uses storage/alarm | | `jobs/cache-creator.ts:11,13` | `env: any` | `WorkerEnv` (needs domain input) | passed to `ensureWorkerInit` | | `jobs/payment-status-checker.ts:3,4,5` | `payment:any; order:any; slot:any` | `typeof payments.$inferSelect` / `{id;userId}` / row types | stub interface; only `order.id/.userId` read | | `lib/queue-consumer.ts:4,27,40` | `batch: any` | `MessageBatch` / `<{orderIds:number[]}>` / `` | pushed message shapes are known | | `lib/const-store.ts:34,58` | `` | `` | generic default | | `lib/const-store.ts:90,102,107` | `Record<…, any>` | `Record<…, unknown>` | opaque KV values | | `lib/api-error.ts:3,5` | `details?: any` | `unknown` | never consumed typed | | `lib/notif-job.ts:11,22` | `notificationQueue:any`, `notificationWorker:any` | bullmq `Queue`/`Worker` or `Record` | stubbed (`{}`) | | `lib/notif-job.ts:91` | `scheduleNotification(userId, payload: any, …)` | `{ title; body; type; orderId? }` | callers pass exactly this | | `lib/worker-init.ts:3,4` | `env: any`, `(globalThis as any).ENV` | `WorkerEnv` / typed global | matches existing typed-global pattern | | `lib/post-order-handler.ts:44,52,95,167,168` | `ordersData: any[]`, `(item: any)`, `createdOrders: any[]`, `Map<…, any[]>` | `OrderWithFullData[]`, `OrderWithFullData['orderItems'][number]`, `PlacedOrder[]` | db-helper return types | | `lib/post-order-handler.ts:86` | `orderData: any` | `OrderWithCancellationData & { refundStatus: string }` | known shape | | `lib/flash-delivery-cron.ts:12` | `env: any` | `WorkerEnv` | | | `lib/env-exporter.ts:62` | `(globalThis as any).ENV \|\| (globalThis as any).process?.env` | typed structural cast | matches rest of codebase | | `lib/notif-job.ts:91`, `trpc/.../staff-user.ts:64,112` | mixture | `AdminVendorSnippet`, `StaffRole` | concrete shared types exist | | `admin-apis/apis/store.ts:17,37` | `stores: any[]`, `store: any` | `Store & { owner: User }` | query includes `owner` | | `admin-apis/apis/banner.ts:59`, `user-apis/apis/auth.ts:274` | `catch (e: any)` | `unknown` + narrow | | | `admin-apis/apis/const.ts:38` | `z.any()` | `z.unknown()` | | | `admin-apis/apis/slots.ts:74` | `deliverySequence: z.any()` | `Record` union | shared `AdminDeliverySequence` | | `user-apis/apis/payments.ts:129` | `payload as any` | `Record` | schema is `jsonText` | | `admin-apis/apis/product.ts:265,357,754` | `as any` casts / `product: any` | `CreateProductInput` / `AdminProductTagWithProducts` | helpers/return types exist | | `user-apis/apis/user.ts:61`, `auth.ts:110,374` | `new Date(userDetail.dateOfBirth as any)` | drop cast | field is `Date \| null` | | `user-apis/apis/order.ts:293` | `slotId: null as any` | `null` | param is `number \| null` | | `admin-apis/apis/coupon.ts:129,141,153,154,351,366,440` | `coupons: any[]`, `Promise`, `(au:any)`, `(ap:any)`, `coupon: any` | `Coupon & { applicableUsers; applicableProducts }` / `ReservedCoupon` (needs domain input) | relations not modelled in shared | | `common-apis/common.ts:79,84,85` | `(id:any)`, `(tag:any)`, `orderedTags:any[]` | `number`, `TagBasicData`, `TagBasicData[]` | `getConstant` default; `getAllTagsForCache()` | | `admin-apis/apis/user.ts:48,60,98,117,170,258` | `(u:any)` etc | `Pick`, `Pick`, `UserIncident` (needs domain input) | select shapes | | Dead-code `any`s (delete) | `product.ts:565,700`, `banner.ts:194`, `slots.ts:176,347`, `address.ts:192`, `admin/order.ts:384`, `vendor-snippets.ts:260`, `const-store.ts:91,97` | — | commented-out old impls | **Easy wins:** drop redundant `as any` (`product.ts:265/357`, `order.ts:293`, `user.ts:61`, `auth.ts:110/374`, `payments.ts:129`); `: any`→`unknown` (`api-error`, `const-store` generics, `payment-status-checker`, `payments-utils`); `catch`→`unknown` (`banner.ts:59`, `auth.ts:274`); `z.any()`→`z.unknown()`; delete dead `any`s; define `AppJwtPayload` once. --- ## packages/db_helper_sqlite + db_helper_postgres **Summary:** **105** tokens across **98 lines / 17 files**. Top: `sqlite/admin-apis/product.ts` (27), `sqlite/admin-apis/order.ts` (19), `run-batched.ts` (7), `admin-apis/user.ts` (7 each pkg), `admin-apis/coupon.ts` (6 each). Dominant: `(x: any)` on already-typed Drizzle results (~60) — just delete. `Row` = `InferSelectModel`, `Insert` = `InferInsertModel`. | Location | Current code | Should be | Why | |---|---|---|---| | `sqlite/admin-apis/product.ts:182,235,385,483,530` | `(ci\|f: any)` | `CreateComboItemInput` / `SkuFeatureLike` | element types of typed relations | | `product.ts:185,238` | `(f: any)` | `Row` | | | `product.ts:596` | `(tag: any)` | inferred (cast at 594) | | | `product.ts:598,669,709` | `(assignment: any)` | `Row & { sku: … }` | | | `product.ts:756` | `(review: any)` | inferred select shape | | | `product.ts:817,822` | `(group:any)`, `(membership:any)` | inferred | | | `product.ts:367` | `updateProduct(id, input: any)` | `UpdateProductInput` (domain) | | | `product.ts:39,41,114,340,498,545` | `any[]`/`(ci:any)` | `CreateComboItemInput` / `AdminProductComboItem[]` | | | `product.ts:384,398,401` | `(sku: any)` | `CreateSkuInput & { id?: number }` | | | `product.ts:956` | `const updateData: any` | `Partial>` | | | `sqlite/admin-apis/order.ts:174,239,250,364,369,378,388,412,523,528,538,549,554,577,615,616,622,643` | `(u\|item\|f\|order\|acc: any)` | delete → inferred | typed query results | | `sqlite/admin-apis/slots.ts:122,125,133,199` | `(item\|s\|sku: any)` | delete → inferred | | | `slots.ts:194` | `(item: any)` + reads `item.isDeleted` | inferred; **add `isDeleted: true` to `columns` at :191** | hidden bug surfaced by typing | | `slots.ts:56` | `fetchExistingSkuIds(tx: any, …)` | Drizzle tx type (`Parameters[0]>[0]`) | | | `stores/store-helpers.ts:312,315,326` | `(item\|s\|sku: any)` | delete → inferred | | | `admin-apis/store.ts:7,17` (both pkgs) | `Promise` / `Promise` | `Array & { owner: Row\|null }>` | | | `admin-apis/staff-user.ts:22,78` (both) | `Promise` | inferred relation / `StaffRole[]` | | | `admin-apis/user.ts:5,28,95,122,184,224,245` (both) | `Promise` | `Row`, `Pick,…>`, `Row`, etc. | select shapes | | `admin-apis/coupon.ts:10,53,175,211,213,300` (both) | `any[]`/`Promise`/`input: any` | `Row & {…}`, `Row`, `ReservedCouponInput` | InferInsertModel | | `user-apis/auth.ts:102,119` (both) | `const userUpdate: any` | `Partial>` / `Partial>` | | | `user-apis/auth.ts:184` (both) | `catch (error: any)` | `unknown` + narrow | `23505` is pg-only | | `lib/run-batched.ts:3,19` | `(cb: (tx: infer Tx) => any, ...args: any[]) => any` | `unknown` | unused callback return/args | | `lib/automated-jobs.ts:25` (both) | `value: any` | `unknown` | | **Easy wins:** delete `: any` from ~60 map/filter callbacks; `run-batched` `any`→`unknown`; `automated-jobs` `value: unknown`; object literals → `Partial>`; `catch`→`unknown`; **free bug fix** at `slots.ts:191/194`; ~35 `Promise` return annotations via `Row`/`Insert`. --- ## apps/admin-ui **Summary:** **161** code occurrences / 49 files. Top: `delivery-sequences/index.tsx` (10), `prices-overview` (11), `products/add+edit` (14), `_layout.tsx` (13). Dominant: `catch/onError (error: any)` (35) and `router.* as any` (36) — both mechanical. | Location | Current code | Should be | Why | |---|---|---|---| | `_layout.tsx:53…137`, `dashboard/index.tsx:32,99,242`, banners/products/slots/stores/prices/manage-orders (≈18 files) | `router.push("…" as any)` | `as Href` (`expo-router`); drop cast for static routes | typed routes | | `dashboard/index.tsx:39,250,275`, `manage-orders/index.tsx:77` | `name={item.icon as any}` | `keyof typeof MaterialIcons.glyphMap` | glyph union | | `TagMenu.tsx:62`, `stores/edit:28`, `send-notifications:48`, `coupons/*`, `prices:286`, `SlotForm:92,107`, `delivery-sequences`, `order-details`, `orders` (≈24) | `onError: (error: any)` | delete annotation | tRPC infers | | VendorSnippet/ProductGroupForm/products/coupons/stores/vendor-snippets/product-tags/send-notifications (≈11) | `catch (error: any)` | `unknown` + narrow | | | `coupons/index.tsx:11,111` | `item: any` | `Coupon & {relations}` / reserved (domain) | backend returns `any[]` | | `stores/index.tsx:15` | `item: any` | `Store` | | | `products/index.tsx:148` | `{ item: any }` | `Omit & { skus: SerializedAdminSku[] }` | | | `slots/index.tsx:11`, `rebalance-orders:10` | `item: any` | `AdminSlotWithProducts` | | | `slots/slot-details.tsx:24`, `product-groupings:130`, `products/detail:146` | `useState` | `AdminSlotProductSummary[]`, `AdminSku[]`, `AdminProductReviewWithSignedUrls` | | | `slots/index:238`, `rebalance:123` | `useState` | `string[]` | product names | | `products/add:15,64,29,36,80`, `edit` (same) | `values: any`, `(variant:any)`, `(a:any)`, `(ci:any)` | `ProductFormData`, `Variant`, `Attribute`, `CreateComboItemInput` (export from ProductForm) | | | `BannerForm:66`, `StoreForm:72`, `products/detail:32` | `setFile: async (assets: any)` | `ImagePicker.ImagePickerAsset \| ImagePickerAsset[]` | | | `TagForm.tsx:40` | `forwardRef` | `unknown` | no imperative handle | | `TagForm.tsx:57` | `(sku: any)` | `SkuSummary` | | | `toaster.tsx:39` | `data: any` | `{ rideId?; carId? } \| null` (domain) | | | `CouponForm.tsx:60,66,156,191,192,205,237,238` | `function(value:any)`, `(values as any)`, `(errors.couponCodes as any)` | `CouponFormValues`, narrow error shapes | | | `ProductGroupForm:13`, `SnippetOrdersView:21`, `SlotForm:37,48,191` | `products: any[]`, `sequence: any[]`, `(snippet/p: any)` | `AdminSku[]`, `number[]`, `AdminVendorSnippetWithAccess`, `AdminSlotProductSummary` | | | `UserIncidentsView:141` | `(incident: any)` | `UserIncident` (domain) | | | `customize-app/index:9,58,59,60` | `Record`, `value:any`, `setFieldValue(any)`, `router:any` | `Record`, `unknown`, `Router` | | | `popular-items:139`, `all-items-order:111`, `product-tags/order:102` | `value.map((id: any) => parseInt(id))` | `number` (root: `Constant.value`) | | | `prices-overview:25,28,29,30,46,153,204,224,270` | `sku:any`, `Record`, `const update:any` | local `PriceSku`, existing `PendingChange`, mutation input | | | `delivery-sequences:53,354,373,389,438,588,622` | `orders:any[]`, `(deliverySequence as any)` | `AdminSlotOrder[]`, drop cast (`AdminDeliverySequence`) | | | `order-details:148` | `const mutationData:any` | `initiateRefund` input | | | `coupons/create:27` | `values: any` | `CouponFormValues` | | **Easy wins:** drop `: any` on 24 `onError`; `catch`→`unknown` (11); `as any`→`as Href` (36); icon glyph types (4); drop `deliverySequence as any` (6); remove `products/edit:180 as any`; `triggerStyle` → `StyleProp`; export `ProductFormData/Variant/Attribute`. --- ## apps/user-ui + apps/fallback-ui **Summary:** **137** real type-level / 46 files (4 non-type "any" words ignored). Top: `home/index.tsx` (20), `cart-page` (11), `ProductDetail` (11). Dominant: `catch/onError` (29); router casts (23); catalog/cart `item: any` (30+). | Location | Current code | Should be | Why | |---|---|---|---| | login/register/edit-profile/ComplaintForm/OrderMenu/ProductDetail/registration-form + fallback create-coupon/demo | `catch (error: any)` | `unknown` + narrow | | | many `onError: (error: any)` (14+) | `onError: (error: any)` | `unknown` / delete (tRPC infers) | | | `me/index`, `SlotSpecificView`, `cart-page` (≈12) | `router.replace/push(target as any)` | `Href` | | | fallback AuthWrapper/home/login/super-admin/user-home (≈13) | `navigate({ to: '…' as any })` | drop cast | routes registered | | `me/index:137`, `my-orders:111,278` | icon `as any` | `ComponentProps['name']` | | | `my-orders:68,69` | `getStatusColor(): any` | typed `StatusColor` | | | `home/index.tsx` (many) | `slot: any`, `(p: any)`, `dashboardTags:any[]`, `Record`, `useRef`, `onLayout(e:any)`, `useState` | `UserSlotWithProducts`, `UserSlotProduct`, `AllProductsApiType['tags']`, `MergedProduct[]`, `ComponentRef`, `LayoutChangeEvent` | catalog types exist | | `ProductDetail:48,75,76,79,87,147,710` | `useState`, `(slot/p/a/b/item:any)`, `setFile(assets:any)` | review/cart/slot types + `ImagePickerAsset` | shared `UserProductReviewWithSignedUrls` missing `adminResponse`/`signedAdminImageUrls` (domain) | | `cart-page:60` | `coupon: any` | `UserCouponWithRelations` | | | `registration-form:29` | `useState` | `ImagePicker.ImagePickerAsset` | | | `useUploadToObjectStore:26` | `contextString as any` | drop cast | shared `ContextString` assignable | | `notif-context:45,46` | `useRef` | listener return types | | | `toaster:5` | `data: any` | `Record` | | | `AddToCartDialog` (6) | `(item/slot/p: any)`, `Record` | `CartItem`, `UserSlotWithProducts`, `UserSlotProduct` | | | `AuthContext:14` | `Record` | tighten shared `AuthRedirectOptions.queryParams` | | | `getCurrentUserId:8` | `jwtDecode(token)` any | `jwtDecode<{id?;userId?}>` | | | fallback `order-details:33,51,223`, `user-details:44,45,141`, `login:23`, `demo:11,76`, `inauguration:74` | `as any` / `any[]` / `any` | `AdminOrderDetails`, inferred, `ReturnType`, local `DemoResponse` | | | `ProductCard:22,29,84,93,94` | `item:any`, `ComponentType`, `(cartItem:any)`, `Record`, `(slot:any)` | `MergedProduct`, `ComponentType`, `CartItem`, `UserSlotWithProducts` | | | `SlotSpecificView:232,252,457` | `item:any`, `(cartItem:any)`, `(p:any)` | `MergedProduct`, `CartItem` | | | `PaymentAndOrderComponent:17,24,25` | `cartItems:any[]`, `constsData:any`, `selectedCoupons:any[]` | `CartItem[]`, `EssentialConstsApiType`, `EligibleCoupon[]` | | | `hooks/useProductSlotIdentifier:18,25`, `prominent-api-hooks:127` | `(slot/a/b/p: any)` | drop → inferred | | **Easy wins:** bulk `unknown` (29); delete fallback `as any` casts (≈18 incl. `contextString`); catalog/cart annotations (≈30) → `MergedProduct`/`CartItem`; icon types (4); trivial typed `any`s (jwt, setInterval, notif refs, toaster, styles). --- ## apps/web-ui **Summary:** **138** annotations / 35 files (2 prose excluded). Top: `slot-view` (20), `home.index` (18), `flash` (10), `home.product.$id` (9). Dominant: untyped list callbacks over cached API data (`MergedProduct`, `UserSlotWithProducts`), then `(error: any)`. | Location | Current code | Should be | Why | |---|---|---|---| | `FloatingCartBar:43,44`, `PaymentAndOrderComponent:34,35`, `cart:18,19`, `checkout:38,39`, `home.cart:18,19` | `Record` / `(p:any)` | `Record` / `(p: MergedProduct)` | from `useAllProducts()` | | `AddToCartDialog:45,78`, `ProductCard:38`, `home.product.$id:63`, `flash:205` | `(item:any)` | `CartItem` | `useGetCart()` | | `slot-view:399` | `(cartItem:any).productId` | `(cartItem: CartItem).skuId` | **latent bug** | | `AddToCartDialog:53,57,59,143`, `usePopulateCentralStores:15,30,31,43`, `home.product.$id:51,52,55,94,316,397`, `slot-view:58…153`, `home.index:623` | `(slot:any)`, `slot.products.forEach((p:any)`, slot maps, `(a/b:any)` | `UserSlotWithProducts`, `UserSlotProduct`, `Record` | `useSlots()` | | `usePopulateCentralStores:15` (+43–48) | extra fields `deliveryDate/displayDate/displayTime` | **domain**: not on `UserSlotWithProducts` but required by `SlotInfo` | stale/dead-field mismatch | | `usePopulateCentralStores:25` | `Record` | `ProductSlotMap[number]` (export) | | | `flash:38,67,77,83,166`, `home.index:154,155,225,326`, `home.search:87,166`, `offers:32,67,68`, `slot-view:139…238`, `stores.$storeId:44,125,163`, `usePopulateCentralStores:28`, `prominent-api-hooks:167` | `(product/p/a/b:any)` | `MergedProduct` | | | `stores.$storeId:28,38` | `new Map`, `(product:any)` | `Map`, `(product: UserStoreProduct)` | | | `home.index:133,135,142,143` | `Record`, `Map`, `const ordered/rest: any[]` | `MergedProduct[]` etc | | | `ProductCard:11` | `item:any` | `MergedProduct` | | | `flash:36,54,124,156`, `home.index:378,530`, `slot-view:228,294,339`, `stores:78,104`, `stores:161` | `(store:any)`, `stores:any[]`, `(product:any)` | `UserStoreSummary`, `UserStoreSampleProduct` | | | `checkout-hooks:16,44,67`, `CheckoutAddressSelector:11,72`, `checkout:31`, `flash.checkout:74`, `home.checkout:75` | `useState`, `(addr/address:any)` | `UserAddress` | | | `AddressForm:48,59,90,92`, `MeOrderMenu:68,95`, `PaymentAndOrderComponent:67`, `me.addresses:121,149`, `me.edit-profile:46,56,103`, `me.orders.$id:42,54` | `onError/catch (e:any)`, `(error:any)` | delete / `unknown`; `AddressForm` narrow `Yup.ValidationError` | | | `me.orders.$id:99` | `order as any` | drop (`UserOrderDetail`) | | | `me.orders.$id:117,301`, `me.orders:189,75,325` | `(item/product:any)`, `useState` | `UserOrderItemSummary`, `UserOrderSummary` | | | `BottomNavigation:72`, `Sidebar:81`, `me:167` | `to: … as any` | generated route union | | | `Topbar:27` | `'/home/search' as any` | drop cast | | | `home.index:122,291,472,474,513` | `(t/tag/b/_:any)`, `banners:any[]` | tag type, `UserBanner`, `string` | | | `home.product.$id:379` | `(deal:any)` | `SpecialDealCore` | | | `lib/auth-context:15,24,110` | `userDetails:any`, `(details:any)` | local `User` | | | `flash-cart-store:4,5` | `any \| null` | `MergedProduct \| null` (domain) | | | `useUploadToObjectStorage:26` | `contextString as any` | drop | | | `ProductCard:47,48`, `PaymentAndOrderComponent:13`, `slot-view:101,104`, `me.complaints:168` | `Record`, `(slot:any)`, `cartItems:any[]`, `as any` time, `(complaint:any)` | `SlotInfo`, `CartItem[]`, widen helper, `UserComplaint` | | **Easy wins:** drop redundant annotations on tRPC `onError` (10) and `cartData.items.find` (5); replace 5-file `productsById` boilerplate (10); `catch`→`unknown` (4); export `CartItem` + `SlotInfo`/`ProductSlotMap`; drop `navigate` casts; **fix `slot-view:399` bug**. --- ## apps/admin-web **Summary:** **128** annotations (3 prose excluded) / 50 files. Top: `orders.sequence` (11), `prices` (10), `products.edit` (9). Dominant: `onError/catch` (36 = 28%), untyped rows, route casts. | Location | Current code | Should be | Why | |---|---|---|---| | 24 sites (`stores.edit:35`, `orders.sequence:471,623,665`, `orders.list:71,171`, `coupons.*`, `slots:51`, `complaints:81`, `SlotForm:91,106`, `notifications:35`, `TagMenu:56`, `UserIncidentsView:31`, `staff-auth:62`, `orders.$id:68,84,95,105`, `CancelOrderDialog:34`, `prices:281`, `stores.new:27`) | `onError: (error:any)` | delete | tRPC infers | | 13 sites (`product-tags.new:50`, `product-tags.edit:65`, `coupons.new:57`, `products.$id:81`, `notifications:72`, `vendor-snippets:54,296`, `OrderOptionsMenu:69`, `products.new:106`, ProductGroupForm:68, VendorSnippetForm:87, products.edit:189, stores:114) | `catch (error:any)` | `unknown` + narrow | | | `orders.sequence:363,382,398,456,603,635` | `(deliverySequence as any)` | drop → `AdminDeliverySequence` | | | `slots.$id.edit:48`, `slots:28`, `rebalance:28`, `slots.new:43`, `SlotForm:47` | `(p:any)` | `AdminSlotProductSummary` | | | `SlotForm:36` | `(snippet:any)` | `AdminVendorSnippetWithAccess` (shape mismatch, domain) | | | `SlotForm:189`, `TagForm:114`, `prices:41` | `(prod/sku/f:any)` | `AdminSku`, `SkuSummary`, `AdminSkuFeature` | | | `product-tags.edit:92` | `(p:any)` | `AdminProductTagAssignment` | | | `ProductGroupForm:12`, `product-groupings:25,108` | `products:any[]`, `useState` | `AdminSku[]` | | | `UserIncidentsView:142` | `(incident:any)` | `UserIncident` (domain) | | | `products:149` | `(product:any)` | `AdminProductWithRelations` | | | `rebalance:13`, `slots:14` | `item:any` | `AdminSlotWithProducts` | | | `rebalance:16,128`, `slots:15,242` | `Dispatch<…any[]>`, `useState` | `string[]` | | | `slots.detail:31` | `useState` | domain (`AdminSlotProductSummary` lacks `shortDescription`) | | | `orders.sequence:69` | `orders:any[]` | `AdminSlotOrder[]` | | | `prices:20,23,24,25,148,199,219,265` | `sku:any`, `Record`, `const update:any` | `SkuRow`, existing `PendingChange`, mutation input | | | `vendor-snippets:233` | `useState` | `{ orders; snippetCode }` (align `totalAmount` number/string) | | | `SnippetOrdersView:20` | `sequence:any[]` | `unknown`/`number[]` | shared is `unknown` | | `coupons.new:30`, `products.edit:87`, `products.new:19` | `values: any` | export `CouponFormValues` / `ProductFormData` | | | `CouponForm:78,84,171,206,207,220,252,253` | `function(value:any)`, `(values as any)`, `(errors as any)` | `CouponFormValues`, narrow | | | `coupons.tsx:14,17,114,220,363` | `item/coupon:any` | `Coupon & {relations}` / reserved (domain) | | | `MultiSelect:25`, `SearchableSelect:23`, `coupons:302,430` | `triggerComponent?: any` | `(props:{onClick:()=>void}) => ReactNode` | | | `SnippetMenu:20`, `TagMenu:11` | `triggerStyle?: any` | `string \| CSSProperties` | | | `TagForm:96` | `forwardRef` | `unknown` (ref unused) | | | `customize-app:12,61,62,63` | `Record`, `value:any`, `setFieldValue(any)`, `router:any` | `Record`, `unknown`, local router shape | | | `customize-app.ordering:113`, `popular:126`, `product-tags.order:106` | `value.map((id:any)=>parseInt(id))` | root: `Constant.value` | | | `stores.edit:33`, `dashboard.index:82`, `customize-app:173`, `orders.tsx:24`, `dashboard.tsx:108` | `navigate({to: … as any})` | generated route union / drop | | | `products.edit:66,101,108,139,162,166,183`, `products.new:33,40,68,84,88` | `(variant/ci/a/attr:any)`, `as any` | `Variant`, `AdminProductComboItem`, `SkuFeatureLike`, `CreateComboItemInput` | | | `orders.$id:159` | `const mutationData:any` | `initiateRefund` input | | | `toaster:16`, `event-bus:6,11` | `_data:any`, `...args: any[]` | `unknown` | | **Easy wins:** drop 23 `onError`; 13 `catch`→`unknown`; type slot rows (9); `prices` `SkuRow`+`PendingChange` (10); export `ProductFormData/Variant/Attribute/CouponFormValues`; fix shared `Constant.value`; typed routes (5). --- ## packages/shared + web-components + ui + migrator **Summary:** **28** tokens / 17 files. Top: `app-common.types.ts` (5), `migrator/sqliteToPostgres` (4), `web-components/data-table.tsx` (4). Dominant: untyped component props + RN style/asset props. (No `catch (e: any)` in scope.) | Location | Current code | Should be | Why | |---|---|---|---| | `packages/shared/types/app-common.types.ts:20` | `queryParams?: Record` | `Record` | only `String(value)` | | `app-common.types.ts:60` | `item: any` | generic `ListItemProps` (domain) | shared list renderer | | `app-common.types.ts:78` | `item: any` | hoist shared product type (domain) | consumers use id/images/name/flashPrice/incrementStep | | `app-common.types.ts:87` | `products: any[]` | shared product type (domain) | offers section | | `app-common.types.ts:96` | `product: any` | shared product type (domain) | AddToCartDialog | | `packages/shared/types/const.types.ts:8` | `value: any` | `ConstValueType \| ConstValueType[]` (defined line 18) | consumers branch/narrow | | `migrator/sqliteToPostgres/index.ts:47` | `.all() as any[]` | named `SqliteColumnRow` | PRAGMA shape fixed | | `migrator/.../index.ts:113` | `parseValue(value:any): any` | `unknown` | returns JSON.parse/raw | | `migrator/.../index.ts:181` | `.all() as any[]` | `Record[]` | dynamic `SELECT *` | | `packages/ui/src/services/axios.ts:56` | `(err as any).original = error` | `Object.assign` / typed `Error & { original: unknown }` | | | `packages/ui/src/lib/refresh-context.tsx:7` | `queryClient: any` | `QueryClient` | dep exists | | `packages/ui/src/lib/tailwind.ts:6` | `create(tailwindConfig as any)` | `as TwConfig` (`twrnc`) | | | `packages/ui/src/components/image-viewer.tsx:16` | `style?: any` | `StyleProp` | | | `packages/ui/src/components/info-dialog.tsx:15`, `dropdown.tsx:17` | `style?: any` | `StyleProp` | | | `packages/ui/src/components/search-bar.tsx:21` | `forwardRef` | `React.ElementRef` | | | `packages/ui/src/components/text.tsx:25` | `let fontWeight: any` | `TextStyle['fontWeight']` | | | `packages/ui/src/components/profile-image.tsx:11,25` | `file?: any`, `setFile:(file:any)` | `ImagePickerAsset & { name }` | | | `packages/ui/src/components/use-pick-image.tsx:13` | `setFile: (file: any) => void` | `ImagePickerAsset \| ImagePickerAsset[] \| null` | root of many `assets: any` | | `packages/ui/src/components/ImageUploaderNeo.tsx:22` | `(files: any)` | `ImagePickerAsset \| ImagePickerAsset[] \| null` | | | `packages/ui/src/components/ImageCarousel.tsx:27` | `(event: any)` | `NativeSyntheticEvent` | | | `packages/web-components/.../my-text-input.tsx:45` | `{...(props as any)}` | `TextareaHTMLAttributes` union | spread onto `