This commit is contained in:
shafi54 2026-09-14 09:43:45 +05:30
parent c4a33b91da
commit 1e7db8f016
12 changed files with 12724 additions and 7674 deletions

View file

@ -79,7 +79,9 @@
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && grep -c \"add-slot-fab\" \"test-results/specs-product-lifecycle-pr-e8d1e-e-→-update-→-slot-→-suspend-chromium/error-context.md\"; grep -ao \"add-slot-fab\" \"test-results/specs-product-lifecycle-pr-e8d1e-e-→-update-→-slot-→-suspend-chromium/error-context.md\" | head; echo \"--- tail of snapshot:\"; sed -n '/```yaml/,/```/p' \"test-results/specs-product-lifecycle-pr-e8d1e-e-→-update-→-slot-→-suspend-chromium/error-context.md\" | tail -25)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && npx tsc --noEmit -p tests 2>&1 | tail -5; echo \"tsc_exit=$?\"; bun run test:e2e > /tmp/e2e_run8.log 2>&1; echo \"exit=$?\" >> /tmp/e2e_run8.log)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && npx tsc --noEmit -p tests 2>&1 | tail -5; echo \"tsc_exit=$?\"; bun run test:e2e > /tmp/e2e_run9.log 2>&1; echo \"exit=$?\" >> /tmp/e2e_run9.log)",
"Shell(DEBUG=pw:browser npx playwright test tests/specs/smoke.spec.ts 2 >& 1)"
"Shell(DEBUG=pw:browser npx playwright test tests/specs/smoke.spec.ts 2 >& 1)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo && git diff product_to_sku_c1 -- apps/user-ui/hooks/cart-query-hooks.tsx apps/user-ui/hooks/useUploadToObjectStore.ts apps/user-ui/eas.json apps/user-ui/'(drawer)' 2>/dev/null; git diff product_to_sku_c1 -- \"apps/user-ui/app/(drawer)/(tabs)/me/addresses/index.tsx\" \"apps/user-ui/app/(drawer)/(tabs)/me/my-orders/index.tsx\" | head -160)",
"Shell(cd /Users/mohammedshafiuddin/WebDev/freshyo/packages/db_helper_sqlite && npx tsc --noEmit 2>&1 | tail -8; echo \"sqlite_exit=$?\"; echo \"=== user-ui ===\"; cd /Users/mohammedshafiuddin/WebDev/freshyo/apps/user-ui && npx tsc --noEmit 2>&1 | tee /tmp/uui_tsc.txt | tail -5; echo \"userui_errors=$(grep -cE 'error TS' /tmp/uui_tsc.txt)\")"
],
"deny": [],
"defaultMode": "default"

View file

