Skip to content

Conversation

@jimchamp
Copy link
Collaborator

@jimchamp jimchamp commented Oct 29, 2025

Closes #10242

Updates the application such that:

  1. All list cards are generated using a common template (/cards/multi_image_card.html).
  2. An activity feed containing up to three cards now appears on the My Books page.

Technical

A new PubSub method has been created for the purpose of determining if a patron follows any other person on the site.

The multi_image_card card

The new multi-image card is composed of three sections:

  • An optional header
  • A container for images
  • A footer

Developers are expected to pass a valid HTML string for the card's header and footer, and a list of URLs for images that will be rendered in the card.

The new multi_image_card has been designed to either be nested in an anchor element (like the "My Lists" cards on the My Books page), or to contain multiple anchor elements within the card itself (like the cards in the "Lists" section of book pages.

If a URL is passed to the template (via the href parameter), both the header and the image container will be wrapped by anchor elements that link to the given URL. If the entire card is rendered within an anchor element, href should not be set as this will result in nested anchor tags.

Testing

Book Page:

  • Ensure that the cards in the "Lists" section appear and behave as expected.

My Books Page -- My Lists section

  • Ensure that the cards in the "My Lists" section appear and behave as expected.

My Books Page -- My Feed section

If not following others, expect:

  • Copy encouraging you to follow others
  • Feed entries to be from the general /trending feed

If following only people that do not log books, expect:

  • Copy explaining why the feed is empty
  • No cards to appear in the section

If following people who do log books, expect:

  • Up to three cards to be present in the "My Feed" section

For all cases, expect:

  • The "My Feed" heading link to go to the patron's "My Feed" page

Screenshot

image

"My Feed" for patrons who follow active patrons.

image

"My Feed" for patrons who do not follow anybody.

Stakeholders

Copilot AI review requested due to automatic review settings October 29, 2025 22:07
Copy link

@accesslint accesslint bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are accessibility issues in these changes.


$def render_images():
$for url in image_urls:
<img src="$url" loading="lazy" width="80">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This image is missing a text alternative. This is a problem for people using screen readers.

@github-actions github-actions bot added the Priority: 2 Important, as time permits. [managed] label Oct 29, 2025
Comment on lines +184 to +188
SELECT EXISTS(
SELECT 1
FROM follows
WHERE subscriber=$subscriber
)
Copy link
Collaborator Author

@jimchamp jimchamp Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick reference to EXISTS documentation, if needed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request refactors list showcase cards to use a reusable multi-image card component and adds an activity feed feature to the mybooks page. The changes extract common styling and markup into a shared component to reduce code duplication.

  • Introduces a new multi-image-card component with reusable styles and template
  • Adds an activity feed feature that displays recent bookshelf events
  • Refactors existing list showcase and list follow components to use the new shared component

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
static/css/components/multi-image-card.less New component with shared styles for multi-image cards
static/css/components/activity-feed.less Styles for activity feed cards using the multi-image-card base
static/css/components/list-showcase.less Refactored to use multi-image-card component, removing duplicate styles
static/css/components/list-follow.less Refactored to use BEM naming and removed inline styles
openlibrary/templates/cards/multi_image_card.html New reusable template for multi-image cards
openlibrary/templates/lists/showcase.html Updated to use the new multi_image_card template
openlibrary/templates/lists/list_follow.html Refactored to use the new card template
openlibrary/templates/account/activity_feed.html New template for rendering activity feeds
openlibrary/templates/account/mybooks.html Updated to include activity feed component
openlibrary/plugins/upstream/mybooks.py Added ActivityFeed class and integration
openlibrary/core/follows.py Added is_following method to check if user follows others
static/css/page-book.less Added import for multi-image-card component
static/css/components/mybooks.less Added import for activity-feed component

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<p>$_("Here's the latest activity around the library. Follow readers to personalize your feed.")</p>
$elif not feed:
<p>$_("None of the people that you follow have logged books. When they do, you'll see it here.")</p>
<div class="list-showcase">
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opening <div class=\"list-showcase\"> tag at line 48 is missing its corresponding closing tag. This will result in invalid HTML structure.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reject your spurious claim. The closing tag is on line 61...

margin-top: -10px;
position: relative;
z-index: @z-index-level-3;
border: 2px solid @white;
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The img nested within .list-follow-card__user is missing the flex-shrink: 0; property that was present in the original code. This could cause layout issues when the container width is constrained.

Suggested change
border: 2px solid @white;
border: 2px solid @white;
flex-shrink: 0;

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

@jimchamp jimchamp Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

From production

border-radius: 4px;
box-shadow: 2px 2px 4px fade(@black, 15%);
}

Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after colon in CSS property declaration. Should be display: flex; for consistency with project style.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh?

But seriously, I wonder if there is a race condition between the pre-commit linting and the copilot review?

Comment on lines +62 to +70
transform: translate(0, 20px);
}

img:nth-child(2) {
z-index: @middle-cover-z-index;
transform: translate(-8.5px, 20px);
}

