Welcome to the final chapter of our Tips system!
In Session History Tracking, we gave our system a memory. It knows when to be quiet because a tip is "on cooldown."
However, we still have a small problem. After filtering out irrelevant tips (Chapter 3) and annoying recent tips (Chapter 4), we might still be left with five valid tips that are all eager to be shown.
We can only show one. Which one do we pick?
This is where the Priority Scheduler comes in.
Imagine you are building a music player. You select a "Chill" genre (Context) and remove songs you skipped recently (History). You are left with 3 songs:
The Problem: If you pick randomly, you might play "Ocean Waves" again, which feels repetitive.
The Solution: You should prioritize the song you haven't heard in the longest time. This keeps the playlist feeling fresh.
Our Priority Scheduler does exactly this. It looks at the eligible tips and picks the "stalest" one (the one shown least recently).
Let's look at a concrete example. The user is editing a Python file. The Contextual Relevance Engine determines that three tips are valid:
Current Session: #105.
The Scheduler's Job:
105 - 100 = 5 sessions ago.105 - 90 = 15 sessions ago.Result: The Scheduler picks Tip C because Infinity is the largest gap.
This logic happens at the very end of the decision pipeline, right before the text appears on the user's screen.
Let's look at tipScheduler.ts to see how we implement this sorting logic.
We need to associate every tip with a number representing how "fresh" or "stale" it is.
// Inside selectTipWithLongestTimeSinceShown...
// map() creates a new list connecting the Tip to its Score
const tipsWithSessions = availableTips.map(tip => ({
tip,
// Ask the History module for the number
sessions: getSessionsSinceLastShown(tip.id),
}))
[TipA, TipB][{ tip: TipA, sessions: 5 }, { tip: TipB, sessions: 100 }]
(Note: The function getSessionsSinceLastShown was covered in Session History Tracking)
Now we sort the list so the highest number (the stalest tip) floats to the top.
// Sort in descending order (Big numbers first)
tipsWithSessions.sort((a, b) => b.sessions - a.sessions)
// The first item is now the one with the biggest gap
return tipsWithSessions[0]?.tip
If a tip has never been shown, its score is Infinity. In programming, Infinity is greater than any number, so new tips will always win against old tips.
getTipToShowOnSpinner)This is the main function the application calls. It ties together every chapter we have written so far.
export async function getTipToShowOnSpinner(
context?: TipContext,
): Promise<Tip | undefined> {
// 1. Check if user turned off tips completely
if (getSettings_DEPRECATED().spinnerTipsEnabled === false) {
return undefined
}
// 2. Filter: Contextual Relevance + History Cooldowns
// (See Chapter 3 and 4)
const tips = await getRelevantTips(context)
// 3. Selection: Run the Priority Scheduler
return selectTipWithLongestTimeSinceShown(tips)
}
Once the Scheduler picks a winner and the app displays it, we must update our records. If we don't, the Scheduler will think the tip is still "stale" and show it again next time!
export function recordShownTip(tip: Tip): void {
// 1. Stamp the history card (Reset the clock)
recordTipShown(tip.id)
// 2. Send data to our analytics team
logEvent('tengu_tip_shown', {
tipIdLength: tip.id,
cooldownSessions: tip.cooldownSessions,
})
}
By calling recordTipShown (from Session History Tracking), we reset the "staleness" score of this tip to 0. It moves to the bottom of the playlist.
Congratulations! You have completed the full tour of the Tips System.
Let's recap the journey of a single tip:
The result is a system that feels intelligent, helpful, and never annoying.
You now understand the architecture behind a smart suggestion engine!
Generated by Code IQ