applet rows: 3-col grid <title> | <body> | <ts> mirroring post.html's .post-line shape — _my_posts_applet_item / _my_buds_applet_item / _my_notes_item / _my_scrolls_item / _my_games_item all gain a .applet-list-entry.row-3col w. <a class="row-title"> (clickable, 35c/32+... server-side truncated via new lyric_extras.truncate_title filter) + <span class="row-body"> (most-recent activity excerpt, dimmed 0.6 opacity, CSS-text-overflow: ellipsis clipped to whatever space remains — no server-side trunc here so the full line lives in the DOM for inspectors) + <time class="row-ts"> (relative_ts formatted, same minmax(3rem,auto) rightward column allocation post.html's .post-line-time uses, font-size 0.75rem + opacity 0.5 + right-aligned + nowrap); SCSS grid minmax(4rem,auto) 1fr minmax(3rem,auto) lifted from .post-line's template so the timestamp column lines up across post.html / scroll.html / every applet list; per-applet data shapes — _recent_posts annotates each Post w. latest_line (Line FK ordered by -id, None for empty Note-unlock posts); _recent_buds select_related('to_user__active_title') warms the bud's donned-Note FK in one query for the buds row body ("the {{ bud.active_title_display }}" + "since {{ bud.active_title.earned_at|relative_ts }}" — the "since " prefix is unique to this row since the ts is "when they donned it", not the row's own creation); _recent_notes attaches description from _NOTE_META per slug; annotate_latest_event(rooms) helper added to apps.epic.utils (next to rooms_for_user) — attaches room.latest_event per Room w. one .events.order_by('-timestamp').first() per item, used by _billboard_context for my_rooms (My Scrolls applet) AND by apps.gameboard.views.gameboard + toggle_game_applets for my_games (My Games applet), keeping the My Scrolls + My Games shapes symmetric; _billboard_context.my_rooms = annotate_latest_event(...) swaps rooms_for_user(...).order_by("-created_at") materialisation point — bud row's "no active title" branch silently drops body + ts cells so unrecognised buds still surface but don't fabricate a "since None" line; new truncate_title filter is the existing _truncate_post_title view helper hoisted into the template namespace (literal ... past 35 chars, None-safe); 5 ITs in BillboardViewTest cover row content / row absence on missing activity / "since" prefix uniquely on the buds row + 1 in GameboardViewTest for My Games row event prose; deferred row-prose body content cap on <span class="row-body"> purely to CSS text-overflow: ellipsis per user's "middle col should take up the remaining space" steer (initial pass also server-side trunc'd the body to 35c; removed) — TDD

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-12 23:06:55 -04:00
parent eccb84f92b
commit c08dd145c3
12 changed files with 227 additions and 26 deletions

View File

@@ -1,7 +1,14 @@
{% load lyric_extras %}
{# Recent-bud row for the My Buds in-grid applet. `item` is a User. #}
{# Mirrors the dedicated billbuds page item: just a name chip, no link #}
{# (per-bud surface doesn't exist yet — coming in the 3-col sprint). #}
<li class="applet-list-entry bud-entry" data-bud-id="{{ item.id }}">
<span class="bud-name">{{ item|display_name }}</span>
{# My Buds applet row — bud handle | the <active title> | since <ts>. #}
{# `item` is a User w. `active_title` select_related'd. Mirrors the #}
{# dedicated billbuds page item shape (.bud-entry / .bud-name) so the #}
{# duplicate-flash add-flow CSS keeps working in this surface too. #}
<li class="applet-list-entry bud-entry row-3col" data-bud-id="{{ item.id }}">
{# No per-bud landing page yet — the handle is a plain span until #}
{# the 3-col bud surface ships (next sprint). #}
<span class="row-title bud-name">{{ item|at_handle }}</span>
{% if item.active_title %}
<span class="row-body">the {{ item.active_title_display }}</span>
<time class="row-ts" datetime="{{ item.active_title.earned_at|date:'c' }}">since {{ item.active_title.earned_at|relative_ts }}</time>
{% endif %}
</li>

View File

@@ -1,4 +1,11 @@
{# Recent-note row for the My Notes in-grid applet. `item` is a Note. #}
<li class="applet-list-entry">
<a href="{% url 'billboard:my_notes' %}">{{ item.display_name }}</a>
{% load lyric_extras %}
{# My Notes applet row — note title | description | earned_at. #}
{# `item` is a Note w. `description` attached by `_recent_notes` (lookup #}
{# into apps.billboard.views._NOTE_META). #}
<li class="applet-list-entry row-3col">
<a href="{% url 'billboard:my_notes' %}" class="row-title">{{ item.display_name|truncate_title }}</a>
{% if item.description %}
<span class="row-body">{{ item.description|striptags }}</span>
{% endif %}
<time class="row-ts" datetime="{{ item.earned_at|date:'c' }}">{{ item.earned_at|relative_ts }}</time>
</li>

View File

@@ -1,4 +1,10 @@
{# Recent-posts row for the My Posts in-grid applet. `item` is a Post. #}
<li class="applet-list-entry">
<a href="{{ item.get_absolute_url }}">{{ item.title }}</a>
{% load lyric_extras %}
{# My Posts applet row — title | latest line text | latest line ts. #}
{# `item` is a Post w. `latest_line` attached by `_recent_posts`. #}
<li class="applet-list-entry row-3col">
<a href="{{ item.get_absolute_url }}" class="row-title">{{ item.title|truncate_title }}</a>
{% if item.latest_line %}
<span class="row-body">{{ item.latest_line.text|striptags }}</span>
<time class="row-ts" datetime="{{ item.latest_line.created_at|date:'c' }}">{{ item.latest_line.created_at|relative_ts }}</time>
{% endif %}
</li>

View File

@@ -1,4 +1,10 @@
{# Recent-scroll row for the My Scrolls in-grid applet. `item` is a Room. #}
<li class="applet-list-entry">
<a href="{% url 'billboard:scroll' item.id %}">{{ item.name }}</a>
{% load lyric_extras %}
{# My Scrolls applet row — room name | latest event prose | event ts. #}
{# `item` is a Room w. `latest_event` attached by `annotate_latest_event`. #}
<li class="applet-list-entry row-3col">
<a href="{% url 'billboard:scroll' item.id %}" class="row-title">{{ item.name|truncate_title }}</a>
{% if item.latest_event %}
<span class="row-body">{{ item.latest_event.to_prose|striptags }}</span>
<time class="row-ts" datetime="{{ item.latest_event.timestamp|date:'c' }}">{{ item.latest_event.timestamp|relative_ts }}</time>
{% endif %}
</li>