my-buds async add: render full row (anchor + the <Title> + data-tt-* attrs) so the appended entry's tooltip isn't empty — TDD

Adding a bud appended a stripped `@handle`-only <li> — no `applet-list-entry`
class, no bud-page anchor, no ` the <Title>` span, no data-tt-* attrs — so the
new row rendered without its title and its tooltip came up empty next to the
server-rendered rows above it.

- add_bud (billboard/views.py) — bud payload now carries `at_handle` (server-
  computed; the client can't replicate at_handle's truncate_email fallback for
  username-less buds) + `title` (active_title_display). IT asserts both.
- _bud_add_panel.html `_appendBudEntry` — rebuilt to mirror _my_buds_item.html
  exactly: both classes, data-tt-title/description/email/shoptalk, the
  bud-name anchor into /billboard/buds/<id>/, and the trailing ` the <Title>`.

New FT pins the regression: appended row carries the attrs + anchor + title and
its portal populates non-empty on row-lock. AddBudViewTest + append FT green.

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-29 11:56:31 -04:00
parent 3bf35ad539
commit af8452f22d
4 changed files with 92 additions and 10 deletions

View File

@@ -142,6 +142,19 @@ class AddBudViewTest(TestCase):
response = self.client.get(reverse("billboard:add_bud"))
self.assertEqual(response.status_code, 405)
def test_add_returns_at_handle_and_title_for_tooltip_row(self):
"""The async-appended My Buds row mirrors _my_buds_item.html, so the
payload must carry the bud's at_handle + active_title_display to fill
the data-tt-* attrs — without them the new row's tooltip renders empty
(the entries above it, server-rendered, have them). Regression
2026-05-29."""
alice = User.objects.create(email="alice@test.io", username="alice")
body = self.client.post(
reverse("billboard:add_bud"), data={"recipient": "alice"},
).json()
self.assertEqual(body["bud"]["at_handle"], "@alice")
self.assertEqual(body["bud"]["title"], alice.active_title_display)
def test_add_resolves_username_too_not_just_email(self):
"""Phase 2: recipient field accepts usernames as well as emails."""
alice = User.objects.create(email="alice@test.io", username="alice")

View File

@@ -700,11 +700,16 @@ def add_bud(request):
already_present = candidate in request.user.buds.all()
if not already_present:
request.user.buds.add(candidate)
from apps.lyric.templatetags.lyric_extras import at_handle
display = candidate.username or candidate.email
bud = {
"id": str(candidate.id),
"username": display,
"email": candidate.email,
"id": str(candidate.id),
"username": display,
"email": candidate.email,
# at_handle + title feed the async row's data-tt-* attrs so its
# tooltip matches the server-rendered rows (regression 2026-05-29).
"at_handle": at_handle(candidate),
"title": candidate.active_title_display,
}
recipient_display = display
recipient_user_id = str(candidate.id)