img:nth-child(3) {
z-index: @bottom-cover-z-index;
transform: translate(-17px, 20px);
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded magic numbers (20px, 8.5px, 17px) for positioning. Consider using a variable like @cover-padding which is already defined at line 3 for consistency and maintainability.

Suggested change
transform: translate(0, 20px);
}
img:nth-child(2) {
z-index: @middle-cover-z-index;
transform: translate(-8.5px, 20px);
}
img:nth-child(3) {
z-index: @bottom-cover-z-index;
transform: translate(-17px, 20px);
transform: translate(0, @cover-padding);
}
img:nth-child(2) {
z-index: @middle-cover-z-index;
transform: translate(-0.425 * @cover-padding, @cover-padding);
}
img:nth-child(3) {
z-index: @bottom-cover-z-index;
transform: translate(-0.85 * @cover-padding, @cover-padding);

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +70
transform: translate(0, 20px);
}

img:nth-child(2) {
z-index: @middle-cover-z-index;
transform: translate(-8.5px, 20px);
}

img:nth-child(3) {
z-index: @bottom-cover-z-index;
transform: translate(-17px, 20px);
Copy link

Copilot AI Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded magic numbers (20px, 8.5px, 17px) for positioning. Consider using a variable like @cover-padding which is already defined at line 3 for consistency and maintainability.

Suggested change
transform: translate(0, 20px);
}
img:nth-child(2) {
z-index: @middle-cover-z-index;
transform: translate(-8.5px, 20px);
}
img:nth-child(3) {
z-index: @bottom-cover-z-index;
transform: translate(-17px, 20px);
transform: translate(0, @cover-padding);
}
img:nth-child(2) {
z-index: @middle-cover-z-index;
transform: translate(-@cover-overlap, @cover-padding);
}
img:nth-child(3) {
z-index: @bottom-cover-z-index;
transform: translate(-2 * @cover-overlap, @cover-padding);

Copilot uses AI. Check for mistakes.
Comment on lines 1 to 9
@card-width: 215px;
@cover-width: 64px;
@cover-padding: 20px;
@top-cover-z-index: 3;
@middle-cover-z-index: 2;
@bottom-cover-z-index: 1;
@cover-overlap: calc(
(3 * @cover-width - (@card-width - 2 * @cover-padding)) / 2
);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe for these to be declared at the top of the file? Maybe they should be nested to avoid overwriting other similarly named Less variables.

@jimchamp jimchamp force-pushed the 10242/activity-feed branch from 02e9d5b to abe95d5 Compare October 29, 2025 22:38
@jimchamp jimchamp force-pushed the 10242/activity-feed branch from b942fd5 to 1ea032f Compare October 29, 2025 22:53
@jimchamp jimchamp force-pushed the 10242/activity-feed branch from 22a1e70 to 64d7a86 Compare October 29, 2025 23:08
@jimchamp
Copy link
Collaborator Author

jimchamp commented Oct 29, 2025

Outstanding tasks and bugs

[Resolved] Book page list card containers no longer flexed in correct direction

image

[Resolved] Missing covers in "My Feed" cards are not properly styled

image

[Resolved] "My Books" page takes quite some time to load

I tested this with an account that doesn't follow others. We'll probably want to use the cached trending query for this case.

If things are still slow, we can request the cards after page load (recommend that this course of action be out-of-scope for this PR).

Edit: Both feeds are now cached for five minutes. Additionally, a redundant call to the follows table has been removed.


Clicking on a "My Feed" card does nothing

Not a bug per se, but it really feels like clicking on these should take me somewhere (like a book page).

@jimchamp jimchamp marked this pull request as draft October 30, 2025 00:10
@jimchamp jimchamp force-pushed the 10242/activity-feed branch from ab055c0 to 6b360d8 Compare October 31, 2025 21:45
@jimchamp jimchamp force-pushed the 10242/activity-feed branch from b539c6d to c9b6949 Compare November 3, 2025 23:58

@classmethod
def get_cached_trending_feed(cls):
five_minutes = 5 * dateutil.MINUTE_SECS
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In testing, the "My Books" page took a bit too long to initially load. On refresh, the page loaded very fast.

It may be worthwhile to increase the cache time for the trending feed to something much larger than 5 minutes. Basically, we want to show patrons who are not following others that books are being cataloged -- it need not be the most recent changes. Maybe an hour?

$for i in feed:
$ shelf_name = shelf_id_to_name[i.get('bookshelf_id')]
$ occurred_on = datestr(i.get("created"))
$ cover = get_cover_url(i) or "/images/icons/avatar_book-sm.png"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is producing incorrect covers. Is there a way to fetch a cover given a work ID (without unmarshalling the data into a model)?

Copy link
Collaborator Author

@jimchamp jimchamp Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be easier to spot if we were rendering these activity feed cards with a work title.

Comment on lines +3 to +11
$def anchored_header():
<a href="$href" class="multi-img-card__header">
$:header_html
</a>

$def header():
<div class="multi-img-card__header">
$:header_html
</div>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make much sense, in retrospect. Developers will be passing in an HTML string for the header -- they should just wrap it in an anchor if they want it to be a link.

@mekarpeles
Copy link
Member

Also related to #10241

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: 2 Important, as time permits. [managed]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Social Activity Feed to My Books page

2 participants