billscroll: first log entry on a fresh room is a system-authored Welcome to <name>! greeting — epic.create_room records a ROOM_CREATED GameEvent (actor=None) immediately after Room.objects.create(...) so every room's scroll opens w. the greeting before any user action; GameEvent.to_prose ROOM_CREATED branch swapped from the unused "opens this room" legacy prose (verb was declared since the initial drama-app spike but no view recorded it — only test fixtures + AP federation tests touched it) → f"Welcome to {self.room.name}!", deliberately dropping the actor prefix the rest of the verbs lead w. since the welcome is the room's greeting, not a user action; scroll template (templates/core/_partials/_scroll.html + templates/apps/billboard/_partials/_applet-most-recent-scroll.html) gain an event.actor-guarded <strong> so the welcome line renders w.o. a leading empty <strong></strong> whitespace gap, and the .drama-event class branches now read mine / theirs / system (the new system slot replaces the prior else: theirs fallthrough when actor is None, opening room for system-line styling later w.o. mis-attributing the welcome to a phantom player); RoomCreationViewTest gains test_create_room_records_welcome_event_with_no_actor + test_create_room_welcome_event_renders_welcome_prose; the existing drama.tests.integrated.test_models tests (test_record_without_actor + test_events_ordered_by_timestamp) already exercise actor=None on ROOM_CREATED + the chronological-first position so the rendering contract holds; 928 ITs green — 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:
@@ -76,7 +76,11 @@ class GameEvent(models.Model):
|
||||
if self.verb == self.SLOT_RELEASED:
|
||||
return f"releases slot {d.get('slot_number', '?')}"
|
||||
if self.verb == self.ROOM_CREATED:
|
||||
return "opens this room"
|
||||
# First scroll log on a fresh room — system-authored greeting
|
||||
# (actor=None upstream). Format intentionally drops the actor
|
||||
# prefix the rest of the verbs use; the room's title is what
|
||||
# the welcome line celebrates.
|
||||
return f"Welcome to {self.room.name}!"
|
||||
if self.verb == self.INVITE_SENT:
|
||||
return "sends an invitation"
|
||||
if self.verb == self.ROLE_SELECT_STARTED:
|
||||
|
||||
@@ -41,6 +41,29 @@ class RoomCreationViewTest(TestCase):
|
||||
response = self.client.get(reverse("epic:create_room"))
|
||||
self.assertRedirects(response, "/gameboard/")
|
||||
|
||||
def test_create_room_records_welcome_event_with_no_actor(self):
|
||||
"""First scroll log on a fresh room is a system-authored welcome —
|
||||
not a user action, so actor=None. The visible greeting is the
|
||||
ROOM_CREATED event's `to_prose` ("Welcome to <name>!")."""
|
||||
self.client.post(
|
||||
reverse("epic:create_room"),
|
||||
data={"name": "Welcoming Room"},
|
||||
)
|
||||
room = Room.objects.get(owner=self.user)
|
||||
event = room.events.first()
|
||||
self.assertIsNotNone(event, "no ROOM_CREATED event recorded")
|
||||
self.assertEqual(event.verb, GameEvent.ROOM_CREATED)
|
||||
self.assertIsNone(event.actor, "welcome line must be system-authored")
|
||||
|
||||
def test_create_room_welcome_event_renders_welcome_prose(self):
|
||||
self.client.post(
|
||||
reverse("epic:create_room"),
|
||||
data={"name": "Greenroom"},
|
||||
)
|
||||
room = Room.objects.get(owner=self.user)
|
||||
event = room.events.first()
|
||||
self.assertEqual(event.to_prose(), "Welcome to Greenroom!")
|
||||
|
||||
|
||||
class MyGamesContextTest(TestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -390,6 +390,10 @@ def create_room(request):
|
||||
name = request.POST.get("name", "").strip()
|
||||
if name:
|
||||
room = Room.objects.create(name=name, owner=request.user)
|
||||
# System-authored welcome (actor=None) — first scroll log
|
||||
# on every room. Renders via GameEvent.to_prose as
|
||||
# "Welcome to <name>!" with no actor prefix.
|
||||
record(room, GameEvent.ROOM_CREATED)
|
||||
return redirect("epic:gatekeeper", room_id=room.id)
|
||||
return redirect("/gameboard/")
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<section id="id_drama_scroll" class="drama-scroll">
|
||||
<a href="{% url 'billboard:scroll' recent_room.id %}" class="most-recent-load-more">Load more….</a>
|
||||
{% for event in recent_events %}
|
||||
<div class="drama-event {% if event.actor == viewer %}mine{% else %}theirs{% endif %}">
|
||||
<div class="drama-event {% if event.actor == viewer %}mine{% elif event.actor %}theirs{% else %}system{% endif %}">
|
||||
<span class="drama-event-body{% if event.struck %} struck{% endif %}">
|
||||
<strong>{{ event.actor|display_name }}</strong>
|
||||
{% if event.actor %}<strong>{{ event.actor|display_name }}</strong>{% endif %}
|
||||
{{ event.to_prose|safe }}
|
||||
</span>
|
||||
<time class="drama-event-time" datetime="{{ event.timestamp|date:'c' }}">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% load lyric_extras %}
|
||||
<section id="id_drama_scroll" class="drama-scroll" data-scroll-position="{{ scroll_position|default:0 }}">
|
||||
{% for event in events %}
|
||||
<div class="drama-event {% if event.actor == viewer %}mine{% else %}theirs{% endif %}" data-label="{% if event.struck %}redact{% else %}frame{% endif %}">
|
||||
<div class="drama-event {% if event.actor == viewer %}mine{% elif event.actor %}theirs{% else %}system{% endif %}" data-label="{% if event.struck %}redact{% else %}frame{% endif %}">
|
||||
<span class="drama-event-body{% if event.struck %} struck{% endif %}">
|
||||
<strong>{{ event.actor|display_name }}</strong>
|
||||
{% if event.actor %}<strong>{{ event.actor|display_name }}</strong>{% endif %}
|
||||
{{ event.to_prose|safe }}
|
||||
</span>
|
||||
<time class="drama-event-time" datetime="{{ event.timestamp|date:'c' }}">
|
||||
|
||||
Reference in New Issue
Block a user