Reference for what these two sections do, and every state they can render — Install + Service Ticket (Restore) drilldowns.
Every task drilldown opens with the same two cards at the top. They look similar but answer different questions.
Mapped in InstallStateBanner.kt → bannerVariantFor().
| # | State | Severity | Title (Hindi) |
|---|---|---|---|
| 1 | AWAITING_SLOT_PROPOSAL | warning | समय चुनना बाकी है |
| 2 | AWAITING_CUSTOMER_SELECTION | info | इंस्टॉलेशन का समय कन्फर्म हो रहा है |
| 3 | AWAITING_CUSTOMER_SLOT_CONFIRMATION | info | कनेक्शन के कन्फ़र्मेशन का इंतज़ार |
| 4 | AWAITING_TECHNICIAN_ASSIGNMENT | warning | टेक्निशियन चुनना बाकी है |
| 5 | TECHNICIAN_ASSIGNED | positive | टेक्निशियन तय हो गया |
| 6 | SLOT_CONFIRMED | positive | स्लॉट पक्का हुआ |
| 7 | SCHEDULED (future day) | positive | इंस्टॉलेशन पक्का हुआ |
| 8 | SCHEDULED (slot day = today) | warning | आज इंस्टॉलेशन का दिन है |
| 9 | ARRIVED_AT_SITE | positive | टीम पहुँच गया |
| 10 | NEEDS_RESCHEDULING | warning | कनेक्शन के लिए नया समय चुनें |
| 11 | SCHEDULING_FAILED | info | कनेक्शन ने स्लॉट कन्फर्म नहीं किया |
| 12 | IN_PROGRESS + pre-fee / fee-pending / post-fee / awaiting-OTP | info | इंस्टॉलेशन पर काम चल रहा है |
| 13 | INSTALL_SUBMITTED | info | इंस्टॉलेशन पूरा हुआ — वेरिफाई के लिए तैयार |
| 14 | DELEGATED_NOT_STARTED | info | {name} ने इंस्टॉलेशन अभी शुरू नहीं किया |
| 15 | DELEGATED_IN_PROGRESS | info | {name} काम कर रहे हैं |
| 16 | DELEGATED_OVERDUE | negative | {name} ने कनेक्शन इंस्टॉलेशन में देरी कर दी |
| 17 | VERIFICATION_PENDING | info | इंस्टॉलेशन वेरिफाई हो रहा है |
| 18 | RESOLVED | positive | इंस्टॉलेशन पूरा हुआ — {amount} मिले |
| 19 | INSTALLATION_REPORTED_FAILED | negative | इंस्टॉलेशन असफल रिपोर्ट किया गया |
SCHEDULED splits on task.isSlotDay (today vs future). RESOLVED swaps to amount-template when commissionDisplay is non-empty. All DELEGATED_* states interpolate the executor's name into the title.
Mapped in RestoreDrilldownContent.kt → RestoreStateBanner().
| # | State | Severity | Title (Hindi) |
|---|---|---|---|
| 1 | PENDING_ACCEPTANCE / RESPOND_NOW | warning | तुरंत ध्यान दें |
| 2 | ACCEPTED | info | काम शुरू करें |
| 3 | WORKING | info | काम जारी है |
| 4 | ASSIGNED_TECHNICIAN | info | टीम मेम्बर असाइन हुआ |
| 5 | DELEGATED_NOT_STARTED | info | टीम मेम्बर को भेजा — शुरू नहीं हुआ |
| 6 | DELEGATED_IN_PROGRESS | info | टीम मेम्बर काम कर रहा है |
| 7 | DELEGATED_OVERDUE | negative | समय निकल गया |
| 8 | ESCALATED_CSP_ACTIVE | negative | एस्केलेट हुआ — आपकी कार्रवाई ज़रूरी |
| 9 | ESCALATED_PLATFORM_TAKEOVER | negative | Wiom ने ले लिया है |
| 10 | CUSTOMER_DENIED | negative | ग्राहक ने मना किया |
| 11 | VERIFICATION_PENDING | info | जाँच हो रही है |
| 12 | RESOLVED | positive | समस्या ठीक हो गई |
| 13 | RECLASSIFIED | info | दोबारा वर्गीकृत हुआ |
VERIFICATION_PENDING, RESOLVED, RECLASSIFIED, and ESCALATED_PLATFORM_TAKEOVER — the banner stays but the body collapses to a summary.
The अपडेट section is the chronological event log for a task. While the banner answers "where are we now?", अपडेट answers "how did we get here?". Every state morph, slot proposal, technician assignment, or system action that happens to this task gets appended as a row.
bgBrandTint background and textPrimary color (vs textSecondary for read rows).data class TimelineEventInfo(
val timestamp: String, // pre-formatted by BE, e.g. "10 Apr, 10:14 AM"
val description: String, // Hindi copy from BE
val timestampEn: String = "",
val descriptionEn: String = "", // English copy from BE
)
// On TaskDetail:
val timeline: List<TimelineEventInfo> = emptyList() // current visible page(s)
val timelineTotalElements: Int = 0 // total on server
val timelineLoadedPage: Int = -1 // highest page idx loaded
Backend returns the timeline ordered DESC (newest first). The client never re-sorts — it just appends incoming pages to the end.
| Step | What happens |
|---|---|
| 1. Drilldown opens | HomeViewModel.fetchInstallTimeline() hits GET /install/candidates/{id}/timeline?page=0&size=5 |
| 2. Server replies | 5 newest entries + totalElements (e.g. 15) + page index 0 |
| 3. Link computation | remaining = totalElements − visible. If remaining > 0, render link with min(remaining, 5) as the displayed count. |
| 4. User taps | loadMoreInstallTimeline(taskId) increments timelineLoadedPage, fetches next page, appends results. |
| 5. All loaded | When timeline.size ≥ timelineTotalElements, the link disappears. Call is idempotent — guard line at HomeViewModel.kt:1534. |
Link copy: "और देखें ({count} और)" / "See more ({count} more)". The displayed count always advertises the next page size (5) so the copy doesn't decrement on every tap (15→10→5 would feel like progress is slowing down). The final tap may load fewer than 5 if the total isn't a multiple of the page size — that smaller count is shown then.
Each task carries unseenCount on its drilldown payload (latest_attention_version − acked_attention_version on the backend).
initialUnseenCount = remember { task.unseenCount }. This freezes which rows are "new" for the lifetime of this drilldown view.isUnread = index < initialUnseenCount. Since the list is DESC, the top N rows are the unseen ones.unseenCount > 0. The remember(task.unseenCount) key on expanded means a fresh push (e.g. an in-session event arriving) re-expands the section even if the user had previously collapsed it.HomeViewModel.onDismissTaskDrilldown ACKs the latest attention version server-side, dropping unseenCount to 0. Next time the drilldown opens, no rows are tinted.| Trigger | Sample description (hi) |
|---|---|
| Task created on CSP | नई कनेक्शन रिक्वेस्ट मिली |
| Assigned to CSP | CSP को दिया गया |
| Slots proposed | कनेक्शन को 2 समय भेजे |
| Customer picked slot | इंस्टॉलेशन के लिए दोपहर, 11 am – 1 pm, सोमवार 14 अप्रैल चुना गया है |
| Slot auto-confirmed | स्लॉट ऑटो-कन्फर्म हुआ |
| Technician assigned | {name} को असाइन किया |
| Arrived at site | टीम साइट पर पहुँची |
| Installation submitted | इंस्टॉलेशन सबमिट हुआ |
| Verification started | व्योम जाँच कर रहा है |
| Resolved | इंस्टॉलेशन पूरा हुआ |
| Reschedule requested | नए स्लॉट भेजे |
| Trigger | What renders |
|---|---|
task.timeline.isEmpty() | "अभी कोई अपडेट नहीं" hint in textHint color; section visible but body is just the hint line. |
task.unseenCount > 0 | Section auto-expands; pink badge with count next to the title; top-N rows highlighted with bgBrandTint. |
unseenCount == 0 & has entries | Section collapsed by default; tap header to expand. All rows in textSecondary. |
timelineTotalElements > visible | "और देखें ({n} और)" link at the bottom; tap loads next 5 from server. |
timeline.size ≥ timelineTotalElements | "और देखें" link disappears; full history shown. |
| Drilldown dismissed | onDismissTaskDrilldown resets unseenCount = 0; badge + tints clear on next open. |
| In-session morph (e.g. CSP assigns tech) | VM appends a synthetic TimelineEventInfo locally + bumps unseenCount so §2 re-expands and the new row is tinted. |
| Aspect | Install (StatusTimelineCard) | Restore (RestoreTimelineSection) |
|---|---|---|
| Title label key | scheduling_timeline → "अपडेट" | event_timeline → "अपडेट" |
| Empty label key | timeline.empty → "अभी कोई अपडेट नहीं" | same (shared) |
| Pagination | Server-side, BE-driven, 5-per-page via /install/candidates/{id}/timeline | Client-side cap at 5 visible; "और देखें" toggles to show-all in the same payload |
| Code location | InstallDrilldownContent.kt:437 | RestoreDrilldownContent.kt:392 |
Scenario: An install task is in AWAITING_TECHNICIAN_ASSIGNMENT, the slot day is approaching, and Wiom decides the CSP needs to hurry up. Where does that message live — banner or अपडेट?
Wiom's backend doesn't transition the state — it pushes one new TimelineEventInfo with unseenCount bumped. That single signal lights up multiple surfaces:
| Surface | What changes | Why |
|---|---|---|
| अपडेट section | New row at top with pink-tint background: "जल्दी करें — स्लॉट 2 घंटे में शुरू, टीम मेम्बर चुनें" | This is the actual carrier of the message |
| अपडेट badge | Pink "1" count next to the section title | Driven by unseenCount bump |
| अपडेट auto-expand | Section auto-opens on next drilldown entry | expanded = remember(unseenCount) { unseenCount > 0 } |
| Home chip dot | Pink "new entry" dot on the नया काम chip | hasNewEntry = true flows up to the chip |
| Task feed row | Card gets a pink dot, may move up | Same unseenCount signal |
| Bell icon | Notification badge increments | Push channel mirrors the timeline entry |
| Banner title | Unchanged — still "टेक्निशियन चुनना बाकी है" | State didn't change — still AWAITING_TECHNICIAN_ASSIGNMENT |
| Banner severity (optional) | Can flip warning → negative if Wiom decides it's critical | Requires a server-side severity bump, not just a timeline event |
| Countdown pill | thresholdState: NORMAL → URGENT → OVERDUE, yellow → red | Time-based, independent of the message |
unseenCount is the single switch that lights up the chip dot, section badge, task card dot, and bell — without each surface needing its own urgency channel.NEEDS_RESCHEDULING → banner flips to "कनेक्शन के लिए नया समय चुनें"CUSTOMER_DENIEDESCALATED_PLATFORM_TAKEOVER