From c03fb2bab0e5de6d2e671ac187f86a3824f5bd90 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 12 May 2026 23:14:01 -0400 Subject: [PATCH] =?UTF-8?q?billscroll:=20first=20log=20entry=20on=20a=20fr?= =?UTF-8?q?esh=20room=20is=20a=20system-authored=20`Welcome=20to=20!?= =?UTF-8?q?`=20greeting=20=E2=80=94=20`epic.create=5Froom`=20records=20a?= =?UTF-8?q?=20`ROOM=5FCREATED`=20GameEvent=20(actor=3DNone)=20immediately?= =?UTF-8?q?=20after=20`Room.objects.create(...)`=20so=20every=20room's=20s?= =?UTF-8?q?croll=20opens=20w.=20the=20greeting=20before=20any=20user=20act?= =?UTF-8?q?ion;=20`GameEvent.to=5Fprose`=20ROOM=5FCREATED=20branch=20swapp?= =?UTF-8?q?ed=20from=20the=20unused=20`"opens=20this=20room"`=20legacy=20p?= =?UTF-8?q?rose=20(verb=20was=20declared=20since=20the=20initial=20drama-a?= =?UTF-8?q?pp=20spike=20but=20no=20view=20recorded=20it=20=E2=80=94=20only?= =?UTF-8?q?=20test=20fixtures=20+=20AP=20federation=20tests=20touched=20it?= =?UTF-8?q?)=20=E2=86=92=20`f"Welcome=20to=20{self.room.name}!"`,=20delibe?= =?UTF-8?q?rately=20dropping=20the=20actor=20prefix=20the=20rest=20of=20th?= =?UTF-8?q?e=20verbs=20lead=20w.=20since=20the=20welcome=20is=20the=20room?= =?UTF-8?q?'s=20greeting,=20not=20a=20user=20action;=20scroll=20template?= =?UTF-8?q?=20(`templates/core/=5Fpartials/=5Fscroll.html`=20+=20`template?= =?UTF-8?q?s/apps/billboard/=5Fpartials/=5Fapplet-most-recent-scroll.html`?= =?UTF-8?q?)=20gain=20an=20`event.actor`-guarded=20``=20so=20the?= =?UTF-8?q?=20welcome=20line=20renders=20w.o.=20a=20leading=20empty=20``=20whitespace=20gap,=20and=20the=20`.drama-event?= =?UTF-8?q?`=20class=20branches=20now=20read=20`mine`=20/=20`theirs`=20/?= =?UTF-8?q?=20`system`=20(the=20new=20`system`=20slot=20replaces=20the=20p?= =?UTF-8?q?rior=20`else:=20theirs`=20fallthrough=20when=20actor=20is=20Non?= =?UTF-8?q?e,=20opening=20room=20for=20system-line=20styling=20later=20w.o?= =?UTF-8?q?.=20mis-attributing=20the=20welcome=20to=20a=20phantom=20player?= =?UTF-8?q?);=20RoomCreationViewTest=20gains=20test=5Fcreate=5Froom=5Freco?= =?UTF-8?q?rds=5Fwelcome=5Fevent=5Fwith=5Fno=5Factor=20+=20test=5Fcreate?= =?UTF-8?q?=5Froom=5Fwelcome=5Fevent=5Frenders=5Fwelcome=5Fprose;=20the=20?= =?UTF-8?q?existing=20drama.tests.integrated.test=5Fmodels=20tests=20(test?= =?UTF-8?q?=5Frecord=5Fwithout=5Factor=20+=20test=5Fevents=5Fordered=5Fby?= =?UTF-8?q?=5Ftimestamp)=20already=20exercise=20actor=3DNone=20on=20ROOM?= =?UTF-8?q?=5FCREATED=20+=20the=20chronological-first=20position=20so=20th?= =?UTF-8?q?e=20rendering=20contract=20holds;=20928=20ITs=20green=20?= =?UTF-8?q?=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Opus 4.7 --- src/apps/drama/models.py | 6 ++++- src/apps/epic/tests/integrated/test_views.py | 23 +++++++++++++++++++ src/apps/epic/views.py | 4 ++++ .../_partials/_applet-most-recent-scroll.html | 4 ++-- src/templates/core/_partials/_scroll.html | 4 ++-- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/apps/drama/models.py b/src/apps/drama/models.py index 3631ea9..d50b812 100644 --- a/src/apps/drama/models.py +++ b/src/apps/drama/models.py @@ -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: diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py index 5059ec4..5af5660 100644 --- a/src/apps/epic/tests/integrated/test_views.py +++ b/src/apps/epic/tests/integrated/test_views.py @@ -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 !").""" + 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): diff --git a/src/apps/epic/views.py b/src/apps/epic/views.py index b62a1c6..652b2d4 100644 --- a/src/apps/epic/views.py +++ b/src/apps/epic/views.py @@ -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 !" with no actor prefix. + record(room, GameEvent.ROOM_CREATED) return redirect("epic:gatekeeper", room_id=room.id) return redirect("/gameboard/") diff --git a/src/templates/apps/billboard/_partials/_applet-most-recent-scroll.html b/src/templates/apps/billboard/_partials/_applet-most-recent-scroll.html index dc6a719..de596cc 100644 --- a/src/templates/apps/billboard/_partials/_applet-most-recent-scroll.html +++ b/src/templates/apps/billboard/_partials/_applet-most-recent-scroll.html @@ -9,9 +9,9 @@
Load more…. {% for event in recent_events %} -
+
- {{ event.actor|display_name }} + {% if event.actor %}{{ event.actor|display_name }}{% endif %} {{ event.to_prose|safe }}