feat: add info icon with filter logic popover to For You feed

Adds a clickable info icon next to the "For You" title that displays a popover explaining the feed's filtering logic ("Most Recent Video - Disinterests Tags - Watch History + Interests Tags"). Implemented as a reusable helper `attachFeedInfoPopover` in `js/ui/components/FeedInfoPopover.js` to support future use on other feed pages. Updated `views/for-you.html` and `js/forYouView.js` to integrate the new component. Verified with Playwright.
This commit is contained in:
google-labs-jules[bot] committed 2026-01-21 17:48:01 +00:00
1 parent 933841bb0e
commit bae4020682
5 files changed
+132 -1

No files matched your search

+9
View File
@@ -1,5 +1,6 @@
import { getApplication } from "./applicationContext.js";
import { devLogger } from "./utils/logger.js";
import { attachFeedInfoPopover } from "./ui/components/FeedInfoPopover.js";
const VIDEO_CARD_SELECTOR = "[data-component=\"video-card\"]";
const EMPTY_STATE_SELECTOR = "[data-for-you-empty-state]";
@@ -23,6 +24,14 @@ class ForYouView {
this.container.addEventListener("click", this.handleContainerClick);
this.observeContainer();
this.updateEmptyState();
const infoTrigger = this.document.getElementById("feedInfoTrigger");
if (infoTrigger) {
attachFeedInfoPopover(
infoTrigger,
"Most Recent Video - Disinterests Tags - Watch History + Interests Tags"
);
}
}
observeContainer() {
+44
View File
@@ -0,0 +1,44 @@
import createPopover from "../overlay/popoverEngine.js";
/**
* Attaches an informational popover to a trigger element.
*
* @param {HTMLElement} triggerElement - The element that triggers the popover.
* @param {string} text - The text content to display in the popover.
* @param {object} [options] - Optional configuration.
* @param {string} [options.placement="bottom-start"] - Popover placement.
* @returns {object|null} - The popover instance or null if failed.
*/
export function attachFeedInfoPopover(triggerElement, text, options = {}) {
if (!triggerElement || typeof text !== "string") {
return null;
}
const render = ({ document: doc, close }) => {
const panel = doc.createElement("div");
panel.className = "popover-panel p-4 max-w-xs text-sm text-text-strong bg-surface-elevated border border-surface-line rounded-lg shadow-lg z-50";
panel.textContent = text;
// Ensure the panel can receive focus if needed, but primarily it's informational.
// Making it a "tooltip" or "dialog" role might be appropriate depending on interaction.
// Since it's a click-toggle info box, standard popover behavior is fine.
// Add a simple close behavior if clicked outside (handled by popoverEngine),
// but maybe we want to close if clicked inside too?
// Usually info popovers stay open until clicked away.
return panel;
};
const popover = createPopover(triggerElement, render, {
placement: options.placement || "bottom-start",
...options,
});
triggerElement.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
popover.toggle();
});
return popover;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

+54
View File
@@ -0,0 +1,54 @@
import sys
import os
import time
from playwright.sync_api import sync_playwright
def verify_feed_info(page):
print("Pre-seeding localStorage...")
page.goto("http://localhost:8000") # Go to root first to set storage origin
page.evaluate("window.localStorage.setItem('hasSeenDisclaimer', 'true')")
print("Navigating to For You view...")
page.goto("http://localhost:8000/#view=for-you")
print("Waiting for feed info trigger...")
trigger = page.locator("#feedInfoTrigger")
try:
trigger.wait_for(state="visible", timeout=10000)
except Exception:
page.screenshot(path="verification/debug_not_found.png")
raise
# Ensure no modals are blocking
# We can try to force click or wait for potential overlays to disappear
# But pre-seeding should handle the main one.
print("Clicking trigger...")
trigger.click()
print("Waiting for popover...")
popover = page.locator(".popover-panel")
popover.wait_for(state="visible", timeout=5000)
text = popover.inner_text()
print(f"Popover text: {text}")
expected = "Most Recent Video - Disinterests Tags - Watch History + Interests Tags"
if expected not in text:
raise Exception(f"Unexpected text: {text}")
page.screenshot(path="verification/feed_info.png")
print("Screenshot saved to verification/feed_info.png")
if __name__ == "__main__":
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context()
page = context.new_page()
try:
verify_feed_info(page)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
finally:
browser.close()
+25 -1
View File
@@ -1,6 +1,30 @@
<!-- views/for-you.html -->
<section data-ds="new">
<h2 class="mb-4 text-xl font-semibold text-text">For You</h2>
<div class="flex items-center gap-2 mb-4">
<h2 class="text-xl font-semibold text-text">For You</h2>
<button
type="button"
id="feedInfoTrigger"
class="text-muted hover:text-text focus:outline-none transition-colors"
aria-label="How is this feed sorted?"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="w-5 h-5"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</button>
</div>
<div id="videoList" class="bv-grid-video">
<div class="sidebar-loading-wrapper" role="status" aria-live="polite">