applet feed unification — My Buds + My Notes drop the [Feature forthcoming] / empty placeholders for live top-3 feeds, mirroring the long-standing My Posts pattern; all five in-grid list applets (My Posts / My Buds / My Notes / My Scrolls / My Games) now route their <ul> through a single shared partial _applet-grid-list.html (newly extracted) so item rendering + empty-state row + scroll-buffer all live in one place — _applet-list-shell.html (the dedicated billbuds/billposts page shell) now internally includes the same grid-list partial for its inner <ul>, so the dedicated-page and in-grid lists share the same skeleton; new per-applet item partials _my_buds_applet_item.html (mirrors _my_buds_item.html w. data-bud-id + display_name), _my_notes_item.html (links to billboard:my_notes; uses display_name), _my_posts_applet_item.html (Post link + title), _my_scrolls_item.html (Room link to billboard:scroll), _my_games_item.html (Room link to epic:gatekeeper); view-side _billboard_context gains _recent_buds(user) — sorts the User.buds auto-through table by -id so newest-added-first w.o. an explicit through model w. timestamps (manage [r.to_user for r in rows]) — + _recent_notes(user) (user.notes.order_by('-earned_at')[:limit]); same two helpers threaded into new_post's GET-with-form-errors branch (line 270-274) so the rerender keeps the new applet content visible; 7 ITs added to BillboardViewTest covering recent_buds ordering / cap / empty + recent_notes ordering / cap / cross-user isolation / empty; SCSS — .applet-list / .applet-list-entry / .applet-list-buffer lifted from .applet-list-page .applet-scroll scope to top level so they apply in both surfaces; in-grid applets get display: flex; flex-direction: column; .applet-list { flex: 1 } so the list scrolls within the applet box; #id_applet_my_games ul-centring + .scroll-list + #id_applet_notes h2 { writing-mode: vertical-rl ... } overrides removed (centring was an empty-state-only behaviour, scroll-list + vertical-rl redundant w. the new shared rule + the %applet-box > h2 rule); My Games items now left-aligned by default; empty-state row recovers the centred-italic-dim treatment via .applet-list-entry--empty { flex: 1; display: flex; align-items: center; justify-content: center; opacity: 0.6; font-style: italic } + .applet-list:has(> .applet-list-entry--empty) { display: flex; flex-direction: column } — so "No buds yet" / "No notes yet" / "No games yet" / "No scrolls yet" / "No posts yet" all centre in their applet aperture, reverting to the left-aligned stack the moment a real item lands; Most Recent Scroll's outer empty <p><small>No recent activity.</small></p> adopts the same .applet-list-entry .applet-list-entry--empty classes (section is already flex-column from existing rule) so it picks up the unified centred-italic-dim treatment
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed

pipeline fix — `_post_gear.html` (commit 6a7464e) gated the NVM target on `{% url 'billboard:my_posts' user_id=request.user.id %}`, which exploded w. NoReverseMatch when an anonymous user (Percival ch.18 anonymous-post lab — ownerless `Post.objects.create()`) hit view_post (which has no @login_required); whole gear-include now wrapped in `{% if request.user.is_authenticated %}` since anonymous viewers can't DEL/BYE/back-to-my-posts anyway; AnonymousPostViewerTest pins the 200-render + gear-absence contract so future ownerless-post regressions surface in ITs (pipeline run #298 fixed) — 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 22:48:32 -04:00
parent 6a7464ee4b
commit eccb84f92b
18 changed files with 256 additions and 146 deletions

View File

