bud panels duplicate-add guard: server-side already_present flag + client-side error Brief w. FYI flash highlight on the existing entry — for each of the three #id_bud_btn panels (My Buds / post-share / gatekeeper-invite), the JSON response from add_bud / share_post / invite_gamer now carries {already_present, recipient_display, recipient_user_id}; bud-btn.js branches on already_present → calls new Brief.showDuplicateBanner({display_name, target_selector}) instead of the normal onSuccess append; banner title reads @<username> is already present, NVM dismisses, FYI dismisses AND eases in the .bud-duplicate-flash class (color: var(--terUser); text-shadow: 0 0 .5em var(--ninUser); transition: 600ms) onto the existing element (.bud-entry .bud-name / .post-recipient[data-user-id=…] / .gate-slot.filled[data-user-id=…]); gatekeeper "already present" = recipient is either GateSlot.FILLED + gamer OR has TableSeat OR has a pending RoomInvite (highlight target only set when seated — pending invites have no visible slot); .post-recipient chips + .gate-slot.filled cells gain data-user-id so the FYI selector can find them; my_buds.html now loads note.js via the {% block scripts %} pattern (Brief module is required by the duplicate banner path); bonus: latent test_jasmine.py bug fixed — "0 failures" in result.text matched "10 failures" / "20 failures" / etc, silently passing up to 99 failed specs; replaced w. re.search(r"(?<!\d)0 failures\b", …) (caught my new red specs, would've caught any prior Jasmine regression); 18 new ITs + 10 new Jasmine specs + 3 new FTs (one per panel) — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-12 16:40:15 -04:00
parent 264ed5968e
commit be919c7aff
19 changed files with 738 additions and 38 deletions

View File

@@ -392,12 +392,16 @@ def share_post(request, post_id):
# list exposes; doesn't widen the privacy surface beyond what the
# post detail page already shows publicly.
recipient_display = None
recipient_user_id = None
if recipient is not None:
recipient_display = recipient.username or recipient.email
recipient_user_id = str(recipient.id)
return JsonResponse({
"brief": brief.to_banner_dict() if brief is not None else None,
"line_text": line_text,
"recipient_display": recipient_display,
"recipient_user_id": recipient_user_id,
"already_present": is_reshare,
})
messages.success(request, "An invite has been sent if that address is registered.")
@@ -446,16 +450,28 @@ def add_bud(request):
candidate = _resolve_recipient(request.POST.get("recipient"))
bud = None
already_present = False
recipient_display = None
recipient_user_id = None
if candidate is not None and candidate != request.user:
if candidate not in request.user.buds.all():
already_present = candidate in request.user.buds.all()
if not already_present:
request.user.buds.add(candidate)
display = candidate.username or candidate.email
bud = {
"id": str(candidate.id),
"username": candidate.username or candidate.email,
"username": display,
"email": candidate.email,
}
recipient_display = display
recipient_user_id = str(candidate.id)
return JsonResponse({"bud": bud})
return JsonResponse({
"bud": bud,
"already_present": already_present,
"recipient_display": recipient_display,
"recipient_user_id": recipient_user_id,
})
@login_required(login_url="/")