freshyo/ANY_USAGE_REPORT.md
2026-09-12 23:03:05 +05:30

313 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# `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<any>` helpers |
| `apps/admin-ui` | ~161 | `onError/catch (e: any)`; `router.push(... as any)`; entity `useState<any>` |
| `apps/user-ui` + `apps/fallback-ui` | ~137 | `catch/onError`; Expo/TanStack `as any`; catalog/cart `item: any` |
| `apps/web-ui` | ~138 | `Record<number, any>`/`(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<AppRouter>`; `.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<string, any>``Record<string, unknown>`** for heterogeneous bags.
7. **`z.any()``z.unknown()`** in Zod schemas (or a precise union).
8. **RN/Expo props → their real types:** `StyleProp<ViewStyle>` / `StyleProp<ImageStyle>`, `NativeSyntheticEvent<NativeScrollEvent>`, `TextStyle['fontWeight']`, `ImagePicker.ImagePickerAsset`.
9. **Icon `name={x as any}` → `React.ComponentProps<typeof MaterialIcons>['name']`** (the glyph-key union).
10. **`Promise<any>` db-helper returns → `InferSelectModel<typeof table>` / explicit `Pick<Row, …>`.**
## 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<string, any>``Record<string, unknown>` | `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 `<T = any>` 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<NotifQueueMessage>` / `<{orderIds:number[]}>` / `<CancellationMessage>` | pushed message shapes are known |
| `lib/const-store.ts:34,58` | `<T = any>` | `<T = unknown>` | 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<string, never>` | 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<string, number[]>` union | shared `AdminDeliverySequence` |
| `user-apis/apis/payments.ts:129` | `payload as any` | `Record<string, unknown>` | schema is `jsonText<unknown>` |
| `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<any>`, `(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<T>` default; `getAllTagsForCache()` |
| `admin-apis/apis/user.ts:48,60,98,117,170,258` | `(u:any)` etc | `Pick<User,…>`, `Pick<PlacedOrder,…>`, `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<T>` = `InferSelectModel<typeof T>`, `Insert<T>` = `InferInsertModel<typeof T>`.
| 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<skuFeatures>` | |
| `product.ts:596` | `(tag: any)` | inferred (cast at 594) | |
| `product.ts:598,669,709` | `(assignment: any)` | `Row<productTags> & { 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<Insert<productMarketStats>>` | |
| `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<Parameters<typeof db.transaction>[0]>[0]`) | |
| `stores/store-helpers.ts:312,315,326` | `(item\|s\|sku: any)` | delete → inferred | |
| `admin-apis/store.ts:7,17` (both pkgs) | `Promise<any[]>` / `Promise<any\|null>` | `Array<Row<storeInfo> & { owner: Row<staffUsers>\|null }>` | |
| `admin-apis/staff-user.ts:22,78` (both) | `Promise<any[]>` | inferred relation / `StaffRole[]` | |
| `admin-apis/user.ts:5,28,95,122,184,224,245` (both) | `Promise<any…>` | `Row<users>`, `Pick<Row<users>,…>`, `Row<userIncidents>`, etc. | select shapes |
| `admin-apis/coupon.ts:10,53,175,211,213,300` (both) | `any[]`/`Promise<any…>`/`input: any` | `Row<coupons> & {…}`, `Row<reservedCoupons>`, `ReservedCouponInput` | InferInsertModel |
| `user-apis/auth.ts:102,119` (both) | `const userUpdate: any` | `Partial<Insert<users>>` / `Partial<Insert<userDetails>>` | |
| `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<Insert<…>>`; `catch``unknown`; **free bug fix** at `slots.ts:191/194`; ~35 `Promise<any>` 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<AdminProductWithRelations,'skus'> & { skus: SerializedAdminSku[] }` | |
| `slots/index.tsx:11`, `rebalance-orders:10` | `item: any` | `AdminSlotWithProducts` | |
| `slots/slot-details.tsx:24`, `product-groupings:130`, `products/detail:146` | `useState<any…>` | `AdminSlotProductSummary[]`, `AdminSku[]`, `AdminProductReviewWithSignedUrls` | |
| `slots/index:238`, `rebalance:123` | `useState<any[]>` | `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<any,…>` | `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<string,any>`, `value:any`, `setFieldValue(any)`, `router:any` | `Record<string,unknown>`, `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<string,any>`, `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<ViewStyle>`; 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<typeof Ionicons/MaterialIcons>['name']` | |
| `my-orders:68,69` | `getStatusColor(): any` | typed `StatusColor` | |
| `home/index.tsx` (many) | `slot: any`, `(p: any)`, `dashboardTags:any[]`, `Record<number,any[]>`, `useRef<any>`, `onLayout(e:any)`, `useState<any[]>` | `UserSlotWithProducts`, `UserSlotProduct`, `AllProductsApiType['tags']`, `MergedProduct[]`, `ComponentRef<typeof ScrollView>`, `LayoutChangeEvent` | catalog types exist |
| `ProductDetail:48,75,76,79,87,147,710` | `useState<any[]>`, `(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<any>` | `ImagePicker.ImagePickerAsset` | |
| `useUploadToObjectStore:26` | `contextString as any` | drop cast | shared `ContextString` assignable |
| `notif-context:45,46` | `useRef<any>` | listener return types | |
| `toaster:5` | `data: any` | `Record<string, unknown>` | |
| `AddToCartDialog` (6) | `(item/slot/p: any)`, `Record<number,any>` | `CartItem`, `UserSlotWithProducts`, `UserSlotProduct` | |
| `AuthContext:14` | `Record<string,any>` | 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<typeof setInterval>`, local `DemoResponse` | |
| `ProductCard:22,29,84,93,94` | `item:any`, `ComponentType<any>`, `(cartItem:any)`, `Record<number,any>`, `(slot:any)` | `MergedProduct`, `ComponentType<PropsWithChildren>`, `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<number,any>` / `(p:any)` | `Record<number, MergedProduct>` / `(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<number, UserSlotWithProducts>` | `useSlots()` |
| `usePopulateCentralStores:15` (+4348) | extra fields `deliveryDate/displayDate/displayTime` | **domain**: not on `UserSlotWithProducts` but required by `SlotInfo` | stale/dead-field mismatch |
| `usePopulateCentralStores:25` | `Record<number,any>` | `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<number,any>`, `(product:any)` | `Map<number, MergedProduct>`, `(product: UserStoreProduct)` | |
| `home.index:133,135,142,143` | `Record<number,any[]>`, `Map<number,any>`, `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<any>`, `(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<any[]>` | `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<number,any>`, `(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<any[]>` | `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<any[]>` | `string[]` | |
| `slots.detail:31` | `useState<any[]>` | domain (`AdminSlotProductSummary` lacks `shortDescription`) | |
| `orders.sequence:69` | `orders:any[]` | `AdminSlotOrder[]` | |
| `prices:20,23,24,25,148,199,219,265` | `sku:any`, `Record<string,any>`, `const update:any` | `SkuRow`, existing `PendingChange`, mutation input | |
| `vendor-snippets:233` | `useState<any>` | `{ 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<any,…>` | `unknown` (ref unused) | |
| `customize-app:12,61,62,63` | `Record<string,any>`, `value:any`, `setFieldValue(any)`, `router:any` | `Record<string,unknown>`, `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<string, any>` | `Record<string, unknown>` | only `String(value)` |
| `app-common.types.ts:60` | `item: any` | generic `ListItemProps<T>` (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<string, unknown>[]` | 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<ImageStyle>` | |
| `packages/ui/src/components/info-dialog.tsx:15`, `dropdown.tsx:17` | `style?: any` | `StyleProp<ViewStyle>` | |
| `packages/ui/src/components/search-bar.tsx:21` | `forwardRef<any,…>` | `React.ElementRef<typeof PaperTextInput>` | |
| `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<NativeScrollEvent>` | |
| `packages/web-components/.../my-text-input.tsx:45` | `{...(props as any)}` | `TextareaHTMLAttributes` union | spread onto `<textarea>` |
| `packages/web-components/.../data-table.tsx:7,12,13` | `(value:any,row:any)`, `data:any[]`, `(row:any,index)` | generic `Column<T>` / `DataTableProps<T>`, `T extends Record<string, unknown>` | |
**Easy wins:** `queryParams`/`Constant.value`; migrator `unknown` + typed PRAGMA row; `QueryClient`/`TwConfig`; RN prop types (10); `Object.assign`; make `DataTable` generic. **4 domain items:** product-shaped props in `app-common.types.ts`.
---
## Suggested remediation order
1. **Mechanical, huge payoff (~200 sites, no domain knowledge):** `catch`/`onError``unknown`; delete tRPC `onError` annotations; `Record<string, any>``unknown`; `z.any()``z.unknown()`; delete `: any` from typed Drizzle callbacks.
2. **Shared root causes (removes dozens downstream):** `Constant.value`, `app-common.types.ts`, `use-pick-image.tsx`, export `CartItem`/`SlotInfo`/`MergedProduct` and `ProductFormData`/`CouponFormValues`.
3. **Backend result typing:** replace `Promise<any…>` db-helper returns and `any[]` procedures with `InferSelectModel`/shared types — unblocks both admin frontends.
4. **Typed routes:** Expo `Href` / TanStack generated union (removes ~50 `as any`).
5. **Domain-input types to define:** `UserIncident`, reserved-coupon/client-coupon-with-relations, `RefundInput`, shared product type, `NotificationToast` payload.
6. **Bugs masked by `any` (fix while typing):** `web-ui/src/routes/slot-view.tsx:399` (`cartItem.productId``skuId`), `db_helper_sqlite/src/admin-apis/slots.ts:191/194` (`isDeleted` not selected), `usePopulateCentralStores.ts:15` (`deliveryDate`/`displayDate`/`displayTime` fields not on the slot type).