Hero slides, featured pieces, push broadcasts, booking inbox — the four levers that turn the Sri Maha Jewels app from a static catalogue into a living conversation with every customer who installs it.
The Android app shipped. Customers are walking around Perambalur with it in their pockets. What they see, today, is exactly whatever inventory happens to be in the DB sorted newest-first. We can't surface the piece that arrived yesterday. We can't tell them about Akshaya Tritiya. We can't push a rate drop. And every booking they send goes into a table the owner has no way to see.
Block B is the four levers that change that: a hero carousel for whatever deserves the front page this week, a featured shelf for pieces the shop wants to sell now, a push composer for the moments worth interrupting a customer for, and a bookings inbox for closing the loop on every visit request. All of it lives in bill.srimahajewels.com — one new sidebar entry, four tabs, owner-only.
Each session ships one tab, owner-reviewed, on prod. Sessions 1 and 2 are pure OTA — no new APK. Session 3 depends on Firebase credentials being set up first (the same fix that unblocks APK 12's push-token check). Session 4 closes the loop on bookings — builds on Monday's booking fix.
The hero is the one thing the shop wants every customer to notice this week. Akshaya Tritiya. A new kasulaperu haram that just landed. A rate drop good for the weekend. The ladies' ring collection the owner wants to push before Diwali.
Admin side: a drag-to-reorder list of slides. Each slide gets a photo, a title in italic serif, an optional subtitle, an optional CTA label pointing to a product code, and a date range. A slide goes live only if today falls inside its range AND the active toggle is on. Outside its range it shows a soft queued or expired chip so the owner can see at a glance what's in flight.
App side: the Home screen's top hero area auto-advances between live slides every 4.5s, respects prefers-reduced-motion, falls back gracefully to the static "Welcome to Sri Maha Jewels" panel if zero slides are live. Max 5 active at once.
hero_slides.hero/<uuid>.jpg.Newest-first is fine when there's no editorial hand. With a curator, it's wrong. The owner wants this kasulaperu haram at the front, that bangle set after it. A checkbox grid with drag-reorder gives them that power without teaching them a new concept — it's exactly how email clients move messages, which they already understand.
App side: the Shop tab renders the pinned list first as a distinct Featured row (gold-star corner badge), then the rest in the existing newest-first order. Pinned items keep their star even when scrolled past. Tap any star → "This is a piece the shop is highlighting" tooltip — no mystery.
Backend is tiny: settings.featuredProductCodes is a JSON array of product codes in display order. Nothing fancy. Max 12 picks — enough for a nice Featured row, not enough to turn the Shop into noise.
/api/app/featured — owner-only.Push is the most powerful lever in Block B — a customer's phone vibrates, their name is on it, and a sentence from the shop is on the lock screen. Used well, it closes the distance. Used carelessly, it costs a customer forever. So the composer is built around one principle: make it slow to send, fast to unsend-in-your-head.
The composer has three fields: title (64 chars), body (240 chars), optional deep-link to a product code. Under that, a segment picker — radio list, never checkbox, so there's exactly one audience per broadcast. Under that, a live notification preview rendered exactly as the customer will see it. Under that, the safety gate: recipient count, first three token previews, dry-run-to-owner button, and the "type SEND to confirm" field that only unlocks the big red button after a 5-second cooldown.
History lives in a new push_campaigns table — every send gets a row with status + delivery counts. So two weeks from now the owner can look at the list and see which message got read vs which fell flat.
Why blocked: push needs Firebase Cloud Messaging credentials uploaded to EAS. That's the same prerequisite as the APK 12 push-token check — ~10 min of Firebase Console work + eas credentials upload. Session 3 can't land until that's done.
activity_logs with recipient count, segment, sender. Non-destructive history.expo_push_token (stale-token cleanup).Bookings are already landing — Monday's fix made sure of that. What they're missing is a home on the shop side. The customer asked to see a piece; right now the owner finds out by running a SQL query. That's not a workflow.
The inbox is an email client pattern, not a dashboard. Four filter pills across the top (All / Awaiting / In convo / Confirmed) with live counts. Rows beneath show who, what, when, a notes excerpt, status, and — the key move — a one-tap WhatsApp button that opens the customer's number with a pre-filled template: "Hi {name}, this is Sri Maha Jewels — about your request to see the {product}...". No copying numbers. No retyping greetings.
Status dropdown on each row cycles Pending → Contacted → Confirmed → Cancelled. Customer's Android app picks up the change on next refresh — their My Bookings list pill flips from "Awaiting reply" to "Owner contacted you" automatically. No second message needed; the status change is the handshake.
/api/app/bookings · /api/app/bookings/:id/statuswa.me/91{phone}?text={prefilled}.
The Android app only ever reads from /api/public/*. App Manager only writes through /api/app/*. Every byte the customer sees lives in the DB the owner controls. No second store, no sync lag, no drift.
New sidebar entry inside bill.srimahajewels.com. Four tabs. Owner-only. Writes go out through /api/app/*.
GET /api/app/hero-slides
PUT /api/app/hero-slides
GET /api/app/featured
PUT /api/app/featured
POST /api/app/push/send
GET /api/app/push/history
GET /api/app/bookings
PATCH /api/app/bookings/:id/status
POST /api/app/photo-upload-url
Same smjdb. One new table (push_campaigns). One new settings key per feature. Bookings already shipped.
settings.appHeroSlides
settings.featuredProductCodes
push_campaigns (new)
bookings (existing)
customers.expo_push_token
S3: hero/<uuid>.jpg
Only reads. No client-side Admin surface. Contract extensions are additive — old APKs keep working if they're on fields they don't know.
GET /api/public/shop-info
+ hero_slides
GET /api/public/products
+ featured_codes
GET /api/public/bookings
existing · status pill
POST /api/public/push-token
existing · receives broadcasts
Small, owner-edited state (hero slides, featured picks) lives as JSON in the existing settings table — zero new migrations, no schema drift, fast iteration. Anything with volume, history, or aggregates (push history) gets its own table. Bookings already exists.
| Item | Shape | Why |
|---|---|---|
| appHeroSlides |
JSON
[{id, photoKey, title, subtitle, ctaLabel, ctaProductCode, startDate, endDate, active, sortOrder}]
Max 20 slides total (5 active). Stored in
settings.appHeroSlides. |
Tiny, infrequently written, rendered on every shop-info call. Migrating to a table later is a 10-line backfill if volume justifies. |
| featuredProductCodes |
JSON
["P0042", "P0017", "P0038", ...]
Max 12. Codes must exist in
products table. |
Order-significant, tiny, pure join key — storing it as an array preserves order naturally without an ORDER BY sortOrder table column.
|
| push_campaigns |
TABLE
id, title, body, segment, recipient_count, sent_count, delivered_count, failed_count, sent_at, status, sent_by, deep_link
New table. One row per broadcast. Indexed on
sent_at DESC. |
Aggregates over time (delivery rates), paginated history view, audit-critical — all pointing to a proper table. Not a JSON blob. |
| bookings |
EXISTING
id, customer_id, product_code, product_name, notes, status, created_at
Already shipped. Status enum: Pending · Contacted · Confirmed · Cancelled.
|
Zero schema change. The inbox is pure UI over what's already there — no new column, no migration. |
| customers.expo_push_token |
EXISTING
VARCHAR(200) NULL
Already migrated. One per customer. Stale tokens cleared by push-send failure path.
|
The single column that makes push possible. Chapter III cleans stale tokens automatically as it discovers them. |
Every lever in App Manager has an undo. Most actions are saved drafts until you explicitly publish. Push is the one exception — you can't un-vibrate a phone — so push gets its own four-gate ritual.
Every push shows the exact number of devices about to receive it, computed server-side from the segment, right above the confirm button. "42 phones" never becomes a surprise.
One tap sends the exact notification only to the owner's phone (7010742905). See it land, read it on your lock screen, then decide whether to broadcast.
Red button stays disabled until "SEND" is typed in caps. Five-second cooldown after that before it unlocks. Muscle-memory click can't fire a broadcast.
Soft: one every 30 min, three per day. Override: discount PIN. Hard ceiling: 10 per day, no override. Stops a bad mood turning into 50 push messages.
Slides save as drafts. A toggle flips one to "active". No slide reaches a customer before the owner explicitly flips that switch. Queued / Expired states shown in the list.
Every tab has a phone-frame preview beside the editor. Hero slide, push notification, featured grid — the owner sees exactly what the customer will, before any save.
Every App Manager write — hero save, push send, booking status change — writes an activity_logs row. Full audit trail for free.
When Expo reports a dead token, the backend clears customers.expo_push_token so next push skips that phone. No "sent to 42, delivered to 18" without an explanation.
Block B is owner-curated work that lands on a customer's phone. Every LOAD-BEARING feedback rule from this project's memory applies to something in these four sessions. Here's the full list and how each one is respected.
Session 1 lands the full Hero surface — editor, drag-reorder, photo upload, CTA autocomplete, live phone-frame preview, zero-state fallback, Android consumption — in the first commit. No "v1 minimal, polish later."
The mockups embedded in this plan (hero editor, featured grid, push composer, bookings inbox) are the quality floor. Direction of travel is always up — richer motion, smoother transitions, better assets. Never down.
Hero carousel animation on Android uses transform+opacity only (useNativeDriver:true). Featured row uses FlatList patterns. No blur, one shadow per hero element. 60 fps holds on the 2 GB Android 6 floor.
Before the first OTA of Block B, eas channel:edit preview --branch preview runs once to link the channel. Without this, publishes silently void. Verified via eas channel:view.
Every OTA instruction to the owner says: open, wait 30s for bundle download, force-close, reopen. Updates apply on the next launch, not the current one. Documented in every session handoff.
Any new EXPO_PUBLIC_* var a session adds is written both into eas.json.build.env (for APKs) AND via eas env:create (for OTAs). Block B doesn't add new client env vars, but this rule armed the day one does.
App Manager runs in the desktop billing app — no Android keyboard concerns in Block B itself. The rule still applies to the Android side of every consumption contract: the carousel, featured grid, and bookings list never overlap system nav, and no input auto-focuses.
Bookings Inbox borrows the WhatsApp-quick-reply pattern — one tap opens the customer's number with a pre-filled template greeting. Owner keeps their existing messaging muscle memory; no new UX to learn.
Hero titles, push body text, and featured-piece blurbs are read by customers, not staff. Tone guideline from feedback_customer_ux.md applies: friendly, trendy, never corporate. Push preview reminds the owner of this tone before sending.
Block B ships almost entirely as OTAs. APK 13 only gets rebuilt when Session 3's Firebase credentials land. If any gap is found mid-build, it waits for the NEXT build — never cancel to add a fix.
Push broadcasts share Expo Push infra, not Meta WhatsApp, so 131049-style retry rules don't apply. But rate-limits (1/30m · 3/day) serve the same purpose: don't hammer a recipient who just got one.
This page itself is the output of a rule-check pass. Every LOAD-BEARING feedback_*.md was re-read in full (not just the index description) before Session 1 code.
When you tap "start", here's the concrete delivery list for the first working session — enough to put a live hero slide in front of every Android customer by end of day. OTA ships the app-side changes automatically.
| Layer | File | What it does |
|---|---|---|
| Prereq |
eas channel:edit preview --branch preview
One-shot per channel. Verify with
eas channel:view preview — must show a branch linked. Without this, OTAs void silently (see feedback_eas_update_flow.md). |
Locks in that every Block B OTA actually reaches customers. Runs once; never again for this channel. |
| Prereq |
OTA delivery note
Every OTA handoff tells the owner: open app → wait 30 s for silent bundle download → force-close → reopen. Updates apply on next launch, not current.
|
Kills the "I shipped but nothing changed on my phone" class of bugs. Documented per feedback_eas_update_flow.md §2. |
| Frontend |
index.html
New
section#app-manager with four tabs, sidebar entry added, owner-only nav gate. |
Entry point. Sidebar shows "📱 App Manager" below Settings. Tabs: Hero (enabled), Featured/Push/Bookings (coming soon). |
| Frontend |
js/app-manager.js
New. Nav + hero-slide CRUD + drag-reorder + photo upload + live preview.
|
Tab state, draft list, save-on-blur, drag handlers, inventory-code autocomplete for CTA links. |
| Frontend |
css/app-manager.css
New. Scoped styles for App Manager surface.
|
Matches Heritage Cream tokens. Phone-frame preview layout. Does not leak into billing pages. |
| Backend |
routes/app.js
New. Owner-only. GET/PUT /hero-slides.
|
Validates shape, date ranges, caps at 5 active. Writes to settings.appHeroSlides. Logs to activity_logs. |
| Backend |
routes/public.js
Extend /shop-info response with
hero_slides. |
Filters to only active + in-date slides. Presigns S3 URLs (24h TTL). Additive — old apps ignore the new field. |
| Backend |
routes/uploads.js
Reuse existing presign pattern. New key prefix
hero/. |
No new endpoint — the existing product photo presign flow takes a prefix arg. Zero new code, zero new auth. |
| Android |
src/api/shop.ts · src/hooks/useShopInfo.ts
Consume
hero_slides field. Render in Home hero area. |
Auto-advance carousel (4.5s), reduced-motion respected, zero-slide fallback to existing brand panel. Ships as OTA. |
| Tests |
app-manager.test.js · hero-slides.test.ts
Backend shape validation + Android carousel render.
|
Covers cap enforcement, date-range math, stale slide filtering, zero-state fallback. |
The shop becomes reachable through the app in a way that isn't possible through WhatsApp alone. Monday's "what should we push this week?" question has a home. Friday's new arrival has a front-page slot. Every booking gets a human reply with a WhatsApp tap. The app earns its place on the home screen.