@ -1,4 +1,5 @@
# Taste
- Defines tunable/magic numbers as named module-level constants in the SAME file where they are used (e.g., `const TOKENS_PER_QUEUE_MESSAGE = 5` next to the code it configures, with a short explanatory comment) instead of inline literals or a shared/global config — explicitly stated: "Define 5 as a constant in the same file." Confidence: 0.7
- Prefers that the agent not run typechecks; instead, read the project's AGENTS.md for instructions on what to do/not do before finalizing work. Confidence: 0.9
- Judges completion by seeing the feature live in the running app ("I still don't see it on the /home route page"), so for data-driven features the agent must verify the app's actual runtime data source (API base URL, cache/backend) has the required data — not just that code builds and typechecks. Confirmed when the user signaled success only after the fix (pointing web-ui at the local backend with the tags data) made the section visible in the running app, and reconfirmed with the admin-ui product selector showing no products because it points at the production worker. Confidence: 0.9
- Prefers detailed analysis and explicit error/mistake checking before executing destructive operations, rather than just performing the action — e.g., before a git merge, or before deleting a row directly from the DB (asked "Can I simply delete the row from db. Will it cause any problems" about an unclaimed reserved coupon, expecting the agent to trace all referencing tables/FKs — couponUsages, couponApplicableUsers/Products — before answering). Confidence: 0.7
@ -104,6 +105,7 @@ er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the
- The dedup directive is general, not limited to DB helpers: for types that are "exactly same but repeated" anywhere in the monorepo, move them into a shared package (@packages/shared), organize them there with proper comments, and export/reuse them from that one source — "Do this for all the types which are same and repeated" (stated when asking to address the duplicate-type spread repo-wide). Confidence: 0.9
- When organizing a shared type package, prefers to keep similar types together — all types that have identical properties should stay grouped/collocated so exact duplicates live side by side (e.g., clustering same-shaped types together in the shared files rather than scattering them). Confidence: 0.75
- When he introduces a processing/batching parameter (e.g., a batch size such as "5 tokens per queue message"), expects it enforced end-to-end on BOTH sides of the pipeline — the write/enqueue producer AND the read/consumer/processing path — and follows up to confirm the read side honors the same limit ("we've also changed the code to read and process 5 tokens at a time right?"); a limit applied on only one side is treated as an incomplete change. Confidence: 0.6
- Scrollable content (e.g., a product grid) must never be buried beneath a persistent floating UI element like the floating cart bar: pages that scroll above such a bar need sufficient bottom padding on the content container so the last row of items can scroll fully clear of the bar ("There should be enough padding in bottom. I see the last row of products buried beneath the floating cart bar. They should be scrollable"). Errs toward GENEROUS bottom spacing and iterates for more if it still feels tight — after the initial fix (matching siblings' mobile `pb-24`) he asked to bump it again ("it's improved but add some more padding"), landing at mobile `pb-32` / desktop `md:pb-16`. Expects the fix to follow the sibling pages' bottom-padding pattern but lean roomier rather than matching the minimum. Confidence: 0.85
- Prefers an incremental "load more"/"Show More" reveal model over rendering the entire product list at once on listing/dashboard pages: show a fixed batch at a time (the user-ui dashboard's All Products list shows 21 items and adds another 21 per click, hiding the button once all are visible) and expects the same model replicated in the sibling web-ui app ("we aren't showing all the items at once. We have a load more model. Implement the same on the @apps/web-ui too"), matching the reference's batch size and button behavior. Confidence: 0.8
- For browser/E2E test automation, prefers Playwright and wants the suite placed in a `tests/` folder at the project ROOT (not inside the app it targets) — "At the project root add a folder tests. I want playwright based tests". Confidence: 0.85
@ -115,7 +117,11 @@ er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the
- Uses Bun as his package manager / package runner ("I use bun"), so whenever giving or documenting commands he expects `bun run <script>` / `bunx <tool>` rather than `npm run` / `npx` (including inside subfolders, e.g. `bun run --cwd .. <script>`). Confidence: 0.9
- Expects generated test automation to be actually EXECUTED and passing, not just written/typechecking — he asks the agent to run the suite and report how many tests succeed ("try to run the script and see how many tests succeed"), treating a suite that was never run against a live app as unverified. Confidence: 0.65
- Treats reports/audits (e.g., DEAD_CODE_REPORT.md) as claims to be independently verified against actual code usage, not trusted at face value: he asks the agent to read a report and confirm "if the pointed code is actually dead", expecting per-claim caller searches and explicit flags of false positives. Confidence: 0.75
- Before shipping/publishing a branch, wants a pre-flight risk assessment rather than just execution: he names the last published reference branch and asks "Tell me if I'm going to face any problems" — expecting the agent to verify the current branch is a superset of what was published (no feature/migration/commit lost), that removed files and assets have no dangling references or build-config dependencies, and that build/typecheck integrity holds, then present blockers vs. non-blocking caveats (e.g., committed secrets) before he deploys. Confidence: 0.7
- After performing removals himself, expects the agent to re-scan the codebase and independently confirm each item is actually gone (and that the removals didn't break anything) — "I've removed them. Check and tell me if all are gone" — treating his own "done" statement as a claim to verify rather than assuming completion. Confidence: 0.6
- When asking to compare branches/versions (e.g., "compare the current branch's @apps/user-ui with the product_to_sku_c1 branch"), wants the answer framed "functionality wise" — a semantic/behavioral summary of what actually changed for the app and its users (new logic, changed API calls, changed UI, removed screens), explicitly distinguishing real functional changes from no-op churn (pure type imports, dead-code deletions, config tweaks which he should be told are non-functional), rather than a raw file/line diff dump. Confidence: 0.6
- Wants order-item rows to display the pack size / unit notation (e.g., `2 × 500g · ₹250`) alongside quantity and unit price, not quantity alone — composed via the existing shared `composeUnitNotation` helper and surfaced through the shared item type to both user-ui and web-ui. Confidence: 0.6
- Uses "quantity" loosely to refer to the pack size / unit notation on order items: he reported "we aren't showing quantity" about a row that already rendered the numeric count, then clarified "yeah, show the pack size" — so when he says a quantity isn't shown, check whether he actually means the unit/pack-size notation is missing. Confidence: 0.55
e changes. Confidence: 0.95
er-ui/app/(drawer)/(tabs)/home/index.tsx, I want similar here") and expects the agent to mirror that pattern exactly, including details like item counts and container styling. Confidence: 0.9
als) rather than introducing new brand palettes; approval of a redesign's layout/composition does not imply approval of color or theme changes. Confidence: 0.95

File diff suppressed because it is too large Load diff

View file

@ -38,28 +38,35 @@ export const notificationWorker:any = {};
// });
export async function sendAdminNotification(data: {
token: string;
tokens: string[];
title: string;
body: string;
imageUrl: string | null;
}) {
const { token, title, body, imageUrl } = data;
const { tokens, title, body, imageUrl } = data;
const { Expo } = await import('expo-server-sdk')
// Validate Expo push token
if (!Expo.isExpoPushToken(token)) {
console.error(`Invalid Expo push token: ${token}`);
// Validate Expo push tokens (drop invalid ones)
const validTokens = tokens.filter((token) => {
if (!Expo.isExpoPushToken(token)) {
console.error(`Invalid Expo push token: ${token}`);
return false;
}
return true;
});
if (validTokens.length === 0) {
return;
}
// Generate signed URL for image if provided
const signedImageUrl = imageUrl ? scaffoldAssetUrl(imageUrl) : null;
// Send notification
const expo = new Expo();
const message = {
const messages = validTokens.map((token) => ({
to: token,
sound: 'default',
sound: 'default' as const,
title,
body,
data: { imageUrl },
@ -71,12 +78,12 @@ export async function sendAdminNotification(data: {
}
]
} : {}),
};
}));
try {
const [ticket] = await expo.sendPushNotificationsAsync([message]);
await expo.sendPushNotificationsAsync(messages);
} catch (error) {
console.log(`Failed to send notification:`, error);
console.log(`Failed to send notifications:`, error);
throw error;
}
}

View file

@ -10,9 +10,13 @@ export const handleNotifQueue =async (batch: any) => {
continue
}
if (body.name === 'send-admin-notification' && body.jobData?.token) {
if (
body.name === 'send-admin-notification' &&
Array.isArray(body.jobData?.tokens) &&
body.jobData.tokens.length > 0
) {
await sendAdminNotification({
token: body.jobData.token,
tokens: body.jobData.tokens,
title: body.jobData.title,
body: body.jobData.body,
imageUrl: body.jobData.imageUrl ?? null,

View file

@ -23,6 +23,9 @@ import {
} from '@/src/dbService';
import { queueDataPusher } from '@/src/lib/queue-data-pusher'
// Number of push tokens batched into a single queue message.
const TOKENS_PER_QUEUE_MESSAGE = 5
export const userRouter = {
getEssentials: protectedProcedure
.query(async () => {
@ -205,14 +208,15 @@ export const userRouter = {
// tokens = ['ExponentPushToken[w4KTsLKnnp8SbURdl5-Q6x]', 'ExponentPushToken[81Io9TG3Qg0s3N0V8L86T-]', 'ExponentPushToken[YJRSQmMUEUbaI2VCZLaoN_]', 'ExponentPushToken[LQZgYkFG_3CweaUbv0fBKJ]']
// Queue one job per token
// Queue one job per batch of tokens
let queuedCount = 0;
for (const token of tokens) {
for (let i = 0; i < tokens.length; i += TOKENS_PER_QUEUE_MESSAGE) {
const tokenChunk = tokens.slice(i, i + TOKENS_PER_QUEUE_MESSAGE)
try {
await queueDataPusher.pushNotifQueue({
name: 'send-admin-notification',
jobData: {
token,
tokens: tokenChunk,
title,
body: text,
imageUrl: imageUrl || null,
@ -225,17 +229,9 @@ export const userRouter = {
},
},
})
// await queueDataPusher.pushNotifQueue({
// jobData: {
// token,
// title,
// body: text,
// imageUrl: imageUrl || null,
// }
// })
queuedCount++;
queuedCount += tokenChunk.length;
} catch (error) {
console.error(`Failed to queue notification for token:`, error);
console.error(`Failed to queue notification for tokens:`, error);
}
}

View file

@ -4,6 +4,7 @@ import {
applyDiscountToUserOrder,
cancelUserOrderTransaction,
checkUserSuspended,
composeUnitNotation,
db,
deleteUserCartItemsForOrder,
getOrderProductById,
@ -366,6 +367,7 @@ export const orderRouter = router({
productName: item.sku?.product?.name || 'Unknown',
skuName: item.sku?.name || null,
quantity: parseFloat(item.quantity),
unitNotation: composeUnitNotation(item.sku?.features || []),
price: parseFloat(item.price.toString()),
discountedPrice: parseFloat(
item.discountedPrice?.toString() || item.price.toString()
@ -503,6 +505,7 @@ export const orderRouter = router({
productName: item.sku?.product?.name || 'Unknown',
skuName: item.sku?.name || null,
quantity: parseFloat(item.quantity),
unitNotation: composeUnitNotation(item.sku?.features || []),
price: parseFloat(item.price.toString()),
discountedPrice: parseFloat(
item.discountedPrice?.toString() || item.price.toString()

View file

@ -310,7 +310,7 @@ export default function OrderDetails() {
</View>
<View style={tw`flex-1`}>
<MyText style={tw`text-slate-900 font-bold text-sm`} numberOfLines={1}>{item.productName}</MyText>
<MyText style={tw`text-slate-400 text-xs mt-1`}>{item.quantity} × {item.price}</MyText>
<MyText style={tw`text-slate-400 text-xs mt-1`}>{item.quantity}{item.unitNotation ? ` × ${item.unitNotation}` : ''} · {item.price}</MyText>
</View>
<MyText style={tw`text-slate-900 font-bold text-base ml-2`}>{item.amount}</MyText>
</View>

View file

@ -316,7 +316,7 @@ function OrderDetailPage() {
</div>
<div className="flex-1">
<p className="line-clamp-1 text-sm font-bold text-slate-900">{item.productName}</p>
<p className="mt-1 text-xs text-slate-400">{item.quantity} × {item.price}</p>
<p className="mt-1 text-xs text-slate-400">{item.quantity}{item.unitNotation ? ` × ${item.unitNotation}` : ''} · {item.price}</p>
</div>
<p className="ml-2 text-base font-bold text-slate-900">{item.amount}</p>
</div>

View file

@ -1585,3 +1585,78 @@ FIX (match user-ui): drop `formatQuantity` helpers + `productQuantity` prefixes;
- 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.

View file

@ -49,6 +49,10 @@ export interface OrderWithRelations {
id: number
name: string | null
images: unknown
features: Array<{
featureName: string | null
featureValue: string
}>
product: {
name: string
} | null
@ -317,6 +321,12 @@ export async function getOrdersWithRelations(
name: true,
},
},
features: {
columns: {
featureName: true,
featureValue: true,
},
},
},
columns: {
id: true,
@ -400,6 +410,12 @@ export async function getOrderByIdWithRelations(
name: true,
},
},
features: {
columns: {
featureName: true,
featureValue: true,
},
},
},
columns: {
id: true,

View file

@ -500,6 +500,7 @@ export type UserSavePushTokenResponse = BasicSuccessResponse
export interface UserOrderItemSummary {
productName: string;
quantity: number;
unitNotation: string;
price: number;
discountedPrice: number;
amount: number;