freshyo/change-log.txt
2026-09-14 09:43:45 +05:30

1662 lines
153 KiB
Text
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.

=== Session change log ===
Session started: 2026-09-02
Rule: Before changing any file that is not a .md or .txt file, write the current timestamp followed by the exact diff here first, then make the change.
(no file changes made yet in this session)
[2026-09-02 20:34:48] DELETE dead files per DEAD_CODE_REPORT.md §1 (28 files):
Removed in packages/db_helper_sqlite/src/helper_methods/:
- banner.ts (entire file — legacy duplicate, never imported)
- complaint.ts (entire file — legacy duplicate, never imported)
- const.ts (entire file — legacy duplicate, never imported)
- coupon.ts (entire file — legacy duplicate, never imported)
- order.ts (entire file — legacy duplicate, never imported)
- product.ts (entire file — legacy duplicate, never imported)
- slots.ts (entire file — legacy duplicate, never imported)
- staff-user.ts (entire file — legacy duplicate, never imported)
- store.ts (entire file — legacy duplicate, never imported)
- user.ts (entire file — legacy duplicate, never imported)
- vendor-snippets.ts (entire file — legacy duplicate, never imported)
Removed in packages/db_helper_postgres/src/helper_methods/: (same 11 files as above)
- banner.ts, complaint.ts, const.ts, coupon.ts, order.ts, product.ts, slots.ts, staff-user.ts, store.ts, user.ts, vendor-snippets.ts (entire files — legacy duplicates, never imported)
Removed in both packages (db_helper_sqlite + db_helper_postgres):
- src/user-apis/tags.ts (entire file — imported by no one)
- src/common-apis/utils.ts (entire file — imported by no one; formatDate/generateCode/calculateDiscount unreferenced)
- src/db/types.ts (entire file — imported by no one)
Kept: helper_methods/upload-url.ts (alive — re-exported by index.ts, used by backend).
Command: rm <each file listed above>
[2026-09-02 20:38:02] COMPLETED deletion of 28 dead files (DEAD_CODE_REPORT.md §1).
Verification:
- grep for 'helper_methods|user-apis/tags|common-apis/utils|db/types' across both packages + apps/backend: only remaining match is the live import of helper_methods/upload-url.ts in both index.ts files.
- tsc --noEmit on both packages reports pre-existing errors only (in untouched live files: src/db/seed.ts, src/user-apis/order.ts, src/user-apis/product.ts, src/admin-apis/complaint.ts, src/admin-apis/product.ts). No errors reference any deleted module.
[2026-09-02 20:45:45] DELETE dead exported methods per DEAD_CODE_REPORT.md §2a (8 symbols, both packages).
=== packages/db_helper_sqlite/src/admin-apis/product.ts ===
- removed getAllUnits (lines 650-657):
export async function getAllUnits(): Promise<AdminUnit[]> {
const allUnits = await db.query.units.findMany({
orderBy: units.shortNotation,
})
return allUnits.map(mapUnit)
}
- removed addProductToGroup (lines 1032-1034):
export async function addProductToGroup(groupId: number, productId: number): Promise<void> {
await db.insert(productGroupMembership).values({ groupId, productId })
}
- removed removeProductFromGroup (lines 1036-1042):
export async function removeProductFromGroup(groupId: number, productId: number): Promise<void> {
await db.delete(productGroupMembership)
.where(and(
eq(productGroupMembership.groupId, groupId),
eq(productGroupMembership.productId, productId)
))
}
=== packages/db_helper_postgres/src/admin-apis/product.ts ===
- identical removals (getAllUnits at 232-239, addProductToGroup at 496-498, removeProductFromGroup at 500-506)
=== packages/db_helper_sqlite/src/admin-apis/vendor-snippets.ts ===
- removed getOrderItemsByOrderIds (lines 196-209):
export async function getOrderItemsByOrderIds(orderIds: number[]) {
return await db.query.orderItems.findMany({
where: inArray(orderItems.orderId, orderIds),
with: { sku: { with: { product: true, features: true } } },
})
}
- removed getOrderStatusByOrderIds (lines 210-214):
export async function getOrderStatusByOrderIds(orderIds: number[]) {
return await db.query.orderStatus.findMany({
where: inArray(orderStatus.orderId, orderIds),
})
}
=== packages/db_helper_postgres/src/admin-apis/vendor-snippets.ts ===
- removed getOrderItemsByOrderIds (lines 188-200) — same signature; body joins product.unit instead of sku:
export async function getOrderItemsByOrderIds(orderIds: number[]) {
return await db.query.orderItems.findMany({
where: inArray(orderItems.orderId, orderIds),
with: { product: { with: { unit: true } } },
})
}
- removed getOrderStatusByOrderIds (lines 201-205) — identical to sqlite
=== packages/db_helper_sqlite/src/admin-apis/staff-user.ts + packages/db_helper_postgres/src/admin-apis/staff-user.ts ===
- removed updateUserSuspensionStatus (lines 101-109 in both) — duplicate of live upsertUserSuspension:
export async function updateUserSuspensionStatus(userId: number, isSuspended: boolean): Promise<void> {
await db
.insert(userDetails)
.values({ userId, isSuspended })
.onConflictDoUpdate({
target: userDetails.userId,
set: { isSuspended },
})
}
=== packages/db_helper_sqlite/src/user-apis/user.ts + packages/db_helper_postgres/src/user-apis/user.ts ===
- removed getUserWithCreds (lines 15-28 in both):
export async function getUserWithCreds(userId: number) {
const result = await db
.select()
.from(users)
.leftJoin(userCreds, eq(users.id, userCreds.userId))
.where(eq(users.id, userId))
.limit(1)
if (result.length === 0) return null
return { user: result[0].users, creds: result[0].user_creds }
}
=== packages/db_helper_sqlite/src/user-apis/auth.ts + packages/db_helper_postgres/src/user-apis/auth.ts ===
- removed createUserWithCreds (lines 145-168 in both):
export async function createUserWithCreds(input: { name; email; mobile; hashedPassword }) {
return db.transaction(async (tx) => { ...insert user, insert userCreds, return user... })
}
=== packages/db_helper_sqlite/index.ts (export list cleanup) ===
- removed line: getAllUnits,
- removed lines: addProductToGroup,
removeProductFromGroup,
- removed lines: getOrderItemsByOrderIds,
getOrderStatusByOrderIds,
- removed line: updateUserSuspensionStatus,
- removed line: getUserWithCreds as getUserWithCreds,
- removed line: createUserWithCreds as createUserAuthWithCreds,
=== packages/db_helper_postgres/index.ts (export list cleanup) ===
- same 8 export-line removals as sqlite index.ts
=== apps/backend/src/sqliteImporter.ts (re-export chain cleanup) ===
- removed line: getAllUnits,
- removed lines: addProductToGroup,
removeProductFromGroup,
- removed lines: getOrderItemsByOrderIds,
getOrderStatusByOrderIds,
- removed line: updateUserSuspensionStatus,
- removed line: createUserAuthWithCreds,
- removed line: getUserWithCreds,
Note: apps/backend/src/dbService.ts uses `export * from '@/src/sqliteImporter'` — no per-symbol change needed there.
Any imports left unused by these deletions (e.g. productGroupMembership, inArray, userCreds) will be removed in a follow-up entry if tsc/grep confirms they are unused.
[2026-09-02 20:49:23] FOLLOW-UP: remove imports/orphans left unused by §2a deletions.
=== packages/db_helper_sqlite/src/admin-apis/product.ts ===
- removed now-unused local type + mapper (only consumer was deleted getAllUnits):
line 103: type UnitRow = InferSelectModel<typeof units>
line 117: const mapUnit = (unit: UnitRow): AdminUnit => ({
id: unit.id,
shortNotation: unit.shortNotation,
fullName: unit.fullName,
})
(postgres product.ts keeps mapUnit — still used there at lines 111, 142)
=== packages/db_helper_sqlite/src/user-apis/user.ts ===
- import line 2: removed 'userCreds' from import { notifCreds, unloggedUserTokens, userCreds, userDetails, users } from '../db/schema'
=== packages/db_helper_postgres/src/user-apis/user.ts ===
- same import fix as sqlite user.ts
[2026-09-02 20:51:30] COMPLETED §2a deletion (8 dead exported methods, both packages + re-export chains).
Removed: getAllUnits, addProductToGroup, removeProductFromGroup, getOrderItemsByOrderIds, getOrderStatusByOrderIds, updateUserSuspensionStatus, getUserWithCreds, createUserWithCreds (+ orphaned mapUnit/UnitRow in sqlite product.ts, orphaned userCreds imports in both user.ts).
Verification:
- grep across packages + apps: zero live references remain (only commented lines in postgresImporter.ts).
- tsc --noEmit: sqlite = 4 errors (same as pre-change baseline), postgres = 40 errors (all pre-existing drizzle inference mismatches in untouched files), backend = 17 errors (all pre-existing, in untouched files). No error references any deleted symbol.
[2026-09-02 20:54:32] DELETE §2b/§2c dead exports + PARITY RENAME + RESTORE two methods for future use.
--- DELETIONS, packages/db_helper_sqlite/src/admin-apis/product.ts ---
- removed createSpecialDealsForSku (lines 1124-1145): inserts specialDeals rows keyed by skuId, returns mapped AdminSpecialDeal[]
- removed updateSkuDeals (lines 1147-1241): diff-based sync of specialDeals for a skuId (add/remove/update validTill)
- removed mergeSkus (lines 1243-1366, end of file): merges fromSkuId into toSkuId across orderItems, specialDeals, cartItems, couponApplicableProducts, JSON skuIds columns (deliverySlotInfo, homeBanners, coupons, reservedCoupons, vendorSnippets), keyValStore popularItems, then deletes skuFeatures + sku + orphaned product
--- DELETIONS, packages/db_helper_postgres/src/admin-apis/product.ts ---
- removed toggleProductOutOfStock (lines 181-203): flips productInfo.isOutOfStock
- removed createSpecialDealsForProduct (lines 677-698): postgres counterpart of createSpecialDealsForSku (keyed by productId)
- removed updateProductDeals (lines 700-775): postgres counterpart of updateSkuDeals
--- DELETION, packages/db_helper_postgres/src/lib/automated-jobs.ts ---
- removed toggleFlashDeliveryForItems (lines 10-16): bulk productInfo.isFlashAvailable update
- import line 2: removed 'productInfo' from import { productInfo, keyValStore } from '../db/schema'
- import line 3: removed 'inArray' from import { inArray, eq } from 'drizzle-orm'
--- EXPORT LIST, packages/db_helper_sqlite/index.ts ---
- removed lines: createSpecialDealsForSku,
updateSkuDeals,
mergeSkus,
--- EXPORT LIST, packages/db_helper_postgres/index.ts ---
- removed lines: createSpecialDealsForProduct,
updateProductDeals,
toggleProductOutOfStock,
- removed line: toggleFlashDeliveryForItems,
--- EXPORT LIST, apps/backend/src/sqliteImporter.ts ---
- removed lines: createSpecialDealsForSku,
updateSkuDeals,
mergeSkus,
--- PARITY RENAME, packages/db_helper_postgres/src/user-apis/product.ts ---
- renamed getSuspendedProductIds -> getSuspendedSkuIds (backend calls getSuspendedSkuIds; sqlite exports that name).
NOTE: implementation unchanged (queries productInfo.isSuspended) — postgres schema has no sku/marketStats model, so this is the closest postgres equivalent of the sqlite query on productMarketStats.skuId.
- packages/db_helper_postgres/index.ts: export line getSuspendedProductIds, -> getSuspendedSkuIds,
--- RESTORE (user request: needed in future), both packages ---
packages/db_helper_sqlite/src/user-apis/user.ts + packages/db_helper_postgres/src/user-apis/user.ts:
- re-added getUserWithCreds:
export async function getUserWithCreds(userId: number) {
const result = await db
.select()
.from(users)
.leftJoin(userCreds, eq(users.id, userCreds.userId))
.where(eq(users.id, userId))
.limit(1)
if (result.length === 0) return null
return {
user: result[0].users,
creds: result[0].user_creds,
}
}
- re-added 'userCreds' to the schema import line in both files.
packages/db_helper_sqlite/src/user-apis/auth.ts + packages/db_helper_postgres/src/user-apis/auth.ts:
- re-added createUserWithCreds:
export async function createUserWithCreds(input: {
name: string
email: string
mobile: string
hashedPassword: string
}) {
return db.transaction(async (tx) => {
const [user] = await tx.insert(users).values({
name: input.name,
email: input.email,
mobile: input.mobile,
}).returning()
await tx.insert(userCreds).values({
userId: user.id,
userPassword: input.hashedPassword,
})
return user
})
}
packages/db_helper_sqlite/index.ts + packages/db_helper_postgres/index.ts:
- re-added export lines: createUserWithCreds as createUserAuthWithCreds, (user auth block)
getUserWithCreds as getUserWithCreds, (user profile block)
apps/backend/src/sqliteImporter.ts:
- re-added re-export lines: createUserAuthWithCreds, and getUserWithCreds,
[2026-09-02 20:58:19] FOLLOW-UP: remove orphaned CreateSpecialDealInput (only consumers were the deleted deal functions; zero usages repo-wide).
=== packages/db_helper_sqlite/src/admin-apis/product.ts ===
- removed (line ~1118):
export interface CreateSpecialDealInput {
quantity: number
price: number
validTill: string | Date
}
=== packages/db_helper_postgres/src/admin-apis/product.ts ===
- removed identical interface (line ~647)
[2026-09-02 20:59:28] COMPLETED §2b/§2c deletions + parity rename + restoration of createUserWithCreds/getUserWithCreds.
Verification:
- grep: zero references to createSpecialDealsForSku, updateSkuDeals, mergeSkus, createSpecialDealsForProduct, updateProductDeals, toggleProductOutOfStock, toggleFlashDeliveryForItems, getSuspendedProductIds (only commented postgresImporter.ts lines excluded from check).
- Restored methods present in both packages' src + index.ts + backend sqliteImporter.ts.
- Postgres now exports getSuspendedSkuIds (renamed from getSuspendedProductIds) matching sqlite/backend usage.
- tsc --noEmit baselines unchanged: sqlite 4, postgres 40, backend 17 errors — all pre-existing, none reference touched symbols.
[2026-09-02 21:01:52] §3 EXPORT-ONLY DEAD cleanup: remove from index.ts (implementations kept), delete non-exported dead types.
=== packages/db_helper_sqlite/index.ts ===
- removed line 10 block: export { staffRoleEnum, staffPermissionEnum } from './src/db/schema'
(these enum values still flow to consumers via the existing `export * from './src/db/schema'` — the explicit line was redundant)
- user product block: removed type lines type SkuSummary,
type OffersPageData,
type OffersPageProductData,
- store helpers block: removed line type AvailabilityCacheData,
- user order block: removed type lines type OrderWithFullData,
type OrderWithCancellationData,
- SKU Features block: removed lines cleanFeatureValue,
splitQuantityFeature,
type SkuFeatureLike,
(kept composeUnitNotation, composeSkuName — used by backend)
=== packages/db_helper_postgres/index.ts ===
- removed line 11 block: export { staffRoleEnum, staffPermissionEnum } from './src/db/schema';
- user order block: removed type lines type OrderWithFullData,
type OrderWithCancellationData,
=== apps/backend/src/sqliteImporter.ts ===
- removed type lines type OrderWithFullData,
type OrderWithCancellationData,
- removed line getUserNotifCred,
Note: getNotifCred index export line in both packages: getNotifCred as getUserNotifCred, — removed (see below).
=== packages/db_helper_sqlite/index.ts + packages/db_helper_postgres/index.ts ===
- user profile block: removed line getNotifCred as getUserNotifCred,
(function getNotifCred kept in user.ts — called internally by upsertNotifCred)
=== packages/db_helper_sqlite/src/user-apis/order.ts ===
- removed dead interface (lines 33-40):
export interface PlaceOrderInput {
userId: number
selectedItems: OrderItemInput[]
addressId: number
paymentMethod: 'online' | 'cod'
couponId?: number
userNotes?: string
isFlash?: boolean
}
- removed dead interface (lines 43-51):
export interface OrderGroupData {
slotId: number | null
items: Array<{
productId: number
quantity: number
slotId: number | null
product: typeof productInfo.$inferSelect
}>
}
=== packages/db_helper_postgres/src/user-apis/order.ts ===
- removed identical PlaceOrderInput (lines 29-36) and OrderGroupData (lines 39-47)
[2026-09-02 21:04:10] CORRECTION to previous entry: while editing packages/db_helper_sqlite/index.ts user product block I accidentally removed getAllSkusSummary, getOffersAndCombos and type ProductSummaryData (all three are USED by backend). Immediately restored them; final state of that block keeps: getAllProductsWithUnits, getAllSkusSummary, getOffersAndCombos, type ProductSummaryData. Only type SkuSummary, type OffersPageData, type OffersPageProductData were meant to be (and now are) removed.
[2026-09-02 21:07:40] CORRECTION: while editing the user order block of packages/db_helper_sqlite/index.ts I accidentally removed getOrdersByIdsWithFullData and getOrderByIdWithFullData (both USED by backend post-order-handler). Immediately restored. Final state: those two functions kept; only type OrderWithFullData and type OrderWithCancellationData removed from that block. Also fixed an earlier accidental newline removal after the store-helpers export close brace.
[2026-09-02 21:05:24] FOLLOW-UP: remove orphaned OrderItemInput from user-apis/order.ts in both packages (its only consumer, PlaceOrderInput, was deleted; definition-only now).
=== packages/db_helper_sqlite/src/user-apis/order.ts ===
- removed (lines 27-31):
export interface OrderItemInput {
productId: number
quantity: number
slotId: number | null
}
=== packages/db_helper_postgres/src/user-apis/order.ts ===
- removed identical interface (lines 23-27)
[2026-09-02 21:06:16] COMPLETED §3 export-only-dead cleanup.
Removed from index.ts (implementations kept in src):
- both packages: redundant staffRoleEnum/staffPermissionEnum export block (still exported via `export * from './src/db/schema'`), type OrderWithFullData, type OrderWithCancellationData, getNotifCred as getUserNotifCred
- sqlite only: type SkuSummary, type OffersPageData, type OffersPageProductData, type AvailabilityCacheData, cleanFeatureValue, splitQuantityFeature, type SkuFeatureLike (kept composeUnitNotation/composeSkuName)
Removed from apps/backend/src/sqliteImporter.ts: type OrderWithFullData, type OrderWithCancellationData, getUserNotifCred
Deleted dead types in user-apis/order.ts (both packages): PlaceOrderInput, OrderGroupData, OrderItemInput
Verification:
- Backend-used exports confirmed intact: getAllSkusSummary, getOffersAndCombos, ProductSummaryData, getOrdersByIdsWithFullData, getOrderByIdWithFullData, getAvailabilityForCache, getAllProductCombosForCache, getUserWithCreds, createUserAuthWithCreds, composeSkuName, composeUnitNotation
- tsc --noEmit baselines unchanged: sqlite 4 (seed.ts 1, order.ts 2, product.ts 1), postgres 40, backend 17 — all pre-existing, none reference touched symbols.
[2026-09-02 21:21:13] §4: remove never-referenced schema tables — addressZones, addressAreas, userNotifications, productCategories (BOTH packages).
NOTE: this only removes them from the drizzle schema (code). The actual DB tables/columns/FK constraints still exist in D1/Postgres — dropping them in the DB requires a migration, which the user will handle (per AGENTS.md). Both schema.ts files drift from the live DB until then; drizzle-kit push/generate must NOT be run by the agent.
=== packages/db_helper_sqlite/src/db/schema.ts ===
- removed addressZones table (lines 106-110):
export const addressZones = sqliteTable('address_zones', {
id: integer().primaryKey({ autoIncrement: true }),
zoneName: text('zone_name').notNull(),
addedAt: timestampText('added_at').notNull().default(sql`CURRENT_TIMESTAMP`),
})
- removed addressAreas table (lines 112-117):
export const addressAreas = sqliteTable('address_areas', {
id: integer().primaryKey({ autoIncrement: true }),
placeName: text('place_name').notNull(),
zoneId: integer('zone_id').references(() => addressZones.id),
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
})
- addresses table: changed zoneId column to drop FK (column KEPT — shared UserAddress type and user-apis/address.ts mapUserAddress still expose zoneId):
before: zoneId: integer('zone_id').references(() => addressZones.id),
after: zoneId: integer('zone_id'),
- removed productCategories table (lines 431-434):
export const productCategories = sqliteTable('product_categories', {
id: integer().primaryKey({ autoIncrement: true }),
name: text().notNull(),
description: text(),
})
- removed userNotifications table (lines 545-551):
export const userNotifications = sqliteTable('user_notifications', {
id: integer().primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
imageUrl: text('image_url'),
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
body: text('body').notNull(),
applicableUsers: jsonText<number[] | null>('applicable_users'),
})
- addressesRelations: removed line zone: one(addressZones, { fields: [addresses.zoneId], references: [addressZones.id] }),
- removed productCategoriesRelations (line 686): export const productCategoriesRelations = relations(productCategories, ({}) => ({}))
- removed userNotificationsRelations (lines 720-722):
export const userNotificationsRelations = relations(userNotifications, ({}) => ({
// No relations needed for now
}))
- removed addressZonesRelations (lines 749-752):
export const addressZonesRelations = relations(addressZones, ({ many }) => ({
addresses: many(addresses),
areas: many(addressAreas),
}))
- removed addressAreasRelations (lines 754-756):
export const addressAreasRelations = relations(addressAreas, ({ one }) => ({
zone: one(addressZones, { fields: [addressAreas.zoneId], references: [addressZones.id] }),
}))
=== packages/db_helper_postgres/src/db/schema.ts === (same removals, postgres style)
- removed addressZones table (lines 58-62, mf.table('address_zones', ...))
- removed addressAreas table (lines 64-69, mf.table('address_areas', ...))
- addresses.zoneId (line 54): dropped .references(() => addressZones.id) — column kept
- removed productCategories table (lines 318-322, mf.table('product_categories', ...))
- removed userNotifications table (lines 434-440, mf.table('user_notifications', ...))
- addressesRelations: removed zone relation line (line 496)
- removed productCategoriesRelations (line 573)
- removed userNotificationsRelations (lines 607-609)
- removed addressZonesRelations (lines 636-639) and addressAreasRelations (lines 641-643)
[2026-09-02 21:23:14] COMPLETED §4 schema table removal.
- Removed from both packages' src/db/schema.ts: addressZones, addressAreas, productCategories, userNotifications tables + productCategoriesRelations, userNotificationsRelations, addressZonesRelations, addressAreasRelations.
- addresses.zoneId column KEPT (FK reference + addressesRelations.zone relation removed) because shared UserAddress type and user-apis/address.ts still expose zoneId.
- Verified: zero remaining references to the 4 tables anywhere; jsonText/jsonb/text imports still used by other tables; tsc baselines unchanged (sqlite 4, postgres 40, backend 17 — all pre-existing).
- REMINDER: DB tables still exist in D1/Postgres until the user runs a drop migration (agent must not run drizzle migrations).
[2026-09-02 21:24:48] §6: DELETE dead backend files (10 files) + clean commented references to them.
DELETED (all verified: zero live imports — only commented mentions remain):
- apps/backend/src/postgresImporter.ts (entirely commented-out swap scaffold; restore from git when swapping to postgres)
- apps/backend/src/lib/signed-url-cache-old.ts
- apps/backend/src/lib/signed-url-cache.ts
- apps/backend/src/lib/disk-persisted-set.ts (body fully commented out)
- apps/backend/src/lib/catch-async.ts
- apps/backend/src/lib/axios.ts (phonepeAxios never imported)
- apps/backend/src/lib/delete-image.ts (deleteS3Image never imported)
- apps/backend/src/lib/event-queue.ts (enqueue never imported; queue-consumer.ts is the live path)
- apps/backend/src/lib/notif-service.ts
- apps/backend/src/types/db.types.ts
COMMENT CLEANUP (files are .ts → logged here):
=== apps/backend/index.ts ===
- removed line 5: // import signedUrlCache from '@/src/lib/signed-url-cache';
- removed line 12: // signedUrlCache.loadFromDisk(); // Disabled for Workers compatibility
Left untouched: s3-client.ts:141 prose comment (mentions the concept, not an import), dbService.ts prose header.
KEPT (verified alive): jobs/cache-creator.ts (Durable Object), lib/queue-consumer.ts, lib/flash-delivery-cron.ts, lib/seed.ts, lib/init.ts, lib/worker-init.ts, src/apis/**, src/test-controller.ts, types/hono.d.ts (ambient).
[2026-09-02 21:25:58] COMPLETED §6 backend dead-file deletion (10 files) + index.ts comment cleanup.
Verification: backend tsc --noEmit now reports 11 errors (was 17) — the 6 that vanished were signed-url-cache-old.ts's own pre-existing errors; remaining 11 are the same pre-existing set in untouched files (app.ts, lib/init.ts, s3-client.ts, main-router.ts, trpc-index.ts, sqlite order.ts/product.ts). No new errors.
Note: apps/backend/src/lib/s3-client.ts.txt (a .txt copy of s3-client) still exists — left alone, flagging as possible future cleanup.
[2026-09-02 21:31:17] Remove 4 dead-code items missed by our report but caught by cmd_v4_dead.md.
DELETED (3 files, verified zero live imports — only commented references):
- apps/backend/src/lib/redis-client.ts (stubbed: createClient = (args:any) => {}; only '// import redisClient' comments remain elsewhere)
- apps/backend/src/lib/redisKeyGetters.ts (only commented imports in trpc/apis/admin-apis/apis/slots.ts:7,613,694)
- apps/backend/src/middleware/auth.ts (verifyToken/requireRole never imported; app uses middleware/auth.middleware.ts + staff-auth.ts)
EDITED:
=== packages/shared/index.ts ===
- removed line 10 (dead type, definition-only):
export type CacheFilename = typeof CACHE_FILENAMES[keyof typeof CACHE_FILENAMES]
(CACHE_FILENAMES const itself is heavily used by backend cloud_cache.ts and web-ui/user-ui hooks — kept)
[2026-09-02 21:32:23] COMPLETED removal of 4 missed dead-code items.
- Deleted: apps/backend/src/lib/redis-client.ts, apps/backend/src/lib/redisKeyGetters.ts, apps/backend/src/middleware/auth.ts (middleware/ dir still has auth.middleware.ts + staff-auth.ts).
- Removed CacheFilename type from packages/shared/index.ts (CACHE_FILENAMES const kept — used by cloud_cache.ts + web-ui/user-ui hooks).
- Verified: zero remaining references (excluding comments); backend tsc --noEmit = 11 errors, same pre-existing baseline.
[2026-09-02 21:45:00] CREATED analysis scripts (new .js files, read-only analysis): scripts/dead-code/user-ui-files.js, user-ui-symbols.js, user-ui-suspects.js, user-ui-store-actions.js, user-ui-store-final.js, user-ui-store-precise.js
[2026-09-02 22:56:40] EXECUTE user_ui_glm.md cleanup: remove dead code, KEEP register.tsx, DO NOT touch npm dependencies.
--- FILE DELETIONS ---
- apps/user-ui/app/(drawer)/(tabs)/me/about/_layout.tsx (unreachable route, zero navigations)
- apps/user-ui/app/(drawer)/(tabs)/me/about/index.tsx (unreachable route)
KEPT: app/(auth)/register.tsx (user decision — keep for future sign-up wiring)
- apps/user-ui/constants/Colors.ts (never imported; constants/ dir becomes empty)
- apps/user-ui/components/BannerCarousel.tsx (never imported)
- apps/user-ui/src/components/google-sign-in.tsx (never imported; login uses common-ui's copy)
- apps/user-ui/app/api/auth/authorize+api.ts (unreferenced Google OAuth endpoints; ⚠️ no caller in repo —
- apps/user-ui/app/api/auth/callback+api.ts restore from git if an external Google redirect URI was configured)
- apps/user-ui/app/api/auth/token+api.ts
- UNUSED ASSETS (zero references in code/app.json/eas.json):
assets/images/adaptive-icon.png, farm2door-logo.png, freshyo-logoo.png, freshyo-logoo.svg,
logo_mini.jpg, partial-react-logo.png, react-logo.png, react-logo@2x.png, react-logo@3x.png,
splash-icon.png, symbuyote.png
(assets/images/logo.png, icon.png, favicon.png, freshyo-logo.png, fonts/SpaceMono-Regular.ttf KEPT — referenced)
--- EDITS ---
=== apps/user-ui/src/hooks/prominent-api-hooks.ts ===
- remove dead chain: useBanners() hook + BannersResponse type (only consumer was deleted BannerCarousel)
(CACHE_FILENAMES import stays — used by other cache hooks in the file)
=== apps/user-ui/hooks/useJWT.ts ===
- removed saveRoles, getRoles, deleteRoles (never called) + ROLES_KEY const (only used by the dead trio)
(AUTH_TOKEN_KEY, USER_ID_KEY, saveUserId, getUserId, saveAuthToken, getAuthToken, deleteAuthToken kept — live)
=== apps/user-ui/services/toaster.tsx ===
- removed InfoToast, ErrorToast, SuccessToast (never imported; NotificationToast + default Toast are live)
=== apps/user-ui/src/store/centralProductStore.ts ===
- removed clearProducts (interface field + implementation; nothing ever clears the product store)
=== apps/user-ui/src/store/addressStore.ts ===
- removed clearSelectedAddress (interface field + implementation)
=== apps/user-ui/src/store/centralSlotStore.ts ===
- removed clearSlotsData (interface field + implementation)
=== apps/user-ui/src/store/quickDeliveryStore.ts ===
- removed setDrawerHidden (never called; isDrawerHidden state + readers kept — flag is permanently false today)
=== apps/user-ui/components/PaymentAndOrderComponent.tsx ===
- removed commented createRazorpayOrderMutation block (~lines 97-104)
- removed commented initiateRazorpayPayment block (~lines 170-210)
(live COD/verify-payment flow untouched; react-native-razorpay dependency KEPT per instruction)
=== apps/user-ui/app/(drawer)/(tabs)/me/my-orders/index.tsx ===
- removed commented initiateRazorpayPayment block (~lines 564-585)
(live handleRetryPayment + createRazorpayOrderMutation untouched)
=== apps/user-ui/app/_layout.tsx ===
- removed line 40: console.log('from layout')
- line 11 import: removed unused Dimensions → import { Appearance, StatusBar, View } from "react-native";
NOT TOUCHED (per instruction): all npm dependencies in package.json (incl. react-native-razorpay, expo-image-picker, etc.)
NOT TOUCHED (left as-is): scripts/reset-project.js (template helper wired to npm script — user call), dist/ build artifacts
[2026-09-02 23:02:51] COMPLETED user_ui_glm.md cleanup (register.tsx KEPT, npm dependencies NOT touched).
Deleted: me/about route (2 files + dir), constants/Colors.ts (+ empty constants/ dir), components/BannerCarousel.tsx, src/components/google-sign-in.tsx, app/api/auth/{authorize,callback,token}+api.ts (+ empty dirs), 11 unused assets.
Edited: prominent-api-hooks.ts (useBanners + BannersResponse + BannersApiType import removed), useJWT.ts (saveRoles/getRoles/deleteRoles/ROLES_KEY removed), toaster.tsx (InfoToast/ErrorToast/SuccessToast removed), 4 stores (clearProducts, clearSelectedAddress, clearSlotsData, setDrawerHidden removed), PaymentAndOrderComponent.tsx + my-orders/index.tsx (commented Razorpay blocks removed), _layout.tsx (console.log + unused Dimensions import removed).
Verification: zero leftover references; tsc --noEmit total unchanged at 106 (all pre-existing, in ../backend files via @backend types; 0 errors in user-ui's own files).
[2026-09-02 23:45:00] EXECUTE admin_ui_dpsk.md cleanup: delete dead code; KEEP §4 (single-source routes + dead affordances); DO NOT touch npm dependencies.
PLANNED FILE DELETIONS (verified zero live references via grep across apps/admin-ui + packages):
- app/(drawer)/dashboard/coupons/reserved-coupons/index.tsx (orphaned route; feature duplicated inline as ReservedTab in coupons/index.tsx)
- components/context/auth-context.tsx (100% commented-out legacy auth; no live exports)
- components/context/roles-context.tsx (imports non-export AuthContext → broken; useRoles/useIsAdmin zero importers)
- components/dashboard-header.tsx (zero importers; replaced by drawer header)
- components/TabNavigation.tsx, components/day-account-view.tsx, components/HorizontalImageScroller.tsx (zero importers)
- components/AddressPlaceForm.tsx, components/AddressZoneForm.tsx, components/app-container.tsx (zero importers)
- components/ui/IconSymbol.ios.tsx, IconSymbol.tsx, TabBarBackground.ios.tsx, TabBarBackground.tsx (expo-template tab files; zero importers)
- components/UserIncidentDialog.tsx (zero importers; live UserIncidentsView.tsx holds private duplicate)
- components/date-time-picker.tsx (zero importers; duplicate of common-ui's live DateTimePickerMod)
- src/api-hooks/banner.api.ts (zero importers)
- hooks/useHideDrawerHeader.ts, hooks/useCurrentUserId.ts, hooks/usePhonepeSdk.ts (zero importers)
- hooks/useThemeColor.ts, hooks/useColorScheme.ts, hooks/useColorScheme.web.ts, constants/Colors.ts (template trio; sole consumer = deleted dashboard-header)
- utils/getCurrentUserId.ts (zero live importers)
- services/axios-admin-ui.ts (zero importers)
- services/notif-service/notif-checker.tsx, notif-context.tsx, notif-register.ts (never mounted in app/_layout; notif-checker imports nonexistent @/api-hooks/user.api)
- scripts/reset-project.js + e2e/jest.config.js + e2e/starter.test.js + .detoxrc.js (expo-template leftovers; no detox dep/script)
PLANNED EDITS (with false-positive corrections verified by reading live callers):
=== apps/admin-ui/app/(drawer)/dashboard/index.tsx ===
- remove unused import: LinearGradient (theme import KEPT — used by menu items; only the LinearGradient JSX block is commented out)
=== apps/admin-ui/app/(drawer)/dashboard/send-notifications/index.tsx ===
- remove commented-out ImageUploader UI block; remove now-unused ImageUploader/usePickImage imports, handleImagePick/handleRemoveImage handlers, selectedImage/displayImage state, and their reset lines in sendNotification.onSuccess
(image-upload feature is commented out/unreachable; useUploadToObjectStorage.uploadSingle KEPT — still used in handleSend)
=== apps/admin-ui/app/(drawer)/dashboard/manage-orders/delivery-sequences/index.tsx ===
- remove commented-out hint-banner JSX block inside the DraggableFlatList
=== apps/admin-ui/hooks/useJWT.ts ===
- prune to live exports only: saveJWT, getJWT, deleteJWT (+ JWT_KEY const). Remove: saveUserId, getUserId, saveRoles, getRoles, deleteRoles, ROLES_KEY, USER_ID_KEY
=== apps/admin-ui/src/components/CouponForm.tsx ===
- remove console.log('Toggling user:', ...) debug line (keep setFieldValue)
=== apps/admin-ui/components/StoreForm.tsx ===
- remove StoreFormRef interface + forwardRef wrapper + unused ref param + displayName (ref never attached by stores/add.tsx or stores/edit.tsx)
- KEPT: ProductForm's ProductFormRef/clearImages (genuinely used by products/edit.tsx via productFormRef.current?.clearImages())
- KEPT: SlotForm's dateTestID/timeTestID props (common-ui DateTimePickerMod accepts them — verified in packages/ui/src/components/date-time-picker.tsx)
- KEPT: OrderOptionsMenu onWhatsApp/onDial props and no-op callers (§4 dead-affordance zone — not deleting)
NOT TOUCHED (per instruction):
- §4: rebalance-orders route, slots/slot-details route, customize-app/product-tags home-grid entries, vendor-snippets "View Slot" no-op button, orders/index packaged-checkbox no-op onPress
- All npm dependencies in package.json (incl. react-native-phonepe-pg, expo-notifications, material-top-tabs, toaster dep, buffer, jwt-decode)
- components/SnippetMenu.tsx SuccessToast call + services/toaster.tsx (product decision — no <Toast/> host mounted)
- user-ui counterparts (LIVE there; e.g. user-ui's useColorScheme/Colors/notif-service are wired and in use)
[2026-09-02 23:58:00] COMPLETED admin_ui_dpsk.md cleanup.
Deleted 34 dead files + emptied dirs:
- Route: app/(drawer)/dashboard/coupons/reserved-coupons/index.tsx (+ emptied dir)
- Components: context/auth-context.tsx, context/roles-context.tsx, dashboard-header.tsx, TabNavigation.tsx, day-account-view.tsx, HorizontalImageScroller.tsx, AddressPlaceForm.tsx, AddressZoneForm.tsx, app-container.tsx, UserIncidentDialog.tsx, date-time-picker.tsx, ui/{IconSymbol,IconSymbol.ios,TabBarBackground,TabBarBackground.ios}.tsx (+ emptied components/ui dir)
- src/api-hooks/banner.api.ts (+ emptied dir)
- Hooks: useHideDrawerHeader.ts, useCurrentUserId.ts, usePhonepeSdk.ts, useThemeColor.ts, useColorScheme.ts, useColorScheme.web.ts
- constants/Colors.ts (+ emptied dir), utils/getCurrentUserId.ts, services/axios-admin-ui.ts, services/notif-service/{notif-checker,notif-context,notif-register} (+ emptied dir)
- scripts/reset-project.js (+ emptied dir), e2e/{jest.config.js,starter.test.js} (+ emptied dir), .detoxrc.js
- components/context/ dir kept (staff-auth-context.tsx is live)
Edited:
- app/(drawer)/dashboard/index.tsx: removed unused LinearGradient import (theme kept — used by menu items; commented gradient JSX block left as comment)
- app/(drawer)/dashboard/send-notifications/index.tsx: removed commented-out image-upload UI + dead image state (selectedImage/displayImage), handleImagePick/handleRemoveImage handlers, ImageUploader/usePickImage/useUploadToObjectStorage imports, and image upload path in handleSend (UI is disabled; uploadSingle had no reachable caller left)
- app/(drawer)/dashboard/manage-orders/delivery-sequences/index.tsx: removed commented-out hint-banner JSX block
- hooks/useJWT.ts: pruned to saveJWT/getJWT/deleteJWT + JWT_KEY (removed saveUserId/getUserId/saveRoles/getRoles/deleteRoles/ROLES_KEY/USER_ID_KEY)
- src/components/CouponForm.tsx: removed debug console.log in toggleUserSelection
- components/StoreForm.tsx: removed unused StoreFormRef interface + forwardRef wrapper + ref param + displayName (now plain function component; StoreFormData export kept)
- Regenerated .expo/types/router.d.ts via brief expo start (removed stale reserved-coupons route types); dev server killed
KEPT (per instruction / verification): §4 routes & affordances (rebalance-orders, slot-details, customize-app/product-tags, vendor-snippets "View Slot" no-op, orders packaged-checkbox no-op); ProductForm ProductFormRef/clearImages (USED by products/edit.tsx); SlotForm dateTestID/timeTestID (common-ui DateTimePickerMod accepts them); OrderOptionsMenu onWhatsApp/onDial props; SnippetMenu SuccessToast + services/toaster.tsx (no Toast host — product decision); all npm dependencies untouched; user-ui counterparts untouched (live there).
Verification:
- grep across apps/admin-ui: zero remaining references to any deleted file/symbol (only generated .expo cache regenerated clean).
- tsc --noEmit: no errors in any file I edited. Remaining 19 admin-ui-local errors + backend errors are pre-existing (in untouched files: complaints, coupons/edit, customize-app, products, user-management, delivery-sequences lines 343/398 implicit-any, ProductGroupForm, ProductsSelector, ProductForm) — verified none are in modified regions.
[2026-09-02 23:20:00] CREATED analysis script: scripts/dead-code/repeat-types.js (read-only type-declaration scanner, outputs scripts/dead-code/repeat-types-raw.json)
[2026-09-03 00:10:00] EXECUTE repeat_types_dpsk.md cleanup — remove legacy doctor/hospital/token types (not relevant to meat-farmer app).
PLANNED CHANGES:
=== packages/ui/shared-types.ts ===
- DELETE all legacy doctor/hospital/token types (zero external references — verified by grep across apps + packages):
Person, User (hospital-era), Hospital, DoctorSpecialization, DoctorInfo, Doctor, DoctorDetails,
DashboardDoctor, TokenDoctor, UpcomingToken, PastToken, UpcomingAppointment, MyTokensResponse,
PastTokensResponse, BookTokenPayload, BookTokenResponse, DoctorAvailability,
DoctorAvailabilityWithDate, DoctorAvailabilityResponse, DoctorAvailabilityPayload, token_user,
PatientHistoryToken, PatientHistory, GetHospitalPatientHistoryResponse, PatientHistoryFilters
- DELETE shadowed meat-farmer duplicates that collide with packages/shared (also zero importers):
Order, Slot, Coupon (canonical copies live in packages/shared/types/{user.ts, admin.ts})
- KEEP: CreateCouponPayload (only live export — imported by apps/admin-ui coupons via 'common-ui/shared-types')
- REMOVE trailing `export * from './token-types'`
=== packages/ui/token-types.ts ===
- DELETE entire file (DoctorTokenSummary, HospitalTodaysTokensResponse, DoctorTodayToken, DoctorTodaysTokensResponse — only re-exported by shared-types.ts:333, no external consumers)
[2026-09-03 00:15:00] COMPLETED legacy doctor/hospital/token type removal.
- packages/ui/shared-types.ts: rewritten to contain ONLY CreateCouponPayload (the single live export). Removed 28 legacy doctor/hospital/token types + 3 shadowed meat-farmer duplicates (Order, Slot, Coupon — canonical copies live in packages/shared) + trailing `export * from './token-types'`.
- packages/ui/token-types.ts: deleted (4 token-summary types, zero external consumers).
Verification:
- grep across apps + packages: zero references to any removed legacy type name or the token-types module.
- CreateCouponPayload intact; its only importers (admin-ui coupons/edit/[id].tsx + src/components/CouponForm.tsx) unchanged.
- Pre-existing tsc error in coupons/edit/[id].tsx:92 (`skuIds` not in CreateCouponPayload) untouched and unrelated (predates this change; CreateCouponPayload body preserved byte-for-byte).
[2026-09-03 20:03:29] DELETE unused types (no consumers repo-wide; scanner: scripts/dead-code/unused-types.js; ambient .d.ts declarations excluded by design).
NOTE: date rollover — this entry and deletions performed 2026-09-03.
=== apps/backend/src/middleware/auth.middleware.ts ===
- removed interface UserContext (lines 7-12):
interface UserContext {
userId: number;
name?: string;
email?: string;
mobile?: string;
}
- removed interface StaffContext (lines 14-17):
interface StaffContext {
id: number;
name: string;
}
(file types context vars via hono's ContextVariableMap augmentation in types/hono.d.ts — those ambient declarations KEPT, they are consumed by hono's type system, not by name)
=== packages/shared/types/coupon.types.ts ===
- removed interface ReservedCoupon (lines 21-25):
export interface ReservedCoupon extends Coupon {
secretCode: string;
redeemedUserId: number | null;
redeemedAt: Date | null;
}
=== packages/shared/types/user.ts ===
- removed interface UserAvailabilityEntry (lines 339-345) — transitively dead (only consumer was UserAvailabilityResponse):
export interface UserAvailabilityEntry {
id: number;
price: string;
marketPrice: string | null;
flashPrice: string | null;
isFlashAvailable: boolean;
isOutOfStock: boolean;
isSuspended: boolean;
}
- removed interface UserAvailabilityResponse (lines 347-350):
export interface UserAvailabilityResponse {
availability: UserAvailabilityEntry[];
count: number;
}
=== packages/ui/src/components/use-pick-image.tsx ===
- removed interface BaseProps (lines 5-8):
interface BaseProps {
label?: string;
multiple: boolean;
}
- removed interface ImageInfo (lines 22-36) — 15-field expo ImagePicker asset mirror, never referenced (picker assets are spread as any)
(live hook + its Props type kept; file has 7 consumers)
[2026-09-03 20:06:30] COMPLETED unused-type deletion: 7 types removed across 4 files.
[2026-09-03 20:20:00] DEDUP IDENTICAL TYPES — centralize exact duplicates into @packages/shared (71 safe uniform names, per types_inventory.md §3).
NOTE: date rollover — this entry performed 2026-09-03.
=== NEW FILES in packages/shared/types/ ===
- upload.types.ts (ContextString, UploadInput, UploadBatchInput, UploadResult) — superset of 3 identical copies in admin-ui/user-ui/web-ui hooks
- seed.types.ts (UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData) — 2 identical copies in db_helper_{postgres,sqlite}/lib/seed.ts
- inputs.types.ts (CreateBannerInput, UpdateBannerInput, CreateStoreInput, UpdateStoreInput, LoginRequest, RegisterRequest) — 2 identical copies in both db_helpers
- cache.types.ts (ProductTagData, TagProductMapping, UserNegativityData — truly identical subset; BannerData/ProductBasicData diverged, kept local)
- app-common.types.ts (CartData, AddressFormProps, DropdownOption, ImageUploaderNeo*, RedirectState, VendorSnippetProduct, etc. — cross-app identical copies)
- Updated packages/shared/types/index.ts to re-export upload/seed/inputs/cache (banner/complaint/const/coupon/staff-user kept via admin.ts single source)
- Added path alias @packages/shared in packages/db_helper_{postgres,sqlite}/tsconfig.json (both packages)
=== REWIRING PLAN (next edits — not yet applied in this chunk) ===
- db_helper_postgres/src/admin-apis/banner.ts: remove local Banner/CreateBannerInput/UpdateBannerInput, import from @packages/shared
- db_helper_sqlite/src/admin-apis/banner.ts: same
- db_helper_{postgres,sqlite}/src/admin-apis/complaint.ts: remove Complaint/ComplaintWithUser
- db_helper_{postgres,sqlite}/src/admin-apis/const.ts: remove Constant
- db_helper_{postgres,sqlite}/src/admin-apis/coupon.ts: remove CouponValidationResult/UserMiniInfo (Coupon stays local until divergence resolved)
- db_helper_{postgres,sqlite}/src/admin-apis/store.ts: remove Store/CreateStoreInput/UpdateStoreInput
- db_helper_{postgres,sqlite}/src/admin-apis/staff-user.ts: remove StaffUser/StaffRole
- db_helper_{postgres,sqlite}/src/lib/seed.ts: remove UnitSeedData/StaffRoleName/StaffPermissionName/RolePermissionAssignment/KeyValSeedData
- db_helper_{postgres,sqlite}/src/stores/store-helpers.ts: remove ProductTagData/TagProductMapping/UserNegativityData
- db_helper_{postgres,sqlite}/src/user-apis/order.ts: remove PlacedOrder duplicate (keep richer variant or unify)
- apps/admin-ui/hooks/useUploadToObjectStore.ts, apps/user-ui/hooks/useUploadToObjectStore.ts, apps/web-ui/src/hooks/useUploadToObjectStorage.ts: remove UploadInput/Batch/Result/ContextString
- apps/user-ui/hooks/cart-query-hooks.tsx ↔ apps/web-ui/src/hooks/cart-query-hooks.ts: CartData
- apps/user-ui/components/AddressForm.tsx ↔ apps/web-ui/src/components/AddressForm.tsx: AddressFormProps
- packages/ui/src/components/ImageUploaderNeo.tsx ↔ packages/web-components/src/components/image-uploader-neo.tsx: ImageUploaderNeo*
- packages/ui/src/components/dropdown.tsx ↔ packages/web-components/src/components/dropdown.tsx: DropdownOption
- STALE per-domain files to delete after rewiring: banner.types.ts, complaint.types.ts, const.types.ts, coupon.types.ts, staff-user.types.ts (all re-exported via admin.ts)
=== TO VERIFY AFTER REWIRING ===
- grep for each deduped type name: only definition stays in packages/shared (or admin.ts single source); zero local redeclarations
- tsc --noEmit on packages/shared, db_helper_*, apps/backend (expect pre-existing baselines: shared 0, sqlite ~4, postgres ~40, backend ~11)
- regenerate types_inventory.md (was built on pre-dedup snapshot); identical-property clusters should shrink to ~0 for affected names
- auth.middleware.ts: UserContext, StaffContext (kept hono.d.ts ContextVariableMap — ambient module augmentation, consumed by hono's type system)
- shared/coupon.types.ts: ReservedCoupon
- shared/user.ts: UserAvailabilityResponse + UserAvailabilityEntry (transitively dead — only consumer was the removed response type)
- ui/use-pick-image.tsx: BaseProps, ImageInfo
Verification: zero leftover references repo-wide; tsc baselines unchanged — backend 11, packages/ui 924, user-ui 106, admin-ui 122 (all pre-existing).
Scanner limitation noted: same-name declarations across files (e.g. Props, Order) can mask per-copy deadness via word-boundary matching; those require import-graph resolution and were not touched.
[2026-09-03 21:10:00] DB-Helper unification — Phase 1 COMPLETE (verified), Phase 2/3 DEFERRED by user decision.
Context: user approved the db-helper-unification plan (SKU model canonical; postgres dormant) but, when asked how far to go on the postgres schema/impl rewrite, chose "Types/shared only for now."
=== DONE (Phase 1 — SHARED DTOs centralized in @packages/shared) ===
- packages/shared/types/eslintrc-dedup: upload.types.ts, seed.types.ts, inputs.types.ts, cache.types.ts, app-common.types.ts — created & re-exported in types/index.ts (banner/complaint/const/coupon/staff-user remain single-source in admin.ts).
- Both db_helper_{postgres,sqlite} tsconfig.json: added "@packages/shared" + "@packages/shared/*" path aliases → ../shared.
- Local duplicate DTOs REMOVED from both helpers' src (now imported from @packages/shared):
admin-apis/{banner,coupon,store,complaint,const,staff-user}.ts, lib/seed.ts, stores/store-helpers.ts (ProductTagData/TagProductMapping/UserNegativityData), user-apis/*.ts.
- Verified: `grep -rnE "^(export )?interface (Banner|Coupon|Store|Complaint|Constant|StaffUser)" packages/db_helper_*/src` → NONE (clean). Both helpers typecheck-resolve @packages/shared via tsconfig paths.
- Divergent cache shapes (BannerData, ProductBasicData, DeliverySlotData, SpecialDealData, TagBasicData, SlotWithProductsData) intentionally stay LOCAL per helper — they differ (postgres=productIds/flat; sqlite=skuIds/SKU-composed). Documented in cache.types.ts header comment.
=== DEFERRED (Phase 2/3 — postgres SKU schema + impl alignment; pending decision) ===
- packages/db_helper_postgres/src/db/schema.ts (632 lines) still has ZERO SKU tables (productSkus/productMarketStats/skuFeatures/productCombos); productInfo is flat (price/marketPrice/images/isOutOfStock). sqlite has 43 tables, SKU-tier (37 refs).
- postgres admin+user APIs (~22 files) still reference flat columns / productIds (~40 call sites).
- Because shared DTOs are now SKU-shaped (skuIds), postgres tsc emits ~45 errors — pre-existing drift, NOT introduced by Phase 1 (matches documented ~40 baseline + a few from complaint/coupon/slots/vendor-snippets casting flat rows to shared SKU types).
- Next step (when user opts in): rewrite postgres schema.ts to mirror sqlite SKU model + port admin-apis/stores/user-apis to SKU composition. Per AGENTS.md, no drizzle migration is run by the agent (DB drift until user migrates).
=== REBUILT types_inventory.md (latest snapshot) ===
792 types (521 unique), 143 duplicate names, 131 identical-property clusters, 711 `any` usages across 210 files. Organized by file + by name, duplicate clusters w/ consumers, identical-property clusters, per-file `any` table.
[2026-09-04 23:36:11] PHASE 1: dedupe packages/shared/types/admin.ts — 10 types re-declared identically in dedicated files are replaced by re-exports (verified byte-identical modulo comments/whitespace).
=== packages/shared/types/admin.ts ===
- removed local declarations: Banner, Complaint, ComplaintWithUser, Constant, ConstantUpdateResult, Coupon, CouponValidationResult, UserMiniInfo, StaffUser, StaffRole
- added re-export block:
export type { Banner } from './banner.types'
export type { Complaint, ComplaintWithUser } from './complaint.types'
export type { Constant, ConstantUpdateResult } from './const.types'
export type { Coupon, CouponValidationResult, UserMiniInfo } from './coupon.types'
export type { StaffUser, StaffRole } from './staff-user.types'
- KEPT in admin.ts: Store (canonical home — store.types.ts note says so), AdminOrderRow and everything below
[2026-09-04 23:40:00] PHASE 1b: packages/shared/types/user.ts — UserBanner is byte-identical to Banner; replaced local declaration with alias.
- removed local interface UserBanner (10 fields) at user.ts ~line 119
- added: import type { Banner } from './banner.types'
export type UserBanner = Banner
(5 live consumers — sqlite/postgres banners.ts, backend dbService re-export — keep working unchanged)
[2026-09-04 23:47:00] PHASE 2: centralize cross-app / in-app exact-duplicate types.
=== apps/user-ui/src/types/auth.ts ===
- replaced local interfaces with shared aliases (shared/inputs.types.ts LoginRequest/RegisterRequest verified byte-identical):
removed: export interface LoginCredentials { identifier: string; password: string; }
export interface RegisterData { name; email; mobile; password; profileImageUrl?: string | null; }
added: import type { LoginRequest, RegisterRequest } from '@packages/shared'
export type LoginCredentials = LoginRequest
export type RegisterData = RegisterRequest
(6 consumer files keep importing the same names from '@/src/types/auth' — zero consumer edits)
=== apps/user-ui/hooks/useAuthenticatedRoute.ts + src/contexts/AuthContext.tsx ===
- RedirectState identical in both. Canonical: AuthContext (line 11 interface → exported).
- hook: deleted local interface, added import type { RedirectState } from '@/src/contexts/AuthContext'
(hook already imports useAuth from AuthContext — no new cycle)
=== login forms (user-ui + web-ui) ===
- both LoginFormInputs identical. Canonical: shared/app-common.types.ts LoginFormInputs (already there).
- apps/user-ui/app/(auth)/login.tsx: deleted local interface, added import type { LoginFormInputs } from '@packages/shared'
- apps/web-ui/src/routes/login.tsx: deleted local interface, added import type { LoginFormInputs } from '@packages/shared'
=== user-ui Order/OrderItem view models ===
- new file apps/user-ui/src/types/orders.ts exporting the identical Order + OrderItem interfaces (from me/my-orders/index.tsx)
- me/my-orders/index.tsx + components/NextOrderGlimpse.tsx: deleted locals (incl. the '// Type definitions' comment), import from '@/src/types/orders'
=== apps/user-ui/src/store/centralSlotStore.ts ===
- deleted local type AvailabilityEntry = AvailabilityApiType['availability'][number] (line 8)
- added import type { AvailabilityEntry } from '@/src/hooks/prominent-api-hooks'
(type-only import — no runtime cycle with the store's existing value import from the same file)
[2026-09-04 23:55:00] PHASE 3: centralize remaining exact-duplicate db types.
(a) FIX broken seed re-export chain (prior session migrated lib/seed.ts to shared imports but left index.ts re-exporting from lib/seed):
- packages/db_helper_sqlite/src/lib/seed.ts + packages/db_helper_postgres/src/lib/seed.ts: added
export type { UnitSeedData, StaffRoleName, StaffPermissionName, RolePermissionAssignment, KeyValSeedData } from '@packages/shared'
(index.ts `export { ..., type UnitSeedData, ... } from './src/lib/seed'` blocks resolve again)
(b) NEW packages/shared/types/order.types.ts: PlacedOrder, CouponUsageWithCoupon, GetAllOrdersInput
(copied verbatim from sqlite; pg verified byte-identical. NOT included: OrderWithCancellationData — extends drifted OrderWithFullData, stays local in both packages.)
- shared/types/index.ts: added export type * from './order.types';
(c) MIGRATIONS to shared:
- sqlite+pg user-apis/order.ts: deleted local PlacedOrder + CouponUsageWithCoupon; extended existing '@packages/shared' import
- sqlite+pg admin-apis/order.ts: deleted local GetAllOrdersInput; extended existing '@packages/shared' import
- sqlite+pg stores/store-helpers.ts: deleted local StoreBasicData interface; added
import type { StoreSummary } from '@packages/shared'
export type StoreBasicData = StoreSummary
(index.ts `type StoreBasicData` export lines + backend consumers keep working via the alias; shared StoreSummary verified identical shape)
NOT touched (verified different, per exact-dupe rule): CreateCouponInput/UpdateCouponInput, CreateProductTagInput/UpdateProductTagInput, SlotSnippetInput, CreateSkuInput/CreateProductInput (sku- vs product-model drift); InferSelectModel *Row aliases (schema-bound per package); user-apis richer CouponValidationResult (different concept, same name — rename candidate only); backend banner-store Banner (subset shape); const-keys files (different key sets).
[2026-09-04 23:58:00] FIX Phase 1 regression: my shape comparison was unreliable (shell-quoting artifact) and wrongly unified 2 drifted pairs. sqlite tsc went 4 -> 12 errors (coupon.ts x5, staff-user.ts x3).
- Coupon: admin.ts original had `skuIds: number[] | null`; canonical coupon.types.ts has `productIds: number[] | null` — RESTORED admin.ts local Coupon.
- StaffUser: admin.ts original had `staffRoleId: number | null`; canonical staff-user.types.ts has `staffRoleId: number` — RESTORED admin.ts local StaffUser.
- Verified truly identical (re-export kept): Banner, Complaint, ComplaintWithUser, Constant, ConstantUpdateResult, UserMiniInfo, StaffRole.
[2026-09-04 23:59:00] PHASE 4: additive shared primitives + aliasing (zero consumer edits).
- NEW packages/shared/types/primitives.types.ts: MessageResponse {success, message}, IdName {id, name} (leaf module, no imports — no cycles)
- shared/types/index.ts: added export type * from './primitives.types';
- shared/types/user.ts: 8 interfaces (UserAddressDeleteResponse, UserCancelOrderResponse, UserDeleteAccountResponse, UserPasswordUpdateResponse, UserPaymentFailResponse, UserPaymentVerifyResponse, UserRaiseComplaintResponse, UserUpdateNotesResponse) become export type X = MessageResponse
- shared/types/admin.ts: AdminOrderMessageResult becomes export type AdminOrderMessageResult = MessageResponse ; AdminVendorSnippetProduct becomes export type AdminVendorSnippetProduct = IdName
- apps/admin-ui/types/vendor-snippets.ts: VendorSnippetProduct becomes alias of shared IdName
- apps/admin-ui/components/ProductListDialog.tsx: local VendorSnippetProduct becomes alias of shared IdName
- apps/admin-ui/src/components/TagForm.tsx: StoreOption becomes alias of shared IdName
- apps/admin-ui/components/context/staff-auth-context.tsx: Staff becomes alias of shared IdName
- apps/backend/src/middleware/auth.middleware.ts: StaffContext becomes alias of shared IdName
DECISION (documented, not done): ui<->web-components prop mirrors left independent (no cross-dep between the RN and web UI packages; wiring one for 3 prop types is disproportionate). migrator TableInfo/ColumnInfo left (ColumnInfo field names differ per direction — unifying TableInfo alone would be wrong). CartData left (each app's CartItem element type differs). Drifted pairs (coupon/tag/slot/product inputs, store-helpers cache types, user-apis order relations) left per exact-dupe rule.
[2026-09-05 00:02:00] FIX two regressions found by tsc verification:
(a) admin.ts used MessageResponse/IdName in aliases without importing them (export-from does not bring names into local scope) — added import type { MessageResponse, IdName } from './primitives.types' (re-export line kept).
(b) Pre-existing broken chain from prior centralization session (NOT mine, but fixed as part of unification): both store-helpers.ts files imported ProductTagData/TagProductMapping/UserNegativityData from shared without re-exporting, while both index.ts files re-export those names from store-helpers (TS2459 x3 in backend build). Added export type { ProductTagData, TagProductMapping, UserNegativityData } from '@packages/shared' to both store-helpers files.
[2026-09-05 00:05:00] FIX two more gaps from prior centralization session (surfaced by my Phase 2 wiring):
(a) shared/types/app-common.types.ts was never added to shared/types/index.ts star exports — added export type * from './app-common.types'; (no name collisions verified). This makes LoginFormInputs/DropdownOption/CartIconProps/AddressFormProps/ComplaintFormProps/CartData importable from '@packages/shared'.
(b) user-ui prominent-api-hooks.ts declared AvailabilityEntry without export — added export keyword so centralSlotStore's import resolves.
[2026-09-05 00:08:50] COMPLETED exact-duplicate type unification (Phases 1-4).
Centralized in @packages/shared and re-used:
- admin.ts: Banner, Complaint, ComplaintWithUser, Constant, ConstantUpdateResult, CouponValidationResult, UserMiniInfo, StaffRole → re-exports (Coupon + StaffUser KEPT local — verified drifted: skuIds vs productIds, staffRoleId nullable vs not)
- user.ts: UserBanner → alias of Banner
- NEW order.types.ts: PlacedOrder, CouponUsageWithCoupon, GetAllOrdersInput; both db packages import them (OrderWithCancellationData left local — extends drifted OrderWithFullData)
- StoreBasicData (both store-helpers) → alias of shared StoreSummary (verified identical shape)
- seed re-export chain repaired in both lib/seed.ts (was dangling after prior session's migration)
- user-ui: LoginCredentials/RegisterData → shared aliases; RedirectState canonical in AuthContext; LoginFormInputs (user-ui + web-ui) → shared app-common; Order/OrderItem → new src/types/orders.ts; AvailabilityEntry canonical in prominent-api-hooks
- primitives.types.ts (MessageResponse, IdName): 8 user.ts + AdminOrderMessageResult + AdminVendorSnippetProduct + 4 cross-file {id,name} types aliased
- Fixed as found: app-common.types.ts added to shared index star-exports; AvailabilityEntry export added in user-ui hooks; 3 dangling store-helpers re-exports repaired
Verification: backend 11, sqlite 4, user-ui 106, admin-ui 122 (all = baselines); pg 45 = 40 + 5 pg-coupon casts that reproduce with byte-restored admin.ts (pre-existing drift from prior session, documented in report §8). Zero errors mention any unified name.
Deliberately NOT unified (documented in repeat_types_glm.md §8 + change-log): drifted input types (coupon/tag/slot/product), InferSelectModel *Row aliases (schema-bound), ui<->web-components (no cross-dep), migrator ColumnInfo (field names differ per direction), CartData (element types differ), backend banner-store Banner (subset shape), const-keys files (different key sets).
[2026-09-05 00:24:25] PHASE 5: unify remaining verified exact-duplicate types.
- shared primitives.types.ts: added BasicSuccessResponse {success: boolean}
- shared admin.ts: AdminDeleteProductResult, AdminSlotDeleteResult → aliases of MessageResponse; AdminSlotUpdateResult → alias of AdminSlotCreateResult
- shared user.ts: UserSavePushTokenResponse → alias of BasicSuccessResponse; UserDeliverySlot → alias of AdminDeliverySlot (+import from ./admin; no cycle — admin.ts does not import user.ts)
- shared app-common.types.ts: DELETED zero-consumer stale CartData (wrong element type), DropdownOption, CartIconProps; REPLACED stale AddressFormProps with the real current shape; ADDED StoreHeaderState, CartStore, AddedToCartProduct, CompactProductCardProps, ComplaintItemProps
- admin-ui TagForm.tsx: exported TagFormData; product-tags/add.tsx: deleted local, imports it (edit/index.tsx extended version untouched)
- admin-ui ProductGroupForm.tsx: exported ProductGroup; product-groupings/index.tsx: deleted local, imports it
- user-ui + web-ui AddressForm: deleted locals, import shared AddressFormProps
- user-ui + web-ui ComplaintForm: deleted locals, import shared ComplaintFormProps
- web-ui flash.tsx + slot-view.tsx: deleted local CompactProductCardProps, import shared
- user-ui complaints/index.tsx + web-ui me.complaints.tsx: deleted local ComplaintItemProps, import shared
- user-ui cartStore + web-ui cart-store: deleted local AddedToCartProduct + CartStore, import shared; flashCartStore: AddedFlashProduct → alias of shared AddedToCartProduct
- user-ui storeHeaderStore + web-ui store-header-store: deleted local state interfaces, import shared StoreHeaderState; web-ui keeps hook name via type StoreHeaderStore = StoreHeaderState
- user-ui home/index.tsx: deleted ExploreProductItemProps, uses ProductItemProps
- user-ui cart.tsx + checkout.tsx: deleted empty Props{} + dropped unused props params
- backend order.ts: deleted second DeliveryStatus/OrderStatus pair (lines ~477-478), first pair serves both scopes
NOT unified (verified different or out of scope, documented): ChipProps/TagChipProps (different Tag element types), Tag pair (productIds? vs imageUrl), ProductsResponse family (backend-circular), CartData element types, StoreHeaderSt— n/a, migrator ColumnInfo, ui<->web props, fallback-ui 3d props, ProductsResponseAlias/ProductsResponse placeholders.
[2026-09-05 00:30:00] CORRECTION: reverted deletion of backend order.ts second DeliveryStatus/OrderStatus pair — both pairs are function-scoped (different closures), not file duplicates. No change needed there; cluster 49/50 verdict in types_inventory was wrong on scope.
[2026-09-05 00:35:00] PHASE 5 (continued): unify remaining verified exact-duplicates; CORRECTIONS for two over-eager aliases.
- shared primitives.types.ts: added BasicSuccessResponse {success: boolean}
- shared admin.ts: AdminSlotUpdateResult → alias of AdminSlotCreateResult; AdminOrderBasicResult → alias of BasicSuccessResponse
- shared user.ts: UserDeliverySlot → alias of AdminDeliverySlot (+import; no cycle)
- shared app-common.types.ts rewritten: DELETED zero-consumer stale CartData (wrong element type), DropdownOption, CartIconProps, RedirectState, VendorSnippetProduct, ProductsResponseAlias/ProductsResponse placeholders; REPLACED stale AddressFormProps with real shape; ADDED StoreHeaderState, CartStore, AddedToCartProduct, CompactProductCardProps, ComplaintItemProps
- user-ui + web-ui cart stores: local AddedToCartProduct + CartStore deleted, import shared; flashCartStore AddedFlashProduct → alias
- user-ui storeHeaderStore + web-ui store-header-store: locals deleted, import shared StoreHeaderState (web-ui keeps name via alias)
- user-ui + web-ui AddressForm/ComplaintForm: locals deleted, import shared
- web-ui flash.tsx + slot-view.tsx: local CompactProductCardProps deleted, import shared
- user-ui complaints + web-ui me.complaints: local ComplaintItemProps deleted, import shared
- admin-ui TagForm.tsx: TagFormData exported; product-tags/add.tsx imports it (edit/index extended version untouched)
- admin-ui ProductGroupForm.tsx: ProductGroup exported; product-groupings/index.tsx imports it
- user-ui home/index.tsx: ExploreProductItemProps deleted (uses ProductItemProps)
- user-ui cart.tsx + checkout.tsx: empty Props{} + unused props params deleted
- CORRECTION 1: reverted AdminDeleteProductResult/AdminSlotDeleteResult → MessageResponse aliases — backend procedures return {message} without success; aliasing changed the contract (tsc proved it). Restored local {message} interfaces. ({success,message}×9 aliases unaffected — identical contracts.)
- CORRECTION 2: removed accidentally-duplicated UserSavePushTokenResponse alias line in user.ts (duplicate identifier).
- CORRECTION 3: reverted backend order.ts DeliveryStatus/OrderStatus deletion — both pairs are function-scoped, not file duplicates.
- NOT unified (verified): ChipProps/TagChipProps (different Tag element types), Tag pair, ProductsResponse family (backend-circular), StoreHeader n/a, AdminCancelOrderError (union, not same shape), OffersSectionProps (single def), Props usage n/a.
[2026-09-05 00:40:00] CREATED analysis script scripts/subset-types.js (read-only subset/superset scanner; outputs scripts/subset-types-raw.json). No source changes in this entry.
[2026-09-05 00:47:00] PHASE 6: unify remaining verified exact-duplicates.
- shared seed.types.ts: KeyValSeedData → alias of Constant (+import from ./const.types; no cycle — const.types has no imports)
- shared user.ts: UserProductStoreInfo → alias of StoreSummary (+import from ./store.types; no cycle)
- shared cache.types.ts: DELETED PlacedOrderShape (zero consumers repo-wide; db PlacedOrder in order.types.ts is canonical)
- backend auth.ts: deleted local LoginRequest/RegisterRequest, import from '@packages/shared' (same treatment user-ui got)
- sqlite+pg user-apis/order.ts: OrderDetailWithRelations → alias of OrderWithRelations (verified byte-identical bodies 879/879 and 820/820)
- shared app-common.types.ts: added FlashDeliveryProps {isFlashDelivery?: boolean}
- user-ui cart-page/checkout-page + web-ui FloatingCartBar: {isFlashDelivery?} prop interfaces → aliases of shared FlashDeliveryProps
- user-ui home/index.tsx: deleted RenderStoreProps, uses SlotItemProps (same file, identical)
- web-components image-gallery.tsx: ImageGalleryProps → alias of ImageCarouselProps (same package)
NOT unified (verified): Tag pair user-ui/web-ui (name-collision risk with backend's richer Tag — same judgment as ChipProps pair); DropdownOption pair (ui has extra disabled?); ImageUploader/quantifier/dialog/checkbox/profile/search props (independent RN/web packages); CharactersProps (fallback-ui, out of scope); ProductsResponse family (backend-circular); migrator TableInfo (ColumnInfo differs); AndSnippets vs Base (verified DIFFERENT — WithAccess vs plain vendor snippets); SkuSummary admin-ui (sqlite's not exported via index); RegisterRequest backend done above.
[2026-09-05 10:01:39] COMPLETED Phase 6 second wave + full verification.
- shared: KeyValSeedData→Constant, UserProductStoreInfo→StoreSummary, AdminOrderBasicResult+UserSavePushTokenResponse→BasicSuccessResponse (new primitive), AdminSlotUpdateResult→AdminSlotCreateResult, UserDeliverySlot→AdminDeliverySlot, AdminDeleteProductResult + AdminSlotDeleteResult restored as local {message} (contract — backend returns message-only), app-common rewritten (real AddressFormProps; added StoreHeaderState/CartStore/AddedToCartProduct/CompactProductCardProps/ComplaintItemProps; deleted stale CartData/DropdownOption/CartIconProps/RedirectState/VendorSnippetProduct/placeholders)
- backend auth.ts: LoginRequest/RegisterRequest → shared imports
- db packages: OrderDetailWithRelations → alias (both, byte-identical verified); PlacedOrderShape deleted (zero consumers)
- admin-ui: TagFormData/ProductGroup canonical in form components; VendorSnippetProduct/StoreOption/Staff → IdName aliases
- user-ui + web-ui: cart/header stores, AddressForm, ComplaintForm, CompactProductCardProps, ComplaintItemProps → shared; ExploreProductItemProps/Props{}/RenderStoreProps removed
- web-ui store-header keeps hook name via alias
Verification: backend 11, sqlite 4, pg 45 (40+5 pre-existing coupon casts), user-ui 106, admin-ui 122 — all baselines hold; web-ui 150 (no baseline; errors are pre-existing JSX friction, none touch unified names).
Left deliberately (verified): Tag pair (backend name-collision risk), DropdownOption pair (ui has extra disabled?), ui<->web prop mirrors (independent packages), ChipProps pair, ProductsResponse family, migrator ColumnInfo, AndSnippets vs Base (verified DIFFERENT), fallback-ui (out of scope), drifted inputs, InferSelectModel rows, SkuSummary (not exported via index), DeliveryStatus/OrderStatus (function-scoped), OffersSectionProps (single), Props usage n/a.
[2026-09-05 10:10:00] Regen unification: OffersSectionProps (web-ui offers.tsx + user-ui order-again/index.tsx, verified identical) → shared app-common.types.ts; both routes import it.
[2026-09-05 10:20:00] Regen unification round 2: OffersSectionProps → shared app-common (web-ui offers + user-ui order-again import it); ImageGalleryProps → alias of ImageCarouselProps (same package web-components).
[2026-09-05 10:22:00] web-components image-carousel.tsx: exported ImageCarouselProps (needed by image-gallery.tsx alias).
[2026-09-05 10:30:00] Regen catch: packages/shared/types/cache.types.ts declared ProductTagData TWICE (bad merge) — deleted the duplicate second copy. No consumers affected (same shape).
[2026-09-05 10:35:00] REGENERATED types_inventory.md via new scripts/gen-inventory.js + scripts/render-inventory.js (read-only analysis scripts).
- Fresh harvest: 562 files, 620 declarations, 490 unique names, 42 single-identifier aliases (classified separately), 98 duplicate names, 56 clusters, 698 any-hits.
- Harvest fixes vs 09-03 edition: import/export-block lines never harvested (~17 bogus clusters gone); aliases excluded from shape clustering (new Unified Aliases section); React. qualifier stripped; optionality ignored (documented).
- Fixed as found during regen: duplicate ProductTagData in shared cache.types.ts (deleted 2nd copy); ImageGalleryProps → alias of ImageCarouselProps (+export added); OffersSectionProps → shared app-common (web-ui + user-ui import it).
- Verification: backend 11, sqlite 4, user-ui 106, admin-ui 122, pg 45 (40+5 pre-existing) — all baselines hold; web-components 3 pre-existing JSX errors, untouched by edits.
[2026-09-05 10:45:00] CREATED analysis scripts (read-only): scripts/gen-inventory.js (types harvest + clustering), scripts/render-inventory.js (md renderer with per-cluster verdicts), scripts/gen-cluster-files.js (per-cluster md files with full defs + consumers), scripts/update-inventory.js (one-off patch helper for the 09-03 edition; superseded by regeneration).
GENERATED: type-clusters/ folder (56 cluster files + README index).
REGENERATED: types_inventory.md from current tree (replaces 09-03 snapshot + manual annotations).
[2026-09-05 10:57:13] DELETE all agent-created analysis scripts (user request — remove, not needed; nothing references them in code or configs).
Deleted files in scripts/:
- gen-cluster-files.js, gen-inventory.js, render-inventory.js, update-inventory.js
- subset-types.js, subset-types-raw.json, types-inventory-data.json
Deleted directory scripts/dead-code/ entirely (contained only agent-created files):
- user-ui-files.js, user-ui-symbols.js, user-ui-suspects.js, user-ui-store-actions.js, user-ui-store-final.js, user-ui-store-precise.js
KEPT (pre-existing, not mine): scripts/clear-db-old.sql, scripts/clear-db.sql, scripts/s3-cleaner.js, scripts/s3-sync.js
Follow-up md-only cleanup: subset_types_glm.md lines referencing the deleted scripts reworded (no code impact).
[2026-09-05 11:03:50] ADD shared ReactParentComponent for Cluster-1 unification.
=== packages/shared/types/primitives.types.ts ===
- added type-only react import + new interface (appended):
import type { ReactNode } from 'react'
export interface ReactParentComponent {
children: ReactNode
}
- header comment updated: leaf module rule now reads "no runtime imports (type-only imports erased at compile are fine)".
[2026-09-05 11:06:00] Cluster-1 unification: replace 9 local children-only interfaces with shared ReactParentComponent (user-ui + ui).
- LocationTestWrapper.tsx: delete lines 44-46 (interface LocationTestWrapperProps); add shared type import; `: LocationTestWrapperProps` → `: ReactParentComponent` at use site line 51
- HealthTestWrapper.tsx: delete lines 9-11 (interface HealthTestWrapperProps); add import; `React.FC<HealthTestWrapperProps>` → `React.FC<ReactParentComponent>`
- WebViewWrapper.tsx: delete lines 9-11; add import; `{ children }: WebViewWrapperProps` → `{ children }: ReactParentComponent`
- FirstUserWrapper.tsx: delete lines 5-7 (interface Props); add import; `React.FC<Props>` → `React.FC<ReactParentComponent>`
- UpdateChecker.tsx: delete lines 5-7; add import; `React.FC<UpdateCheckerProps>` → `React.FC<ReactParentComponent>`
- notif-context.tsx: delete lines 36-38; add import; `React.FC<NotificationProviderProps>` → `React.FC<ReactParentComponent>`
- AuthContext.tsx: delete lines 19-21; add import; `React.FC<AuthProviderProps>` → `React.FC<ReactParentComponent>`
- CentralStoreInitializer.tsx: delete lines 5-7; add import; `{ children }: CentralStoreInitializerProps` → `{ children }: ReactParentComponent`
- ui app-container.tsx: delete lines 7-9 (interface Props); add import; `(props: Props)` → `(props: ReactParentComponent)`
Import line used everywhere: import type { ReactParentComponent } from '@packages/shared'
Excluded: MyTouchableOpacityProps (optional children + extends — different shape); other files' same-named Props (different interfaces, verified).
[2026-09-05 11:10:00] CORRECTION (LocationTestWrapper): my first edit inserted a duplicate component declaration (interface did not directly precede the component). Repaired — file now has single component with ReactParentComponent annotation, interface + comment preserved correctly.
[2026-09-05 11:07:29] Cluster-1 fallback-ui: add @packages/shared path mapping to tsconfig (mirrors user-ui mapping), replace SuperAdminGuardProps + AuthWrapperProps with shared ReactParentComponent.
- apps/fallback-ui/tsconfig.json paths: added "@packages/shared": ["../../packages/shared"], "@packages/shared/*": ["../../packages/shared/*"]
- SuperAdminGuard.tsx: deleted lines 3-5 interface; added shared type import; `{ children }: SuperAdminGuardProps` → `{ children }: ReactParentComponent`
- AuthWrapper.tsx: deleted lines 7-9 interface; added shared type import; `{ children }: AuthWrapperProps` → `{ children }: ReactParentComponent`
[2026-09-05 11:08:15] Cluster-1 cleanup: remove now-unused ReactNode named imports (orphaned by interface deletions).
- notif-context.tsx: removed `ReactNode,` from react import block
- AuthContext.tsx: removed `ReactNode` from react import list
[2026-09-05 11:09:00] Cluster-1 cleanup: remove orphaned ReactNode named import in LocationTestWrapper.tsx (only remaining ReactNode use was the deleted interface).
[2026-09-05 11:12:00] FIX: shared/types/index.ts never starred primitives.types (existing MessageResponse/IdName only surfaced via admin.ts re-export). Added export type * from './primitives.types'; so ReactParentComponent resolves from '@packages/shared'.
[2026-09-05 11:15:00] COMPLETED Cluster-1 ReactParentComponent unification (11 files + shared def + fallback-ui tsconfig).
Verification: user-ui 106, admin-ui 122, backend 11 (all = baselines); fallback-ui 107 total with zero errors in touched files. Zero leftover local interfaces. Docs: cluster-01 file + types_inventory status section marked resolved.
[2026-09-05 11:20:24] ADD shared TabIconProps for Cluster-2 unification.
=== packages/shared/types/primitives.types.ts ===
- appended:
export interface TabIconProps {
focused: boolean
size: number
color: string
}
[2026-09-05 11:21:00] Cluster-2 unification: replace 6 local tab-icon prop interfaces with shared TabIconProps.
- apps/user-ui/components/icons/{HomeIcon,OffersIcon,CartIcon,StoresIcon,MeIcon}.tsx + packages/ui/src/components/icons/CartIcon.tsx: in each file delete lines 4-8 (local interface + trailing blank line) and add import type { TabIconProps } from '@packages/shared' after the react-native-svg import; update the single annotation React.FC<XxxIconProps> → React.FC<TabIconProps>
- Excluded: nothing — all 6 members verified byte-identical shape, single local use each, no cross-file consumers.
[2026-09-05 11:25:00] COMPLETED Cluster-2 TabIconProps unification (6 files + shared def).
Added shared TabIconProps {focused, size, color} in primitives.types.ts (auto-exported via existing primitives star). Replaced all 6 local interfaces (5 user-ui icons + ui CartIcon), each verified single local use, zero cross-file consumers.
Verification: user-ui 106, admin-ui 122, backend 11 (all = baselines); zero leftover local interfaces. Docs: cluster-02 file + types_inventory status section marked resolved.
[2026-09-05 11:25:09] Cluster-04 (ProductRow): NOT unified — sqlite vs pg productInfo schemas differ (sqlite: storeId/incrementStep/productType; pg: unitId/price/images/flags), so one shared alias cannot represent both. Correct action instead: delete the 2 DEAD copies.
- packages/db_helper_postgres/src/admin-apis/vendor-snippets.ts: delete line 15 type ProductRow = InferSelectModel<typeof productInfo> (zero references anywhere)
- packages/db_helper_sqlite/src/admin-apis/vendor-snippets.ts: delete line 16 type ProductRow = InferSelectModel<typeof productInfo> (zero references anywhere)
Kept local (live, genuinely different types): pg product.ts:31 (1 use), sqlite product.ts:64 (2 uses).
[2026-09-05 11:28:00] COMPLETED Cluster-04 ProductRow handling (unification declined, dead copies removed).
Verification: tsc sqlite 4, pg 45 (both = baselines). Zero ProductRow references remain in either vendor-snippets.ts. Docs: cluster-04 file updated.
[2026-09-05 11:31:26] Cluster-05 (StoreRow): NOT unified — both live aliases evaluate the same today (identical storeInfo columns; both mapStores assign cleanly to shared Store), but no shared InferSelectModel source exists and hand-written copies would go silently stale. Correct action instead: delete the 2 DEAD copies.
- packages/db_helper_postgres/src/user-apis/stores.ts: delete line 7 type StoreRow = InferSelectModel<typeof storeInfo> (zero references anywhere)
- packages/db_helper_sqlite/src/user-apis/stores.ts: delete line 8 type StoreRow = InferSelectModel<typeof storeInfo> (zero references anywhere)
Kept local (live, self-maintaining mirrors): pg product.ts:33 (1 use), sqlite product.ts:103 (2 uses).
[2026-09-05 11:32:00] FOLLOW-UP cluster-05: remove orphaned imports left by StoreRow deletions.
- packages/db_helper_postgres/src/user-apis/stores.ts: delete line 4 import type { InferSelectModel } from 'drizzle-orm' (no remaining uses in file)
- packages/db_helper_sqlite/src/user-apis/stores.ts: delete line 4 import type { InferSelectModel } from 'drizzle-orm' (no remaining uses in file)
[2026-09-05 11:34:00] COMPLETED Cluster-05 StoreRow handling (unification declined, dead copies removed).
Verification: tsc sqlite 4, pg 45 (both = baselines). Zero StoreRow references remain in either user-apis/stores.ts. Docs: cluster-05 file updated.
[2026-09-05 11:36:35] Clusters 06-56 survey: no cluster carries a unify verdict — all are keep-local (schema-bound mirrors, RN/web independence, drifted shapes, circular deps) except dead-copy deletions. Executing dead-only cleanup:
- c15: migrator postgresToSqlite/index.ts lines 7-10 + sqliteToPostgres/index.ts lines 5-8 — delete dead `interface TableInfo` (ColumnInfo stays, live). No unification (ColumnInfo differs per direction).
- c28: pg + sqlite user-apis/complaint.ts line 7 — delete dead `type ComplaintRow`; line 4 `InferSelectModel` import orphaned in both, delete too.
[2026-09-05 11:39:00] Clusters 35/36/37/39/43 dead-copy deletions (unification declined per file verdicts — schema-bound mirrors stay local).
- pg admin-apis/product.ts: delete line 36 type ProductTagRow (c39, dead) + lines 37-39 ProductGroupRow / ProductGroupMembershipRow / ProductReviewRow (c35-37, dead). Keep 35 ProductTagInfoRow (live), 161/162 Insert/Update (live).
- sqlite admin-apis/product.ts: delete lines 107-109 ProductGroupRow / ProductGroupMembershipRow / ProductReviewRow (c35-37, dead) + line 302 type ProductInfoUpdate = Partial<ProductInfoInsert> (c43, dead). Keep 106 ProductTagRow (5 uses), 301 ProductInfoInsert (live).
InferSelectModel/InferInsertModel imports stay (other live aliases in both files).
[2026-09-05 11:42:00] COMPLETED type-cluster dead-copy sweep (clusters 15, 28, 35, 36, 37, 39, 43 — 12 deletions total).
Verification: tsc sqlite 4, pg 45 (= baselines), migrator 0 errors. All deleted names grep-blank; live counterparts intact (sqlite ProductTagRow 6 refs, pg ProductInfoUpdate 2 refs). Docs: 7 cluster files updated.
FINAL POSITION on "unify the rest" (06-56): no cluster carries a unify verdict. Keep-local reasons per file: schema-bound InferSelectModel mirrors (16,26,27,29-34,38,40-42 + remaining 39/43), drifted/differing shapes (07,15,18,19,22,25,45,46,49,50,55,56), intentionally distinct shared types (10,18), RN/web independence (11-14,47,51-54,56), single-use co-located props (08), backend-circular (23-25), out-of-scope fallback-ui (06,09 partial w/ shared canonical already, 48), per-app collisions (44,49,50).
[2026-09-05 11:45:00] Cluster-06: use shared IdName in fallback-ui userStore.ts.
- apps/fallback-ui/src/stores/userStore.ts: delete local `interface User/Role/Permission` (each exactly {id:number;name:string}); add import type { IdName } from '@packages/shared' + type User = IdName / type Role = IdName / type Permission = IdName. Names kept (UserWithRole extends User + store shape unchanged); only the hook is exported so no external imports affected.
[2026-09-05 11:47:00] COMPLETED Cluster-06 IdName adoption. Verification: fallback-ui tsc 107 = baseline, zero errors in userStore.ts. Docs: cluster-06 file updated.
================================================================================
2026-09-05 12:20:00 — TYPE-CLUSTER UNIFICATION (all remaining duplicated types → @packages/shared; db_helper_postgres excluded per user)
Scope decisions: schema-bound sqlite mirrors stay local (no remaining dup once pg excluded); backend-derived BaseProduct/AvailabilityEntry/MergedProduct (c23-25) stay local; ConstKey (c22) stays local; c44/49 Tag gets qualified shared name `StoreTag`.
[NEW FILE] packages/shared/types/ui-common.types.ts — single source for common-ui ↔ web-components duplicated component prop types (clusters 11,12,13,14,47,51,52,53,54,56):
+ import type { ReactNode } from 'react'
+ export interface DropdownOption { label: string; value: string | number }
+ export interface DialogProps { open: boolean; onClose: () => void; children: ReactNode; enableDismiss?: boolean }
+ export interface ConfirmationDialogProps { open: boolean; positiveAction: (comment?: string) => void; commentNeeded?: boolean; negativeAction?: () => void; title?: string; message?: string; confirmText?: string; cancelText?: string; isLoading?: boolean }
+ export interface LoadingDialogProps { open: boolean; message?: string }
+ export interface QuantifierProps { value: number; setValue: (value: number) => void; step?: number; unit?: string | { shortNotation: string }; min?: number; max?: number }
+ export interface ImageUploaderProps { images: { uri?: string }[]; onAddImage: () => void; onRemoveImage: (uri: string) => void; existingImageUrls?: string[]; onRemoveExistingImage?: (url: string) => void; allowMultiple?: boolean }
+ export interface ImageUploaderNeoItem { imgUrl: string; mimeType: string | null }
+ export interface ImageUploaderNeoPayload { url: string; mimeType: string | null }
+ export interface ImageUploaderNeoProps { images: ImageUploaderNeoItem[]; onImageAdd: (images: ImageUploaderNeoPayload[]) => void; onImageRemove: (image: ImageUploaderNeoPayload) => void; allowMultiple?: boolean }
+ export interface TextButtonProps { text: string }
[EDIT] packages/shared/types/app-common.types.ts:
+ export interface FlashDeliveryProps { isFlashDelivery?: boolean } (c8: web-ui FloatingCartBarProps, user-ui CheckoutPageProps/CartPageProps)
+ export interface ListItemProps { item: any } (c9: user-ui RenderStoreProps/SlotItemProps)
- export interface ComplaintItemProps { item: any }
+ export type ComplaintItemProps = ListItemProps (canonical shape kept under old name)
+ export interface CelebrationProps { isCelebrating: boolean } (c48: fallback-ui CharactersProps/SceneProps)
[EDIT] packages/shared/types/order.types.ts:
+ export type OrderStatus = 'cancelled' | 'success' (c20: backend order.ts 2 function-scoped copies)
+ export type DeliveryStatus = 'cancelled' | 'success' | 'pending' | 'packaged' (c21: same file)
+ export interface CartData<T = unknown> { items: T[]; totalItems: number; totalAmount: number } (c50: element CartItem differs per app → generic)
[EDIT] packages/shared/types/const.types.ts:
+ export type ConstValueType = 'string' | 'boolean' | 'number' (c19: backend + sqlite const-keys.ts)
[EDIT] packages/shared/types/admin.ts:
+ export type { ConstValueType } from './const.types' (added to existing const re-export line — barrel surface unchanged)
[EDIT] packages/shared/types/cache.types.ts (c17):
- stale note "BannerData ... intentionally NOT included" replaced: pg copy remains local by exclusion, sqlite + backend now share.
+ export interface BannerData { id: number; name: string; imageUrl: string | null; serialNum: number | null; skuIds: number[] | null; createdAt: Date }
[EDIT] packages/shared/types/store.types.ts (c44/c49):
+ export interface StoreTag { id: number; tagName: string; productIds?: number[] } (qualified name — avoids clash with richer backend Tag)
+ export interface TagChipProps { tag: StoreTag; isSelected: boolean; onPress: () => void }
[EDIT] packages/shared/types/admin.ts (c7):
+ export interface SkuFeatureLike { featureName?: string | null; featureValue: string } (was sqlite CreateSkuFeatureInput + SkuFeatureLike)
[EDIT] packages/shared/types/index.ts: + export type * from './ui-common.types'; stale comments corrected (DropdownOption/CartData now real; CartIconProps comment fixed).
2026-09-05 12:26:00 — Adopt shared ui-common types in packages/ui (common-ui) + packages/web-components (clusters 11,12,13,14,47,51,52,53,54,56). All replacements are `import type` (runtime-erased). Local prop interfaces deleted; public re-exports preserved.
packages/ui/src/components/dialog.tsx:
- lines 7-12 interface DialogProps {...} → import type { ConfirmationDialogProps, DialogProps } from '@packages/shared'
- lines 194-204 interface ConfirmationDialogProps {...} (deleted; shared import above)
- line 1: ReactNode named import dropped (only DialogProps used it): import React, { useState } from 'react'
packages/ui/src/components/loading-dialog.tsx:
- lines 3-5 interface LoadingDialogProps → import type { LoadingDialogProps } from '@packages/shared'
packages/ui/src/components/quantifier.tsx:
- lines 7-13 interface QuantifierProps → import type { QuantifierProps } from '@packages/shared' (Quantifier + MiniQuantifier both use it)
packages/ui/src/components/ImageUploader.tsx:
- lines 9-16 interface ImageUploaderProps (+ trailing comments) → import type { ImageUploaderProps } from '@packages/shared'
packages/ui/src/components/ImageUploaderNeo.tsx:
- lines 11-27: local ImageUploaderNeoItem / ImageUploaderNeoPayload / ImageUploaderNeoProps deleted
+ import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
+ export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared' (keeps packages/ui/index.ts:22,91-92 re-export surface intact)
packages/ui/src/components/dropdown.tsx:
- lines 6-9 export interface DropdownOption → import type { DropdownOption } from '@packages/shared' + export type { DropdownOption } from '@packages/shared' (public surface kept)
NOTE: bottom-dropdown.tsx (+disabled) and multi-select.tsx (value: string) are DIFFERENT shapes — intentionally untouched.
packages/ui/src/components/button.tsx:
- lines 74-76 interface MyTextButtonProps extends Omit<Props, "children"> { text: string }
+ import type { TextButtonProps } from '@packages/shared' (top)
+ interface MyTextButtonProps extends Omit<Props, "children">, TextButtonProps {}
packages/web-components/tsconfig.json:
+ paths: "@packages/shared": ["../shared"], "@packages/shared/*": ["../shared/*"] (type-only imports resolve; no runtime dep)
packages/web-components/src/components/dialog.tsx:
- lines 6-12 interface BottomDialogProps {...} → import type { ConfirmationDialogProps, DialogProps } from '@packages/shared'; BottomDialog annotated : DialogProps
- lines 55-65 interface ConfirmationDialogProps {...} (deleted)
packages/web-components/src/components/loading-dialog.tsx:
- lines 3-5 interface LoadingDialogProps → import type { LoadingDialogProps } from '@packages/shared'
packages/web-components/src/components/quantifier.tsx:
- lines 6-12 interface QuantifierProps → import type { QuantifierProps } from '@packages/shared' (Quantifier + MiniQuantifier)
packages/web-components/src/components/image-uploader.tsx:
- lines 5-12 interface ImageUploaderProps → import type { ImageUploaderProps } from '@packages/shared'
packages/web-components/src/components/image-uploader-neo.tsx:
- lines 6-19: local ImageUploaderNeoItem / ImageUploaderNeoPayload / ImageUploaderNeoProps deleted
+ import type { ImageUploaderNeoItem, ImageUploaderNeoPayload, ImageUploaderNeoProps } from '@packages/shared'
+ export type { ImageUploaderNeoItem, ImageUploaderNeoPayload } from '@packages/shared' (keeps src/index.ts:19 re-export intact)
packages/web-components/src/components/dropdown.tsx:
- lines 5-8 export interface DropdownOption → import type { DropdownOption } from '@packages/shared' + export type { DropdownOption } from '@packages/shared' (keeps src/index.ts:24 intact)
packages/web-components/src/components/my-button.tsx:
- lines 46-48 interface pButtonProps extends Omit<MyButtonProps, 'children'> { text: string }
+ import type { TextButtonProps } from '@packages/shared' (top)
+ interface pButtonProps extends Omit<MyButtonProps, 'children'>, TextButtonProps {}
2026-09-05 12:34:00 — Adopt shared types across apps + db_helper_sqlite (clusters 3,7,8,9,17,19,20,21,44,48,49,50). db_helper_postgres untouched throughout.
apps/user-ui/components/checkout-page.tsx:
- lines 20-22 interface CheckoutPageProps { isFlashDelivery?: boolean }
+ import type { FlashDeliveryProps } from '@packages/shared' (top); FC generic uses FlashDeliveryProps
apps/user-ui/components/cart-page.tsx:
- lines 32-34 interface CartPageProps { isFlashDelivery?: boolean } → same shared FlashDeliveryProps import
apps/user-ui/app/(drawer)/(tabs)/home/index.tsx:
- line 71-73 interface RenderStoreProps { item: any } → import type { ListItemProps } from '@packages/shared'; RenderStore uses ListItemProps
- line 346-348 interface SlotItemProps { item: any } → SlotItem uses ListItemProps
apps/user-ui/app/(drawer)/(tabs)/stores/store-detail/[id].tsx:
- lines 24-28 interface Tag { id; tagName; productIds? } → import type { StoreTag, TagChipProps } from '@packages/shared'; local `Tag` refs replaced with StoreTag
- lines 30-34 interface ChipProps { tag; isSelected; onPress } → Chip uses shared TagChipProps
apps/user-ui/hooks/cart-query-hooks.tsx:
- lines 45-49 interface CartData { items: CartItem[]; totalItems; totalAmount }
+ import type { CartData as SharedCartData } from '@packages/shared'; type CartData = SharedCartData<CartItem> (element differs per app — generic instantiation)
apps/web-ui/src/components/FloatingCartBar.tsx:
- lines 10-12 interface FloatingCartBarProps { isFlashDelivery?: boolean } → import type { FlashDeliveryProps } from '@packages/shared'
apps/web-ui/src/routes/stores.$storeId.tsx:
- lines 14-18 interface Tag → shared StoreTag (local refs at lines 50,134 updated)
- lines 200-204 interface TagChipProps → shared TagChipProps (tag: StoreTag)
apps/web-ui/src/hooks/cart-query-hooks.ts:
- lines 15-19 interface CartData → type CartData = SharedCartData<CartItem> (same generic pattern as user-ui)
apps/backend/src/trpc/apis/user-apis/apis/order.ts:
- lines 337-338 + 477-478: function-scoped `type DeliveryStatus` / `type OrderStatus` duplicates deleted (2 pairs)
+ import type { DeliveryStatus, OrderStatus } from '@packages/shared'
apps/backend/src/lib/const-keys.ts (c19):
- line 55 export type ConstValueType = 'string' | 'boolean' | 'number' → import type { ConstValueType } from '@packages/shared' (no external importers — verified grep)
apps/backend/src/stores/banner-store.ts (c17):
- lines 11-18 interface Banner {...} → import type { BannerData } from '@packages/shared'; Promise<Banner|null>/Promise<Banner[]> signatures now use BannerData
- line 6: `type BannerData` removed from '@/src/dbService' import list (now from shared; shape identical)
packages/db_helper_sqlite/src/lib/const-keys.ts (c19):
- line 57 export type ConstValueType → import type { ConstValueType } from '@packages/shared'
packages/db_helper_sqlite/src/user-apis/slots.ts + src/admin-apis/vendor-snippets.ts (c3, schema-bound → single-sourced INSIDE sqlite, not shared):
- vendor-snippets.ts line 15 type DeliverySlotRow = InferSelectModel<typeof deliverySlotInfo>
+ import type { SlotRow } from '../user-apis/slots' ; type DeliverySlotRow = SlotRow
- slots.ts line 7: type SlotRow = ... → export type SlotRow = ... (single source of the alias)
packages/db_helper_sqlite/src/lib/sku-features.ts + src/admin-apis/product.ts (c7):
- sku-features.ts lines 1-4 export interface SkuFeatureLike → import type { SkuFeatureLike } from '@packages/shared' + export type { SkuFeatureLike } from '@packages/shared'
- product.ts lines 69-72 interface CreateSkuFeatureInput → import adds SkuFeatureLike; line 90 features: CreateSkuFeatureInput[] → features: SkuFeatureLike[]
packages/db_helper_sqlite/src/stores/store-helpers.ts (c17):
- lines 33-40 export interface BannerData → added to existing shared re-export line: export type { ProductTagData, TagProductMapping, UserNegativityData, BannerData } from '@packages/shared'; BannerData added to type import (used by getAllBannersForCache)
apps/admin-ui/src/components/ProductForm.tsx (c7):
- lines 9-12 interface Attribute { featureName: string | null; featureValue: string } → import type { SkuFeatureLike } from '@packages/shared'; type Attribute = SkuFeatureLike (name kept for ~10 in-file refs; optional featureName is a safe widening — verified all uses)
apps/fallback-ui/src/components/3d/Characters.tsx + 3d/Scene.tsx (c48):
- local CharactersProps / SceneProps { isCelebrating: boolean } → import type { CelebrationProps } from '@packages/shared'
[2026-09-05 12:55:00] COMPLETED type-cluster unification pass (clusters 3,7,8,9,11,12,13,14,17,19,20,21,44,47,48,49,50,51,52,53,54,56 → @packages/shared).
Verification (tsc --noEmit error counts vs pre-change baselines): backend 11=11, web-ui 150=150, user-ui 106=106, admin-ui 122=122, fallback-ui 107=107, db_helper_sqlite 4=4. Diffs are line-number shifts only (deleted lines) — zero new errors. `apps/web-ui` vite build exit 0. db_helper_postgres: untouched. Remaining same-name locals verified genuinely different shapes (bottom-dropdown/multi-select DropdownOption variants, web-ui Dialog.tsx DialogProps, user-ui floating-cart-bar props) — intentionally left. type-clusters/README.md verdicts updated to ✅ for the resolved clusters with a pass note at top.
================================================================================
2026-09-05 13:40:00 — IMPLEMENT all 🟢 "unify now" clusters from similar_types.md (29 clusters; 🟡/🟣/⚪/🔵 untouched). db_helper_postgres still excluded. Derived types are shape-EXACT replacements of the locals they replace (verified per-cluster; tsc baselines re-checked after).
packages/shared/types/admin.ts (clusters 8,10,22,23,43,51):
- import type { UnitSeedData } from './seed.types' (top import block)
- AdminRebalanceSlotsResult {success,updatedOrders,message} → extends MessageResponse { updatedOrders: number[] } (c23)
- AdminUnit {id,shortNotation,fullName} → extends UnitSeedData { id: number } (c51)
- AdminSpecialDeal → NEW `export interface SpecialDealCore { quantity: string; price: string; validTill: Date }` + `AdminSpecialDeal extends SpecialDealCore { id: number; skuId: number }` (c10)
- AdminProductReview → NEW `export interface ProductReviewCore { id: number; reviewBody: string; ratings: number; reviewTime: Date; userName: string|null }` + `AdminProductReview extends ProductReviewCore { imageUrls: unknown; adminResponse: string|null; adminResponseImages: unknown }` (c22)
- AdminProductGroup {id,groupName,description,createdAt,products,productCount} → extends AdminProductGroupInfo { products: AdminSku[]; productCount: number } (c8)
- NEW SkuSummary { id: number; productId: number; productName: string; label: string; images: unknown; storeId: number|null; price: string } (after AdminSlotsProductIdsResult) (c43)
packages/shared/types/user.ts (clusters 4,10,22,29):
- './admin' import adds ProductReviewCore, SpecialDealCore; NEW import type { Complaint } from './complaint.types'
- UserComplaint {6 fields} → type UserComplaint = Pick<Complaint, 'id'|'complaintBody'|'response'|'isResolved'|'createdAt'|'orderId'> (c29; shape identical)
- NEW `export interface StoreProductCard { id; name; shortDescription: string|null; price: string; marketPrice: string|null; incrementStep: number; unit: string; unitNotation: string; images: string[]|null; isOutOfStock: boolean; productQuantity: number }` (c4)
- UserStoreProduct → type UserStoreProduct = Omit<StoreProductCard,'images'> & { images: string[] } (c4)
- UserStoreProductData → type UserStoreProductData = StoreProductCard (c4)
- UserProductSpecialDeal {quantity,price,validTill} → type UserProductSpecialDeal = SpecialDealCore (c10)
- UserProductReview {id,reviewBody,ratings,imageUrls:string[]|null,reviewTime,userName} → interface UserProductReview extends ProductReviewCore { imageUrls: string[] | null } (c22)
packages/shared/types/store.types.ts (clusters 47,48):
- + import type { IdName } from './primitives.types'; + import type { UserTagSummary } from './user' (type-only cycle, erased)
- StoreSummary {id,name,description} → interface StoreSummary extends IdName { description: string | null } (c47)
- StoreTag {id,tagName,productIds?} → type StoreTag = Pick<UserTagSummary,'id'|'tagName'> & { productIds?: number[] } (c48; productIds stays OPTIONAL)
packages/shared/types/app-common.types.ts (clusters 19,26,28,34,37,42):
+ AuthStateCore { isAuthenticated: boolean; isLoading: boolean; token: string | null } (c26)
+ AddressSelectionState { selectedAddressId: number|null; setSelectedAddressId: (addressId: number|null) => void } (c19)
+ NavigationState { isNavigatedFromHome: boolean; selectedStoreId: number|null; setNavigatedFromHome(value); setSelectedStoreId(id: number|null) } (c37)
+ FlashNavigationState { shouldNavigateToCart: boolean; setShouldNavigateToCart(value) } (c34)
+ QuickDeliveryState { isDrawerHidden: boolean; selectedSlotId: number|null; setDrawerHidden(hidden); setSelectedSlotId(id: number|null) } (c42)
+ CentralProductState<Product = unknown> { products: Product[]; productsById: Record<number,Product>; refetchProducts: (() => void | Promise<void>) | null; setProducts(products); setRefetchProducts(refetch) } (c28; refetch union accepts void AND Promise<void> to cover both apps)
packages/shared/types/index.ts: app-common comment line updated to list new types.
packages/db_helper_sqlite/src/stores/store-helpers.ts (c10):
- SpecialDealData {skuId,quantity,price,validTill} → interface SpecialDealData extends SpecialDealCore { skuId: number } (SpecialDealCore added to existing @packages/shared import; index.ts:318 re-export unaffected)
packages/db_helper_sqlite/src/user-apis/product.ts (c43):
- SkuSummary {7 fields} → import type { SkuSummary } from '@packages/shared' + export type { SkuSummary } (module export surface kept; getAllSkusSummary signature unchanged)
apps/admin-ui:
- dashboard-banners/index.tsx (c6): interface Banner → type Banner = Omit<SharedBanner,'createdAt'|'lastUpdated'> & { createdAt: string; lastUpdated: string }
- dashboard-banners/edit/[id].tsx (c6): interface Banner → Omit<SharedBanner,'description'|'skuIds'|'redirectUrl'|'serialNum'|'createdAt'|'lastUpdated'> & { description?; skuIds?; redirectUrl?; serialNum: number; createdAt: string; lastUpdated: string }
- components/ProductGroupForm.tsx (c8): ProductGroup → Omit<AdminProductGroup,'createdAt'|'products'> & { createdAt: string; products: any[] }
- manage-orders/orders/index.tsx (c20): OrderType → Omit<AdminOrderListItem,'customerName'|'customerMobile'|'items'|'createdAt'|'adminNotes'|'userNotes'|'userNegativityScore'> & { customerName: string|null; customerMobile?: string|null; items: OrderItemRow[]; createdAt: string; adminNotes?; userNotes?; userNegativityScore?; couponCode?; couponDescription?; discountAmount? } where OrderItemRow = Omit<AdminOrderListItemProduct,'id'|'skuName'|'features'|'isPackaged'|'isPackageVerified'> & { id?: number; isPackaged?: boolean; isPackageVerified?: boolean }
- components/SnippetOrdersView.tsx (c11+24): OrderProduct → Pick<AdminVendorSnippetOrderProduct,'productId'|'productName'|'quantity'|'price'|'unit'|'subtotal'>; SnippetOrder → Omit<AdminVendorSnippetOrderSummary,'totalAmount'|'slotInfo'|'products'> & { totalAmount: string; slotInfo: { time: string; sequence: any[] } | null; products: OrderProduct[] }
- components/StoreForm.tsx (c14): StoreFormData → Omit<CreateStoreInput,'description'> & { description: string; products: number[] }
- components/ProductsSelector.tsx (c43): SkuSummary → Omit<SharedSkuSummary,'images'>
- send-notifications/index.tsx (c55): User → Omit<UserMiniInfo,'name'> & { name: string|null; isEligibleForNotif: boolean }
- NEW types/drag-order-item.ts (c16): export interface DragOrderItemProps<T> { item: T; drag: () => void; isActive: boolean }
- customize-app/all-items-order.tsx (c16): ProductItemProps deleted → React.FC<DragOrderItemProps<Product>>
- customize-app/popular-items.tsx (c16+c4): ProductItemProps → interface extends DragOrderItemProps<PopularProduct> { onDelete: (id:number)=>void }; PopularProduct → Omit<StoreProductCard,'price'|'marketPrice'|'unitNotation'|'images'> & { price: number; marketPrice: number|null; images: string[]; storeId: number|null; nextDeliveryDate: string|null }
- product-tags/order.tsx (c16): TagItemProps deleted → React.FC<DragOrderItemProps<Tag>>
apps/user-ui:
- src/types/auth.ts (c17+53+26): User → Pick<SharedUser,'id'|'email'> & Partial<Omit<SharedUser,'id'|'email'|'createdAt'>> & { mobile: string|null; profileImage?: string|null; createdAt: string }; UserDetails → Pick<UserAuthProfile,'id'|'email'|'mobile'> & Partial<Omit<UserAuthProfile,'id'|'email'|'mobile'|'createdAt'>>; AuthState → interface extends AuthStateCore { user: User|null; userDetails: UserDetails|null }
- src/store/addressStore.ts (c19): interface AddressState deleted → create<AddressSelectionState>
- src/store/navigationStore.ts (c37): interface NavigationState deleted → create<NavigationState> (imported from shared)
- components/stores/flashNavigationStore.ts (c34): → type FlashNavigationState = FlashNavigationState (shared) & { reset: () => void }
- src/store/quickDeliveryStore.ts (c42): → type QuickDeliveryState = Omit<QuickDeliveryState(shared),'setDrawerHidden'> (user-ui store has no setDrawerHidden action)
- src/store/centralProductStore.ts (c28): interface CentralProductState → type CentralProductState = CentralProductState<Product> (shared generic; local Product = MergedProduct)
- hooks/cart-query-hooks.tsx (c36): interface LocalCartItem → type LocalCartItem = Omit<CartItem,'subtotal'>
- src/types/orders.ts (c56+57): OrderItem → Pick<UserOrderItemSummary,'productName'|'quantity'|'price'|'amount'|'image'>; Order → Omit<UserOrderSummary,'deliveryDate'|'items'|'discountAmount'> & { deliveryDate?: string; items: OrderItem[]; discountAmount?: number }
apps/web-ui:
- src/lib/auth-context.tsx (c17+26): User → Pick<SharedUser,'id'|'email'> & Partial<Omit<SharedUser,'id'|'email'|'createdAt'>> & { mobile: string|null; profileImage: string|null }; AuthState → interface extends AuthStateCore { user: User|null; userDetails: any }
- src/lib/stores/address-store.ts (c19): → type AddressState = AddressSelectionState & { clearSelectedAddress: () => void }
- src/lib/stores/navigation-store.ts (c37): interface NavigationStore → type NavigationStore = NavigationState (shared)
- src/lib/stores/flash-navigation-store.ts (c34): → type FlashNavigationStore = FlashNavigationState (shared)
- src/lib/stores/quick-delivery-store.ts (c42): → type QuickDeliveryStore = QuickDeliveryState (shared)
- src/lib/stores/central-product-store.ts (c28): interface CentralProductStore → type CentralProductStore = CentralProductState<Product> (shared generic, local Product)
- src/routes/me.coupons.tsx (c54): Coupon → Omit<UserCouponDisplay,'maxValue'|'minOrder'|'validTill'|'maxLimitForUser'> & { maxValue?: number; minOrder?: number; validTill?: string | Date; maxLimitForUser?: number }
[2026-09-05 14:05:00] COMPLETED all 29 🟢 "unify now" clusters from similar_types.md (clusters 4,6,8,10,11,14,16,17,19,20,22,23,24,26,28,29,34,36,37,42,43,47,48,51,53,54,55,56,57). 33 files touched (5 shared, 12 admin-ui incl. 1 new types/drag-order-item.ts, 8 user-ui, 7 web-ui, 2 db_helper_sqlite). Every replacement is shape-exact vs the local it replaced. One derivation fix during verification: user/web auth `User` originally intersected `Partial<Omit<SharedUser,...>>` with `{mobile: string|null}` — object intersection collapsed mobile to `string`; fixed by excluding 'mobile' from the Omit and overriding it directly.
Verification (tsc --noEmit error counts vs pre-change state): backend 11=11, web-ui 150=150, user-ui 106=106 (login.tsx lost 2 pre-existing errors, AuthContext regained symmetric — net 0), admin-ui 122=122, fallback-ui 107=107, db_helper_sqlite 4=4. Error-code distributions identical per project (line-shift only). db_helper_postgres: untouched. similar_types.md: 29 clusters marked ✅ done.
Remaining (documented, not in scope): 🟡 worth-doing (1,5,27,31,32,33,38,45,52,61), 🟣 shared-internal (2,7,21,46), ⚪ optional (25,30,35,39,44,50,59,60), 🔵 schema-bound/intentional (3,9,12,13,15,18,40,41,58).
2026-09-05 14:20:00 — Missed 🟢 cluster 49 (TagFormData ×2 in admin-ui) from the unify-now batch; implementing now so similar_types.md can drop ALL done clusters.
- apps/admin-ui/app/(drawer)/dashboard/product-tags/edit/index.tsx: local `interface TagFormData {…5 fields, existingImageUrl?}` → `type TagFormData = BaseTagFormData & { existingImageUrl?: string }` importing the canonical TagForm.tsx declaration (name alias import to avoid self-shadowing).
[2026-09-05 14:35:00] Pruned similar_types.md: all 30 implemented 🟢 clusters (29 batch + follow-up 49) removed. File now lists the 31 remaining candidates / 84 types (🟡 10 · 🟣 4 · ⚪ 8 · 🔵 9), original cluster IDs kept for change-log traceability; header, top-recommendations, summary table and counts rewritten. Verified: admin-ui tsc 122 = baseline after cluster-49 fix.
================================================================================
2026-09-05 15:00:00 — similar_types.md §B cluster 2 (Coupon family): reduce to TWO types — canonical `Coupon` (entity, skuIds) + `CouponFormInput = Omit<Coupon,'id'|'createdAt'>` (single form for create+update; complete data everywhere). Decisions: canonical field skuIds (API zod + admin form + sqlite schema already use it); db_helper_postgres stays UNTOUCHED (its local Create/UpdateCouponInput documented exception); update path sends complete data, helper strips createdBy from the DB write (authorship preserved).
packages/shared/types/coupon.types.ts:
- drifted Coupon {productIds: number[]|null} → canonical {skuIds: number[]|null} (all other 14 fields identical to admin.ts's shape)
+ export type CouponFormInput = Omit<Coupon, 'id' | 'createdAt'>
- CouponValidationResult.coupon?: Partial<Coupon> now references the canonical type (verified: no read-side consumers of `.coupon`)
packages/shared/types/admin.ts:
- local interface Coupon (lines ~16-31) deleted
- re-export line: export type { Coupon, CouponFormInput, CouponValidationResult, UserMiniInfo } from './coupon.types' (admin.ts has no other internal Coupon references — verified grep)
packages/db_helper_sqlite/src/admin-apis/coupon.ts:
- local CreateCouponInput + UpdateCouponInput deleted
- import adds CouponFormInput (from '@packages/shared')
- createCouponWithRelations(input: CouponFormInput, ...): insert mapping unchanged (field names match; previously-optional fields now arrive complete; NULL ≡ omitted for the nullable columns)
- updateCouponWithRelations(id, input: CouponFormInput, ...): const { createdBy: _createdBy, ...updateFields } = input; .set(updateFields) — createdBy stripped from write only
apps/backend/src/trpc/apis/admin-apis/apis/coupon.ts (only caller of both helpers; type-only + payload-completeness changes):
- create: call object completed — discountPercent/flatDiscount/minOrder/maxValue/maxLimitForUser get `?? null`, validTill `: null` when absent, isInvalidated: false added (DB result identical: all nullable columns; NULL ≡ omitted)
- update: updateData becomes a COMPLETE CouponFormInput by merging partial `updates` over the existing coupon via getCouponByIdFromDb(id) (already imported) — sent fields win, unsent fields keep current values (exclusiveApply/isInvalidated/createdBy from existing); net DB write identical to today's partial update
db_helper_postgres: untouched (user decision). admin-ui: zero changes (Formik→zod→backend flow untouched).
[2026-09-05 15:20:00] COMPLETED §B cluster 2 (Coupon family → 2 types). 4 files changed: coupon.types.ts (canonical Coupon w/ skuIds + CouponFormInput + CouponValidationResult.coupon re-anchored), admin.ts (local Coupon deleted; combined coupon re-export), sqlite admin-apis/coupon.ts (CouponFormInput signatures; update strips createdBy from write), backend admin coupon router (create sends complete payload incl. isInvalidated: false + nulls; update merges partial updates over existing coupon → complete CouponFormInput; getCouponByIdFromDb fetch added). pg untouched (45 = baseline); admin-ui untouched. Verification (tsc error counts vs pre-change): backend 11=11, web-ui 150=150, user-ui 106=106, admin-ui 122=122, fallback-ui 107=107, sqlite 4=4. similar_types.md: cluster 2 removed → 30 clusters / 78 types remain (🟡 10 · 🟣 3 · ⚪ 8 · 🔵 9).
================================================================================
2026-09-05 15:40:00 — similar_types.md §B cluster 7 (Admin order item trio, all in shared admin.ts): extract common base, chain with extends; all three names + shapes preserved exactly (contract intact).
packages/shared/types/admin.ts:
+ NEW `export interface AdminOrderItemCore { id: number; name: string; amount: number; isPackaged: boolean; isPackageVerified: boolean; skuName?: string | null; features?: { featureName: string | null; featureValue: string }[] }` (declared before AdminOrderDetailsItem)
- AdminOrderDetailsItem: common 7 fields deleted → extends AdminOrderItemCore { quantity: string; price: string; productSize: number; unit?: string }
- AdminSlotOrderItem: common 7 fields deleted → extends AdminOrderItemCore { quantity: number; price: number; unit: string }
- AdminOrderListItemProduct: common fields deleted → extends AdminSlotOrderItem { productSize: number } (Slot/List share numeric quantity+price; List adds productSize)
Consumers unchanged (backend getOrderDetails / slot-orders / orders-list + admin-ui derivations — shapes byte-equivalent). db_helper_postgres untouched.
[2026-09-05 15:50:00] COMPLETED §B cluster 7 (Admin order item trio). 1 file changed: packages/shared/types/admin.ts — NEW AdminOrderItemCore {id, name, amount, isPackaged, isPackageVerified, skuName?, features?}; AdminOrderDetailsItem extends it {quantity: string; price: string; productSize: number; unit?}; AdminSlotOrderItem extends it {quantity: number; price: number; unit: string}; AdminOrderListItemProduct extends AdminSlotOrderItem {productSize: number}. All names + shapes preserved (contract intact); backend identical tsc output. Verification (tsc error counts vs pre-change): backend 11=11, web-ui 150=150, user-ui 106=106, admin-ui 122=122, fallback-ui 107=107, sqlite 4=4, pg 45=45. similar_types.md: cluster 7 removed → 29 clusters / 75 types remain (🟡 10 · 🟣 2 · ⚪ 8 · 🔵 9).
================================================================================
2026-09-05 16:00:00 — FULL UNIFICATION PASS (remaining 29 clusters of similar_types.md). User rules: (1) add unneeded fields rather than fragment types, (2) make differing types identical + cast (numbers on inputs/cache; DB rows keep DB strings; images → string[]|null; relatedStores → number[]|null), (3) base types via extends. pg stays untouched → clusters 13/15/40/41 (pure pg↔sqlite pairs) = decided-skips. GROUP 1: packages/shared.
packages/shared/types/admin.ts:
- (c21) AdminOrderRow {16 fields} → type AdminOrderRow = Omit<PlacedOrder,'orderGroupId'|'orderGroupProportion'> & { adminNotes: string|null; orderGroupId: string|null; orderGroupProportion: string|null }; + import type { PlacedOrder } from './order.types' (shape byte-equal)
- (c9) NEW SkuFlagCore { name?: string|null; isOutOfStock?; isSuspended?; isFlashAvailable?; isOffer?; isComboOnly?; isDeleted? } (all optional); AdminSku → extends SkuFlagCore re-tightening name: string|null + all 6 flags required (shape unchanged)
- (c9/31) DEAD types replaced by the live sqlite contracts (verified zero consumers of the old shapes repo-wide):
- DELETE CreateSkuVariantInput (dead)
- NEW CreateComboItemInput { skuId: number } (was sqlite-local)
- CreateSkuInput (dead: skuCode/displayName/unitId/variants…) → live shape extends SkuFlagCore { price: number; marketPrice?: number|null; images?: string[]|null; flashPrice?: number|null; features: SkuFeatureLike[]; comboItems?: CreateComboItemInput[] }
- CreateProductInput (dead: storeId: number, no productType) → live shape { name: string; shortDescription?: string|null; longDescription?: string|null; storeId: number|null; incrementStep?: number; productType?: 'item'|'combo'; skus: CreateSkuInput[] }
- (c1) NEW ProductTagCore { tagName: string; tagDescription: string|null; imageUrl: string|null; isDashboardTag: boolean; relatedStores: number[]|null }; AdminProductTagInfo → extends ProductTagCore { id; sortOrder: number[]; createdAt: Date } (relatedStores unknown → number[]|null)
- (c5) AdminVendorSnippetCreateInput + AdminVendorSnippetUpdateInput (both dead, verified) → single export type AdminVendorSnippetInput = Omit<AdminVendorSnippet,'id'|'createdAt'>; dbService.ts re-export list updated (2 lines → 1)
packages/shared/types/staff-user.types.ts (c46):
- local StaffUser (staffRoleId: number, non-null — unreachable duplicate; barrel only exports StaffRole from this file) deleted → single source = admin.ts StaffUser (staffRoleId: number|null)
packages/shared/types/user.ts (c12/18/38/59/61/3):
- (c12) NEW ProductPriceFlags { price: number; marketPrice: number|null; flashPrice: string|null; isFlashAvailable: boolean; isOutOfStock: boolean; isSuspended: boolean } — MergedProduct (both apps) and sqlite AvailabilityCacheData anchor here
- (c18) NEW ProductDetailCore { id; productId; name; shortDescription: string|null; longDescription: string|null; price: string; marketPrice: string|null; unitNotation: string; images: string[]|null; isOutOfStock: boolean; incrementStep: number; productQuantity: number; isFlashAvailable: boolean; flashPrice: string|null; productType: string; isOffer?: boolean; isComboOnly?: boolean }; UserProductDetailData → extends ProductDetailCore { store: UserProductStoreInfo|null; deliverySlots: UserProductDeliverySlot[]; specialDeals: UserProductSpecialDeal[]; comboItems: UserProductComboItem[] } (isOffer/isComboOnly gained optional — rule 1)
- (c38) NEW ProductSummaryCore { id; name; price: string; marketPrice: string|null; unitNotation: string; images: string[]|null; isOutOfStock: boolean; incrementStep: number }
- (c59) UserSlotsWithProductsResponse → extends UserSlotsResponse { productAvailability: UserSlotAvailability[] }
- (c61) NEW UserStoreSampleProductCore<I> { id: number; images: I } + UserStoreSummaryCore<P> { id; name; description: string|null; productCount: number; sampleProducts: P[] }; UserStoreSampleProduct = Core<{signedImageUrl: string|null}>; UserStoreSampleProductData = Core<{images: string[]|null}>; UserStoreSummary extends SummaryCore<UserStoreSampleProduct> { signedImageUrl: string|null }; UserStoreSummaryData extends SummaryCore<UserStoreSampleProductData> { imageUrl: string|null }
- (c3) UserSlotWithProducts → extends UserProductDeliverySlot { products: UserSlotProduct[] }
packages/shared/types/app-common.types.ts (c25/30/39):
+ OrderDialogBaseProps { open: boolean; onClose: () => void; orderId: number }; ComplaintFormProps → extends it
+ AuthRedirectOptions { targetUrl: string; queryParams: Record<string, any> }
+ OrderCardActionProps<T> { order: T; onPress: () => void }
GROUP 2 (2026-09-05 16:10) — sqlite helpers + slots adopt shared bases (clusters 1,3,9,12,18,31,38,45,58):
packages/db_helper_sqlite/src/stores/store-helpers.ts:
- (c18) ProductBasicData {20 fields} → extends ProductDetailCore (shared) { skuName: string|null; storeId: number|null } + re-tightens isOffer/isComboOnly to required; images unknown → string[]|null via core; producer (getAllProductsForCache:129) images: sku.images → cast `as string[] | null`
- (c12) AvailabilityCacheData {7 fields} → extends ProductPriceFlags { id: number }; producer getAvailabilityForCache now writes NUMBERS: price: Number(stat.ourPrice ?? 0), marketPrice: stat.marketPrice != null ? Number(stat.marketPrice) : null, flashPrice unchanged string|null; downstream apps already Number()-cast (verified user-ui:143-144, web-ui:181-182) — no app changes
- (c58) ProductComboCacheData {8 fields} → extends UserProductComboItem { comboSkuId: number }; producer (getAllProductCombosForCache:242) images: ci.sku?.images → cast `as string[] | null`
- (c1) TagBasicData {7 fields} → extends ProductTagCore { id: number; sortOrder: number[] | null }; producer (:272) relatedStores: productTagInfo.relatedStores → cast `as number[] | null`
- (c3) DeliverySlotData {5 fields} → extends UserProductDeliverySlot { isCapacityFull: boolean; skuId: number }; SlotWithProductsData → extends UserProductDeliverySlot { isActive: boolean; isCapacityFull: boolean; products: Array<{…same inline…}> }; import adds UserProductDeliverySlot
packages/db_helper_sqlite/src/admin-apis/product.ts (c9/31/38):
- local CreateComboItemInput / CreateSkuInput / CreateProductInput deleted → imported from '@packages/shared' (createProduct's ?? null / ?? 'item' defaults unchanged)
- (c38) OffersPageProductData {8 fields} → extends ProductSummaryCore; producer mapOffersPageProduct images cast `as string[] | null`
packages/db_helper_sqlite/src/admin-apis/slots.ts (c45):
- local SlotSnippetInput {name, skuIds, validTill} deleted → import shared SlotSnippetInput {name; groupIds: number[]; skuIds: number[]; validTill: string} (groupIds unused in sqlite — rule 1)
packages/db_helper_postgres/src/admin-apis/product.ts: UNTOUCHED (pg-side tag input/sortOrder drift remains the documented exception).
GROUP 3 (2026-09-05 16:25) — component prop families (clusters 27, 33, 35):
packages/ui/src/components/dropdown.tsx (c27): NEW exported DropdownBaseProps { label: string; options: DropdownOption[]; error?: boolean; style?: any; placeholder?: string; disabled?: boolean; className?: string } (the truly common scaffold — value/onValueChange intentionally excluded because the two dropdowns declare incompatible value widths: string|number vs string|number|string[]|number[]); Props → extends DropdownBaseProps { value; onValueChange }
packages/ui/src/components/bottom-dropdown.tsx (c27+33): DropdownOption → interface extends shared DropdownOption (import alias BaseDropdownOption) + disabled?: boolean (public export kept); BottomDropdownProps → extends DropdownBaseProps + topLabel/value(wide)/onValueChange(wide)/multiple/triggerComponent/onSearch/testID
packages/web-components/src/components/image-gallery-with-delete.tsx (c35): ImageGalleryWithDeleteProps → extends ImageCarouselProps { onRemove: (uri: string) => void }
GROUP 4 (2026-09-05 16:35) — apps adopt the shared bases (clusters 12, 32, 38, 39, 44, 45, 50, 52, 60) + admin-ui ProductForm unification (cluster 9):
apps/user-ui/src/hooks/prominent-api-hooks.ts (c12): MergedProduct → BaseProduct & ProductPriceFlags & { isComboOnly?: boolean } (shared ProductPriceFlags imported; fields identical)
apps/web-ui/src/hooks/prominent-api-hooks.ts (c12): MergedProduct → BaseProduct & ProductPriceFlags
apps/user-ui/app/(drawer)/(tabs)/me/addresses/index.tsx (c52): local Address {12 fields} → type Address = Pick<UserAddress, 'id'|'name'|'phone'|'addressLine1'|'addressLine2'|'city'|'state'|'pincode'|'isDefault'|'latitude'|'longitude'|'googleMapsUrl'>
apps/user-ui/hooks/cart-query-hooks.tsx (c38): ProductSummary → extends ProductSummaryCore { isFlashAvailable: boolean; flashPrice: string|null; productQuantity: number } (images string[] → string[]|null via core)
apps/user-ui/components/NextOrderGlimpse.tsx (c39): OrderCardProps → extends OrderCardActionProps<Order> { supportMobile: string|null }
apps/admin-ui/app/(drawer)/dashboard/user-management/[id].tsx (c39): OrderItemProps → extends OrderCardActionProps<Order>
apps/user-ui/components/SlotSpecificView.tsx (c44): SlotLayoutProps → extends SlotProductsProps { isForFlashDelivery: boolean }
apps/user-ui/app/(drawer)/(tabs)/home/index.tsx (c50): ListHeaderProps → extends TagSceneProps { onSelectTag: (id:number)=>void; storesData: any; sortedSlots: any[] }
apps/web-ui/src/components/Dialog.tsx (c32): local DialogProps deleted → import shared DialogProps (now carries title?)
apps/admin-ui/app/(drawer)/dashboard/product-tags/order.tsx (c1): TagItemData? no — admin-ui TagItemData lives in product-tags/index.tsx → extends ProductTagCore { id; createdAt: string|Date }
apps/admin-ui/app/(drawer)/dashboard/customize-app/all-items-order.tsx (c60): Product → Pick<StoreProductCard,'id'|'name'|'images'|'isOutOfStock'> (images string[] → string[]|null)
apps/admin-ui/components/SlotForm.tsx (c45): local VendorSnippet → type VendorSnippet = SlotSnippetInput (shared; validTill string → string|null)
apps/admin-ui/src/components/ProductForm.tsx + products/add.tsx + products/edit.tsx (c9): Variant → Omit<CreateSkuInput,'features'|'comboItems'|'flashPrice'|'marketPrice'|'name'> & { name: string; marketPrice: number; flashPrice: number|null; features: Attribute[]; comboItems: CreateComboItemInput[] }; attributes→features rename across the form; price/marketPrice/flashPrice become numbers (TextInput display casts via String(... ?? ''), combo picker pushes {skuId: 0} + Number(val) on select); add/edit screens drop their parseFloat boundary casts (values already numeric)
[2026-09-05 17:30:00] COMPLETED the FINAL unification pass — all 25 remaining actionable clusters (1,3,5,9,12,18,21,25,27,30,31,32,33,35,38,39,44,45,46,50,52,58,59,60,61). ~30 files across shared/admin.ts+user.ts+app-common, sqlite helpers/product/slots, backend (slot-store, product-store, product-tag-store, admin coupon/slots/product trpc), packages/ui (dropdowns), web-components, user-ui, web-ui, admin-ui (incl. ProductForm numeric/features unification). Notable standardizations per user rules: availability cache now writes NUMERIC prices (apps already Number()-cast — zero app changes); SKU/form prices numeric end-to-end (ProductForm casts at display, add/edit drop parseFloat); form field attributes→features renamed; images → string[]|null everywhere; relatedStores → number[]|null (trpc getProductTags annotation updated); vendor-snippet input single-sourced with wire (string) validTill; SlotSnippetInput shared with groupIds (unused in sqlite); dead shared CreateProductInput/CreateSkuInput/CreateSkuVariantInput/AdminVendorSnippet{Create,Update}Input/staff-user StaffUser deleted and replaced by live-shape single sources. Decided-skips (13,15,40,41) documented in similar_types.md (4 clusters / 8+1 detail remain — 13, 15, 40, 41).
FINAL VERIFICATION (tsc error counts vs baselines): backend 11=11, admin-ui 122=122, user-ui 106=106, web-ui 150=150, fallback-ui 107=107, db_helper_sqlite 4=4, db_helper_postgres 45=45. All diffs are line-number/message-render shifts of pre-existing errors only.
================================================================================
2026-09-05 17:45:00 — Add root typecheck script (runs every package/app with a tsconfig).
[NEW FILE] ./typecheck (executable bash): iterates apps/backend, apps/user-ui, apps/admin-ui, apps/web-ui, apps/fallback-ui, packages/db_helper_sqlite, packages/db_helper_postgres, packages/migrator, packages/web-components — runs `npx tsc --noEmit` per target, prints ✅/❌ with error counts, prints first errors on failure, exits non-zero if any target fails. Targets without a tsconfig (packages/shared, packages/ui) are typechecked transitively via consumers.
packages/web-components/tsconfig.json:
- removed "declaration": true, "declarationMap": true, "outDir": "./dist", "rootDir": "./src" — the package is consumed as source (main: ./src/index.ts) and never emitted standalone; with the unified @packages/shared type imports now in its sources, declaration emit forced TS6059 rootDir violations (all 21 standalone errors were exactly this). Checking-only tsconfig → 0 errors.
package.json (root): + "typecheck": "bash typecheck"
[2026-09-05 17:55:00] VERIFIED ./typecheck: all 9 targets report exactly their known baselines — backend 11, user-ui 106, admin-ui 122, web-ui 150, fallback-ui 107, db_helper_sqlite 4, db_helper_postgres 45, migrator 0 ✅, web-components 3 (the pre-existing `<p weight>` custom-prop errors; standalone count went 21 → 3 after removing the declaration-emit settings). Script exits 1 while any target fails; run via `./typecheck` or `npm run typecheck` / `bun run typecheck`.
Phase 3 (2026-09-05 18:40) — admin-ui debt paydown (15 → 0) + API completeness (rule 1):
apps/backend/src/trpc/apis/common-apis/common.ts:
- scaffoldProducts response gains the numeric pricing fields it was missing (they came only from the availability feed): + price: Number(product.price ?? 0); + marketPrice (number|null); + flashPrice (number|null); + isFlashAvailable. Additive — MergedProduct/BaseProduct now carry numeric pricing consistently.
packages/ui/shared-types.ts (c: coupons edit screen):
- CreateCouponPayload + skuIds?: number[] (the edit form pre-fills coupon.skuIds; create flow already forwarded it).
apps/admin-ui:
- all-items-order.tsx(97), popular-items.tsx(124), ProductGroupForm.tsx(28), ProductsSelector.tsx(48), ProductForm.tsx(185): trpc void-input procedures called with `.useQuery({})` → `.useQuery()`
- complaints/index.tsx(66): resolveComplaint response `?? ''`
- coupons/edit/[id].tsx(92): unblocked by CreateCouponPayload.skuIds
- products/index.tsx(41/99×2/100): getDefaultSku param typed to the trpc-serialized SKU (`Omit<AdminSku,'createdAt'> & { createdAt: string | Date }`)
- products/index.tsx(117): SearchBar usage passed a non-existent onSearch prop (ignored at runtime) → removed
- products/edit.tsx(215): empty-branch initialValues gains productType: 'item'
- popular-items.tsx(150/191): unblocked by the scaffoldProducts pricing fields; PopularProduct.images loosened to string[]|null (Item already optional-chains images)
[2026-09-05 19:00:00] TYPE-DEBT PAYDOWN COMPLETE. ./typecheck: 8/9 targets ✅ clean (backend, user-ui, admin-ui, web-ui, fallback-ui, db_helper_sqlite, migrator, web-components); db_helper_postgres intentionally left red (45 pre-existing errors) per user decision — documented exception. Total debt went 455 → 45.
Genuine runtime bugs the types caught and fixed along the way: (1) backend init.ts called non-existent startOrderHandler/startCancellationHandler → startup crash removed (queue consumers live in worker); (2) s3-client passed signQuery/expires at the top level of aws4fetch's sign() where they were silently ignored → presigned URLs were header-signed and useless; now signed via aws:{signQuery} + X-Amz-Expires header; (3) web-ui register read the pre-wrapper response shape (data.token) → registration never navigated; now data.data.token; (4) web-ui localStorage cart stored sku ids under a `productId` field and matched update/remove by cart-line id — quantity updates and removals silently did nothing; renamed to skuId end-to-end; (5) web-ui used lowercase <pInput>/<p> JSX (unknown HTML elements — styled components never rendered); renamed to <PInput>/<P>; (6) availability cache wrote string prices while every consumer Number()-cast → cache now numeric (flashPrice number|null per rule 2).
Phase-1 note: backend's 'sqliteService' paths-only alias failed under the app tsconfigs (TS2307 → export * yielded nothing → hundreds of cascade errors); switched to relative imports, which is what collapsed the 455-error flood to 68 and then to 45.
2026-09-05 19:20:00 — Fix admin product save "expected number, received string": Formik TextInput values are raw strings at runtime; the Variant/ProductFormData numeric types (from the unification) were runtime-false for any price field the user touched, and the parseFloat boundary removed during unification now lives at the single submit boundary instead.
apps/admin-ui/src/components/ProductForm.tsx (onSubmit normalization — single boundary for create + edit):
+ price: v.price == null || v.price === '' ? undefined : Number(v.price)
+ marketPrice: v.marketPrice == null || v.marketPrice === '' ? null : Number(v.marketPrice)
+ flashPrice: v.flashPrice == null || v.flashPrice === '' ? null : Number(v.flashPrice)
(features normalization unchanged). Semantics preserved: cleared price → undefined → yup required() blocks submit; cleared market/flash → null/undefined as the old parseFloat-conditional did. add.tsx pre-check (price == null || price <= 0) and edit.tsx (marketPrice ?? undefined) work unchanged — values are genuinely numeric past this point.
[2026-09-06 00:00:00] Scaffold apps/admin-web as copy of apps/web-ui (src, public, package.json, package-lock.json, vite.config.ts, tsconfig.json, wrangler.jsonc, README.md). Excluded: node_modules, dist, .output, .tanstack, .wrangler.
Planned edits: package.json name web-ui->admin-web + add sonner, @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities, date-fns (web equivalents of react-native-toast-message, react-native-draggable-flatlist, date-fns already used by admin-ui); vite.config.ts port 4174->4175; wrangler.jsonc worker name; root package.json workspaces + !"apps/admin-web" + admin-web scripts (mirror web-ui standalone treatment).
[2026-09-06 00:05:00] admin-web infra batch 1 (new files): src/lib/event-bus.ts (DeviceEventEmitter-compatible bus + REFRESH_EVENT/FORCE_LOGOUT_EVENT consts), src/lib/trpc-client.ts (admin: BASE_API_URL https://devapi.freshyo.in fallback + VITE_API_URL override, Bearer JWT, 401 UNAUTHORIZED -> force-logout emit), src/lib/query-client.ts (singleton, retry 3 — same as admin-ui), src/lib/refresh-context.tsx (RefreshProvider/useRefresh/useManualRefresh/useMarkDataFetchers/useFocusCallback web ports), src/lib/theme.ts (exact theme.colors hex copy from packages/ui), src/lib/navigation-target.ts (useNavigationTarget zustand copy), src/hooks/useJWT.ts, src/hooks/useUploadToObjectStorage.ts (trpc path fix only), src/services/toaster.tsx (sonner-backed same 4-function API), src/types/vendor-snippets.ts + drag-order-item.ts (verbatim copies), src/components/context/staff-auth-context.tsx (expo-router/DeviceEventEmitter -> TanStack/bus). Deleted copied user-app src/routes/*, src/components/*, src/hooks/*, src/lib/* (scaffold placeholders, replaced by admin ports).
[2026-09-06 00:30:00] admin-web dashboard heading fixes (3 edits): (1) dashboard.index.tsx MenuItemComponent text wrapper span flex-1 -> flex min-w-0 flex-1 flex-col (title/subtitle stacked vertically like mobile RN column layout; was rendering inline same-line); (2) dashboard.index.tsx quick-action title add numberOfLines={2} (parity with mobile index.tsx:257); (3) dashboard.products.$id.tsx reply-quote raw <p> -> <MyText numberOfLines={3}> (parity with mobile detail/[id].tsx:480 N=3, same classes).
[2026-09-06 00:32:00] VERIFIED dashboard heading fixes: tsc 0 errors, vite build clean.
[2026-09-06 00:35:00] admin-web dashboard quick-action tiles: button className add flex flex-col (was items-center with no display:flex, so icon left-aligned; mobile tile is column + items-center with icon centered above title). Title already text-center.
[2026-09-06 00:36:00] VERIFIED quick-action tile fix: tsc 0 errors, vite build clean.
[2026-09-06 00:40:00] admin-web dashboard quick-action tiles: replace style width calc(25%-9px) with flex-1. RN ignores calc() so mobile tiles are content-width filling the row; web honored calc giving a 4-per-row grid with an empty 4th slot (~30% gap). flex-1 gives 3 equal tiles filling the row on all viewports, matching the phone look.
[2026-09-06 00:41:00] VERIFIED quick-action full-width fix: tsc 0 errors, vite build clean.
[2026-09-06 00:45:00] admin-web FABs absolute->fixed (viewport-pinned bottom-right irrespective of scroll; offsets/z-index unchanged): dashboard.banners.tsx:424, dashboard.customize-app.popular.tsx:338, dashboard.stores.tsx:187, dashboard.slots.tsx:286, dashboard.slots.detail.tsx:204, dashboard.coupons.tsx:502, dashboard.product-groupings.tsx:190, dashboard.rebalance.tsx (style position), dashboard.orders.sequence.tsx:683 save FAB (kept bottom-LEFT per mobile original delivery-sequences:680, only pinned to viewport).
[2026-09-06 00:47:00] VERIFIED FAB viewport pinning: tsc 0 errors, vite build clean.
[2026-09-06 01:00:00] backend admin-apis image-URL pattern: replicate user-ui scaffoldAssetUrl in admin-apis/product.ts (9 sites) + admin-apis/complaint.ts (1 site), replacing generateSignedUrlsFromS3Urls. Collapse redundant await/Promise.all wrappers, drop unused import, rename signed* locals. Response field names unchanged (no frontend impact). Write/upload paths untouched. No migration.
[2026-09-06 01:05:00] VERIFIED admin image-URL unification: backend tsc 0 errors (= baseline); only remaining generateSignedUrlsFromS3Urls refs in admin-apis are inside commented-out blocks (product.ts old getProductById + old reviews query), left untouched as history. Live admin product/complaint image fields now return permanent CDN scaffoldAssetUrl URLs like user-apis; response field names unchanged.
[2026-09-06 09:15:00] WEB-UI ME SECTION — EXACT REPLICA of user-ui me screens. Ported RN layouts/cards/dialogs/colors; added mobile-only stack headers to /me subpages (desktop keeps global shell); removed invented "About" menu entry from hub.
NEW FILES:
- apps/web-ui/src/components/me/MeScreenHeader.tsx — mobile-only (md:hidden) back-chevron + title bar replicating RN stack headers.
- apps/web-ui/src/components/me/NextOrderGlimpse.tsx — port of RN amber "Upcoming Order" horizontal snap carousel (filters active orders, flash-first sort, pagination dots, tel: support).
- apps/web-ui/src/components/me/MeOrderMenu.tsx — port of RN OrderMenu 3-dot action sheet (View Details / Edit Notes / Raise Complaint / Cancel Order) using web BottomDialog.
REWRITES (route → RN parity):
- me.tsx — hub: gray-50 bg; "My Account" 3xl header + subtitle + gear → edit-profile; NextOrderGlimpse below; three rounded-[32px] section cards w/ per-item colored 15%-alpha icon tiles (orders brand600, cart #10B981, coupons #8B5CF6, addresses #F59E0B, profile #3B82F6, complaints #EF4444, terms #6B7280) + chevron circles; white rounded-[24px] "Log Out from Freshyo" card; "Version 1.2.0 • Freshyo App" footer. Removed profile-card + About section.
- me.orders.tsx — full RN order card (rounded-[28px], ORDER REFERENCE header w/ blue dot, cancelled/FLASH pills, delivery + placed-on rows, 3-dot menu, item thumbnails w/ qty bubbles, +N more, shipping status, notes/cancel panels, total + Saved ₹ + delivery, View Details); page-1/10 infinite scroll with Load-more + loading/retry/end-of-list footer; empty/error states; local REFUND_STATUS const matching packages/ui (na/initiated/success/pending); mobile header "My Orders".
- me.orders.$id.tsx — Dialog → BottomDialog for cancel + complaint sheets; bg-linear → bg-gradient fix; header/colors aligned to RN detail.
- me.addresses.tsx — RN address cards w/ Default badge + 📞/📍, Set Default(Edit/Delete actions; + FAB header; empty state w/ MapPin + Add; loading/error+retry; add/edit via web BottomDialog + existing web AddressForm; window.confirm + alert parity for delete/set-default.
- me.coupons.tsx — wrapped w/ gray-50 + mobile header "My Coupons"; loading/error headers; content identical.
- me.complaints.tsx — full-bleed gray-50 page + mobile header "Complaints"; loading/error aligned; removed AppContainer confinement so brand "Need Help?" header is full-width.
- me.edit-profile.tsx — RN parity: centered avatar + camera upload (FileReader → useUploadToObjectStorage 'profile' context, ProfileImage), labeled Full Name/Email/Mobile fields w/ gray-50 bg + inline validation, brand "Update Profile", brand "Update Password" BottomDialog (match+length validation), gray Logout row, red "Delete Me and My Data" + BottomDialog confirm (⚠️ title + mobile entry + Cancel/Delete Forever). updateProfile now sends mobile + optional profileImageUrl (was name/email only).
- me.terms.tsx — replaced invented stub with verbatim FRESHYO legal content (sections AE) from apps/user-ui/components/TermsAndConditions.tsx inside a white card + mobile header.
NO LONGER LINKED: /me/about (hub About entry removed; RN has no About screen). Route file me.about.tsx left in place but no nav entry points to it.
NOTES: no backend/migration changes; colors/tokens reused from web-ui theme.
[2026-09-06 09:40:00] web-ui flash page bottom padding fix: apps/web-ui/src/routes/flash.tsx content wrapper gained `pb-24 md:pb-12` (was `py-6 md:px-8` only) so the last product row clears the floating 1-Hr cart bar on mobile and is scrollable — matches sibling product-grid pages (offers.tsx, stores.tsx, home.index.tsx).
[2026-09-06 09:45:00] Increased flash page bottom padding further per user: `pb-24 md:pb-12` → `pb-32 md:pb-16` in apps/web-ui/src/routes/flash.tsx.
[2026-09-06 10:00:00] web-ui home "All Available Products" load-more model (parity with user-ui dashboard): apps/web-ui/src/routes/home.index.tsx now keeps visibleCount (starts 21, +21 per click) + hasMore state; effect resets to 21 on productsData/productSlotsMap change (deps = sortedProducts recompute); grid renders sortedProducts.slice(0, visibleCount); centered rounded-full brand "Show More" button shown while hasMore. Explore-tag section already had its own 6→all Show More; untouched.
[2026-09-06 11:25:00] ADD root Playwright E2E suite driving apps/admin-web (long single-instance workflows).
NEW FILES (root):
- playwright.config.ts — testDir ./tests, fullyParallel false, workers 1, timeout 120s, expect 15s, baseURL = TEST_BASE_URL || http://localhost:4175, trace/screenshot/video on failure; projects: setup (auth.setup.ts) + chromium (deps setup, storageState tests/.auth/staff.json). NO webServer block (dev server assumed running, per user).
- tests/README.md — setup/run docs + warning that tests create real records.
- tests/.env.test.example — TEST_BASE_URL / TEST_STAFF_NAME / TEST_STAFF_PASSWORD template (real .env.test is gitignored).
- tests/tsconfig.json — strict, noEmit, types node; includes tests + config.
- tests/helpers/env.ts — dependency-free .env.test loader (no override of real env).
- tests/helpers/admin-app.ts — page object: trackDialogs/expectDialog, login, product add/fill/submit/open-edit, setProductName/Price, toggleSuspend/isSuspendChecked, slot open/fill/select-product/submit, futureSlotTimes.
- tests/auth.setup.ts — logs in via data-testid fields, saves storageState; skips with hint when creds unset.
- tests/specs/smoke.spec.ts — dashboard shell + products list render (skips without creds).
- tests/specs/product-lifecycle.spec.ts — ONE long test (single page/context) with steps: add product → verify in list → update name+price → create slot for it → verify slot card → suspend SKU → reload-check suspension persists.
EDITED (apps/admin-web — additive data-testids only, admin-web-local components; no behavior change):
- components/ProductForm.tsx: data-testid on Store wrapper (product-store-select), Product Type wrapper (product-type-select), suspend row (variant-suspend-row-0), submit button (product-submit-button).
- components/DateTimeInput.tsx: new optional testID prop → applied to wrapper (both branches).
- components/ProductsSelector.tsx: new optional testID prop → forwarded to inner MultiSelect/SearchableSelect.
- components/MultiSelect.tsx: option rows get data-testid="multiselect-option".
- components/SlotForm.tsx: passes testID slot-delivery-datetime / slot-freeze-datetime / slot-products-selector.
EDITED (root):
- package.json: +devDependency @playwright/test ^1.63.0 (via `bun add`, bun.lock updated); +scripts test:e2e / test:e2e:ui.
- .gitignore: +/test-results/, /playwright-report/, /blob-report/, /playwright/.cache/, tests/.auth/, tests/.env.test.
VERIFICATION: `npx playwright test --list` discovers 4 tests / 3 files; `npx tsc --noEmit -p tests` = 0; admin-web `tsc --noEmit` = 0; `npx playwright test` with no creds = 4 skipped (clean skip path); admin-web dev booted on :4175 with /login returning 200 rendering login-name-input/login-password-input/login-button. Full E2E run requires a live admin-web + tests/.env.test credentials (not available here).
NOTE: tests create real products/slots on whatever backend the running admin-web targets.
[2026-09-06 11:40:00] Playwright: run headed (visible browser) by default.
- playwright.config.ts: `use.headless` now env-driven — HEADED=1 → headed, HEADED=0 → headless, unset → headed locally / headless when CI is set. Locally the browser window is now visible.
- package.json: +script "test:e2e:headed": "playwright test --headed".
- tests/README.md: Running section updated (browser visible by default; HEADED=0/1, --headed, --slow-mo options documented).
VERIFICATION: `npx playwright test --list` still discovers 4 tests; `npx tsc --noEmit -p tests` = 0.
[2026-09-06 12:05:00] FIX apps/admin-web: products child routes never rendered (missing Outlet) + make E2E suite pass.
=== apps/admin-web/src/routes/dashboard.products.tsx ===
- import: `createFileRoute, Outlet, useLocation, useNavigate` (added Outlet + useLocation)
- inside Products(): + const location = useLocation()
- + after all hooks (so hook order stays stable), before the isLoading return:
// Render child routes (new / edit / detail) when not on the list route.
if (location.pathname !== '/dashboard/products') {
return <Outlet />
}
BUG: dashboard.products.new.tsx / .edit.tsx / .$id.tsx are children of the
/dashboard/products route, but the parent rendered only the list and no
<Outlet/>, so navigating to /dashboard/products/new (or /edit, or /:id)
changed the URL while still showing the list — product add/edit/detail were
unreachable. Fixed with the same guard its siblings use (stores, orders,
banners, coupons, slots).
=== tests/helpers/admin-app.ts (hydration + actionability hardening) ===
- waitForHydration(page, selector) / waitForAppHydration(page): wait for React's
`__reactFiber$`/`__reactProps$` DOM markers before interacting. Filling an SSR
input pre-hydration passes a value assertion but React (not yet attached)
never records it, then hydration resets the controlled input to ''.
- fillStable / selectStable: retry fill+assert (expect.toPass).
- clickUntilUrl / dispatchClickUntilUrl: retry a navigational click until the URL
matches (covers clicks swallowed pre-hydration, and fixed controls under the
always-mounted TanStack devtools overlay).
- login(): waitForHydration on the submit button, fillStable on both fields.
- openAddProduct/openProductEdit/openAddSlot: hydrate, then clickUntilUrl (still
used for the slots FAB from the devtools overlay).
- fillNewProduct: poll until the async store <select> has real options before
selecting a store.
=== tests/specs/product-lifecycle.spec.ts ===
- assert exact:true for the sidebar "Products" locator (also matched dashboard tiles).
- suspend step: assert unchecked → toggle → expect.poll(checked) BEFORE saving →
save. Without letting the toggle re-render, Formik submitted stale values
(isSuspended=false) — a real race, so the poll is the fix.
VERIFICATION — `bun run test:e2e` against a live admin-web (localhost:4175) with
tests/.env.test creds: 4 passed, 0 failed (44.2s) — setup (login), product-lifecycle
(create → verify in list → update name+price → create slot for it → verify slot card
→ suspend → reload-check suspension persists), smoke dashboard + products.
`npx tsc --noEmit -p tests` = 0; apps/admin-web tsc = 0.
NOTE: the run created a real product ("E2E Product <ts> Updated") and a real slot on
the backend the UI points at.
OTHER MODULES LIKELY AFFECTED (same missing-Outlet defect, not fixed — out of scope
for the product lifecycle flow): dashboard.product-groupings.tsx, dashboard.product-tags.tsx
and dashboard.customize-app.tsx have child routes (new/edit/order/popular/ordering) but
no <Outlet/> guard. Their sub-routes would also fail to render; flagged for a follow-up.
[2026-09-06 04:00:00] BUG web-ui quantity labels: "1Kg" rendered as "11Kg", "0.5Kg" -> "10.5Kg".
ROOT CAUSE: user product `unitNotation` already contains the amount+unit (e.g. "0.5Kg","1Kg"), but web-ui prepends `productQuantity` (always 1) via a ported `formatQuantity(quantity, unit)` that assumed a bare unit. user-ui renders `unitNotation` directly.
FIX (match user-ui): drop `formatQuantity` helpers + `productQuantity` prefixes; render `unitNotation` (with productType !== 'combo' guard where user-ui has one). Files: components/ProductCard.tsx, components/AddToCartDialog.tsx, components/FloatingCartBar.tsx, routes/home.product.$id.tsx, routes/flash.tsx, routes/slot-view.tsx, routes/cart.tsx, routes/home.cart.tsx.
[2026-09-06 04:05:00] VERIFIED web-ui quantity-label fix: grep shows zero formatQuantity/productQuantity render usages left; tsc 0 errors; vite build clean. Label now renders `unitNotation` directly (e.g. Qty: 0.5Kg, 1Kg) matching user-ui.
[2026-09-06 05:00:00] Feature: product out-of-stock toggle on admin products index (admin-ui + admin-web).
- packages/shared/types/admin.ts: add AdminToggleOutOfStockResult { productId, isOutOfStock, message }.
- packages/db_helper_sqlite/src/admin-apis/product.ts: add toggleProductOutOfStock(productId) — flips isOutOfStock on ALL SKU rows' productMarketStats (next = !some(out)); returns {id,isOutOfStock}|null.
- apps/backend/src/sqliteImporter.ts: export toggleProductOutOfStock.
- apps/backend/.../admin-apis/apis/product.ts: add toggleOutOfStock procedure ({id} -> helper -> 404 -> scheduleStoreInitialization -> result). Caches (products.json + availability.json) regenerate.
- apps/admin-ui .../products/index.tsx + apps/admin-web .../dashboard.products.tsx: third card button (green "Stock" when out, orange "Out" when live), mutation + refetch + toast.
[2026-09-06 05:10:00] VERIFIED out-of-stock toggle: packages/db_helper_sqlite index.ts needed explicit export of toggleProductOutOfStock (added). tsc clean: sqlite 0, backend 0, admin-ui 0, admin-web 0; admin-web vite build clean. Note: admin-ui-only toaster + backend sqlite-only DB helper (used by running backend); postgres parity not added.
[2026-09-14 09:21:37] BATCH push notifications: 5 tokens per queue message (was 1 per token).
=== apps/backend/src/trpc/apis/admin-apis/apis/user.ts ===
- add module constant near the top:
const TOKENS_PER_QUEUE_MESSAGE = 5
- sendNotification: replace the per-token enqueue loop with chunked enqueue:
before:
// Queue one job per token
let queuedCount = 0
for (const token of tokens) {
await queueDataPusher.pushNotifQueue({
name: 'send-admin-notification',
jobData: { token, title, body: text, imageUrl: imageUrl || null },
options: { attempts: 3, backoff: { type: 'exponential', delay: 2000 } },
})
queuedCount++
}
after:
let queuedCount = 0
for (let i = 0; i < tokens.length; i += TOKENS_PER_QUEUE_MESSAGE) {
const tokenChunk = tokens.slice(i, i + TOKENS_PER_QUEUE_MESSAGE)
await queueDataPusher.pushNotifQueue({
name: 'send-admin-notification',
jobData: { tokens: tokenChunk, title, body: text, imageUrl: imageUrl || null },
options: { attempts: 3, backoff: { type: 'exponential', delay: 2000 } },
})
queuedCount += tokenChunk.length
}
(jobData.token -> jobData.tokens; message payload otherwise unchanged)
=== apps/backend/src/lib/queue-consumer.ts (handleNotifQueue) ===
- before: if (body.name === 'send-admin-notification' && body.jobData?.token) {
await sendAdminNotification({ token: body.jobData.token, title, body, imageUrl: body.jobData.imageUrl ?? null })
}
- after: if (body.name === 'send-admin-notification' && Array.isArray(body.jobData?.tokens) && body.jobData.tokens.length > 0) {
await sendAdminNotification({ tokens: body.jobData.tokens, title: body.jobData.title, body: body.jobData.body, imageUrl: body.jobData.imageUrl ?? null })
}
=== apps/backend/src/lib/notif-job.ts (sendAdminNotification) ===
- signature: data.token: string -> data.tokens: string[]
- body: filter invalid tokens once, map all valid ones to Expo messages, single
expo.sendPushNotificationsAsync(messages) call per batch (was one call per token);
drop the unused `const [ticket] =` binding. Signed imageUrl / attachments logic unchanged.
NOTE: 'send-notification' messages (order packaged/delivered via scheduleNotification) remain
unhandled by the consumer — unchanged by this edit.
[2026-09-14 09:28:38] Show pack size (unitNotation) on order items.
=== packages/shared/types/user.ts ===
- UserOrderItemSummary: add unitNotation: string;
=== packages/db_helper_sqlite/src/user-apis/order.ts ===
- OrderWithRelations.orderItems[].sku: add features: Array<{ featureName: string | null; featureValue: string }>
(OrderDetailWithRelations = OrderWithRelations, so one type only)
- getOrdersWithRelations (sku.with): + features: { columns: { featureName: true, featureValue: true } }
- getOrderByIdWithRelations (sku.with): + features: { columns: { featureName: true, featureValue: true } }
=== apps/backend/src/trpc/apis/user-apis/apis/order.ts ===
- import: + composeUnitNotation from '@/src/dbService'
- getOrders item mapping: + unitNotation: composeUnitNotation(item.sku?.features || [])
- getOrderById item mapping: + unitNotation: composeUnitNotation(item.sku?.features || [])
=== apps/user-ui/app/(drawer)/(tabs)/me/my-orders/[id].tsx (order item row) ===
- before: <MyText ...>{item.quantity} × ₹{item.price}</MyText>
- after: <MyText ...>{item.quantity}{item.unitNotation ? ` × ${item.unitNotation}` : ''} · ₹{item.price}</MyText>
=== apps/web-ui/src/routes/me.orders.$id.tsx (order item row) ===
- before: <p className="mt-1 text-xs text-slate-400">{item.quantity} × ₹{item.price}</p>
- after: <p className="mt-1 text-xs text-slate-400">{item.quantity}{item.unitNotation ? ` × ${item.unitNotation}` : ''} · ₹{item.price}</p>
NOTE: db_helper_postgres order helpers left untouched (dormant package, pre-existing drift).
Order list screens (my-orders/index.tsx, web-ui me.orders.tsx) unchanged — they render item rows too
but were not in scope for this request.