gatekeeper invite ports to #id_bud_btn slide-out: drop the inline #id_invite_email form, add bud-invite panel for room owner during gate phase, async POST to invite_gamer w. autocomplete + symmetric buds auto-add + slide-down Brief banner — TDD
- Brief schema (billboard/0007): post FK becomes nullable + new room FK to epic.Room + KIND_GAME_INVITE enum value. to_banner_dict resolves post_url to reverse('epic:gatekeeper', room.id) when post is null and room is set.
- epic.invite_gamer view refactor:
• Accepts `recipient` (matches bud-panel field; legacy `invitee_email` still works for full backwards compat).
• Resolves via apps.billboard.views._resolve_recipient (email if "@" present, else username).
• RoomInvite stores the resolved User's email (or raw input if unregistered).
• Auto-adds inviter ↔ recipient to each others' buds (symmetric per Phase 2 spec) when recipient is a registered User.
• Spawns a Brief w. owner=request.user, kind=GAME_INVITE, room=room, post=null.
• Accept: application/json → {brief, recipient_display}; otherwise redirects to gatekeeper as before.
• Self-invite + blank recipient: 200 w. brief=null, no RoomInvite, no buds touch.
- _gatekeeper.html: gate-invite-panel block (lines 62-71) removed.
- new templates/apps/billboard/_partials/_bud_invite_panel.html: clone of _bud_panel.html w. data-invite-url + autocomplete from request.user.buds. JS posts to invite_gamer + Brief.showBanner. room.html includes it owner-only when not table_status and gate_status != RENEWAL_DUE.
- room.html scripts block now loads apps/dashboard/note.js so window.Brief is defined for the slide-down banner.
- Tests: new test_invite_gamer.py (14 ITs) covering ajax + legacy form-submit paths, recipient resolution, RoomInvite creation, Brief w. room FK + GAME_INVITE kind, symmetric buds auto-add, unregistered/self/blank silent no-op cases. New test_gatekeeper_bud_btn.py FT (9 tests) covers presence (owner-only), absence of legacy #id_invite_email, async invite flow end-to-end (RoomInvite, Brief, banner, panel close, username resolve, buds auto-add).
- test_brief.test_brief_owner_post_required relaxed to test_brief_owner_required (post is now nullable).
- test_room_gatekeeper.test_second_gamer_drops_token_into_open_slot updated to drive the bud-btn flow (drops the #id_invite_email/#id_invite_btn references).
- 866 ITs (+14) + 9 gatekeeper FTs + 28 existing room-gatekeeper FTs green.
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:
@@ -40,14 +40,14 @@ class BriefModelTest(TestCase):
|
||||
b = Brief.objects.create(owner=self.user, post=self.post)
|
||||
self.assertIsNone(b.line)
|
||||
|
||||
def test_brief_owner_post_required(self):
|
||||
"""Brief without owner OR post is invalid; both are the load-bearing
|
||||
FKs (owner = whose attention; post = where FYI navigates)."""
|
||||
def test_brief_owner_required(self):
|
||||
"""Brief without owner is invalid (load-bearing for "whose
|
||||
attention"). Post used to be required too, but became nullable
|
||||
when GAME_INVITE briefs landed (those use Brief.room instead of
|
||||
Brief.post). The view layer enforces "post XOR room" per kind."""
|
||||
from django.db import IntegrityError, transaction
|
||||
with transaction.atomic(), self.assertRaises(IntegrityError):
|
||||
Brief.objects.create(post=self.post, line=self.line)
|
||||
with transaction.atomic(), self.assertRaises(IntegrityError):
|
||||
Brief.objects.create(owner=self.user, line=self.line)
|
||||
|
||||
def test_brief_carries_title(self):
|
||||
b = Brief.objects.create(
|
||||
|
||||
Reference in New Issue
Block a user