@@ -1,25 +1,80 @@
// ── My Posts applet ────────────────────────────────────────────────────────
// ── Shared scrollable applet list (.applet-list) ───────────────────────────
// In-grid applet partials (My Posts, My Buds, My Notes, My Scrolls, My Games)
// + the dedicated applet-list pages (billbuds, billposts) all wrap items in a
// `<ul class="applet-list">`. The list rules live at top level so the same
// item-entry styling applies in both surfaces; per-context wrappers below
// handle flex sizing so the list scrolls inside its parent's aperture.
#id_applet_my_posts {
.applet-list {
list-style: none;
margin: 0;
padding: 0 0.75rem 0 0;
min-height: 0;
overflow-y: auto;
// Flex-column lets the empty-state entry fill the aperture so it can
// centre vertically. Fires only when the list is entirely empty —
// as soon as a real .applet-list-entry lands, layout reverts to the
// default left-aligned vertical stack.
&:has(> .applet-list-entry--empty) {
display: flex;
flex-direction: column;
}
}
// Empty-state filler (`No <X> yet.` rows). Centres in any flex-column
// parent — the .applet-list above OR a `display: flex; flex-direction:
// column` applet section directly (e.g. #id_applet_most_recent_scroll
// when no Room has events yet).
.applet-list-entry--empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
opacity: 0.6;
font-style: italic;
margin: 0;
}
.applet-list-entry {
padding: 0.4rem 0;
.bud-name { font-weight: bold; opacity: 0.85; }
a {
color: rgba(var(--terUser), 1);
text-decoration: none;
font-weight: bold;
transition: text-shadow 0.15s ease;
&:hover,
&:active {
color: rgba(var(--ninUser), 1);
text-shadow: 0 0 0.55rem rgba(var(--terUser), 0.7);
}
}
}
.applet-list-buffer {
flex-shrink: 0;
height: 0.5rem;
}
// In-grid applet sections: flex-column so the .applet-list can flex:1
// and scroll within the applet box. Left-aligned items across the
// board (My Games used to centre — symmetrised w. the rest 2026-05-12).
#id_applet_my_posts,
#id_applet_my_buds,
#id_applet_my_scrolls,
#id_applet_notes {
display: flex;
flex-direction: column;
.my-posts-container {
.applet-list {
flex: 1;
min-height: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
&::-webkit-scrollbar { display: none; }
mask-origin: padding-box;
mask-clip: padding-box;
mask-image: linear-gradient(
to bottom,
transparent 0%,
black 5%,
black 85%,
transparent 100%
);
padding-top: 0.25rem;
}
}
@@ -235,40 +290,9 @@ body.page-billposts {
display: flex;
flex-direction: column;
.applet-list {
list-style: none;
margin: 0;
padding: 0 0.75rem 0 0;
flex: 1;
min-height: 0;
overflow-y: auto;
}
.applet-list-entry {
padding: 0.4rem 0;
.bud-name { font-weight: bold; opacity: 0.85; }
&--empty { opacity: 0.6; font-style: italic; }
a {
color: rgba(var(--terUser), 1);
text-decoration: none;
font-weight: bold;
transition: text-shadow 0.15s ease;
&:hover,
&:active {
color: rgba(var(--ninUser), 1);
text-shadow: 0 0 0.55rem rgba(var(--terUser), 0.7);
}
}
}
.applet-list-buffer {
flex-shrink: 0;
height: 0.5rem;
}
// .applet-list / .applet-list-entry / .applet-list-buffer rules
// live at top level (above) so they apply to in-grid applets too.
.applet-list { flex: 1; }
}
// Side-by-side in landscape; stacked in portrait (default).
@@ -302,20 +326,6 @@ body.page-billposts {
}
}
// ── Notes applet — vertical title ─────────────────────────────────────────
#id_applet_notes {
h2 {
writing-mode: vertical-rl;
transform: rotate(180deg);
margin: 0;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
// ── Most Recent Scroll applet — scrollable drama feed ─────────────────────
#id_applet_most_recent_scroll {
@@ -365,22 +375,5 @@ body.page-billposts {
}
}
// ── My Scrolls list ────────────────────────────────────────────────────────
#id_applet_my_scrolls {
.scroll-list {
list-style: none;
padding: 0;
margin: 0;
overflow-y: auto;
li {
padding: 0.25rem 0;
border-bottom: 1px solid rgba(var(--priUser), 0.15);
&:last-child { border-bottom: none; }
a { text-decoration: none; }
}
}
}
// My Scrolls now rides the shared `.applet-list` rule above (lifted out of
// `.applet-list-page .applet-scroll`). Old `.scroll-list` styling removed.