diff --git a/src/apps/billboard/tests/integrated/test_views.py b/src/apps/billboard/tests/integrated/test_views.py
index e4c58c5..adc205d 100644
--- a/src/apps/billboard/tests/integrated/test_views.py
+++ b/src/apps/billboard/tests/integrated/test_views.py
@@ -1464,7 +1464,8 @@ class MyBudsRowEnrichmentTest(TestCase):
def test_row_carries_tt_title_description_email_attrs(self):
response = self.client.get(reverse("billboard:my_buds"))
self.assertContains(response, 'data-tt-title="@alice"')
- self.assertContains(response, 'data-tt-description="Earthman"')
+ # Title carries the article — "the Earthman", matching the visible row.
+ self.assertContains(response, 'data-tt-description="the Earthman"')
self.assertContains(response, 'data-tt-email="alice@row.io"')
def test_row_renders_at_handle_the_title(self):
diff --git a/src/apps/epic/tests/integrated/test_views.py b/src/apps/epic/tests/integrated/test_views.py
index b3262a3..11b285e 100644
--- a/src/apps/epic/tests/integrated/test_views.py
+++ b/src/apps/epic/tests/integrated/test_views.py
@@ -675,7 +675,8 @@ class PositionTooltipRenderTest(TestCase):
self.assertIn('data-tt-title="@g2"', slot2)
# No email field in the tooltip payload (user-spec).
self.assertNotIn("data-tt-email", slot2)
- self.assertIn("data-tt-description", slot2)
+ # Title carries the article — "the Earthman", not bare "Earthman".
+ self.assertIn('data-tt-description="the Earthman"', slot2)
def test_bud_occupant_carries_bud_class_and_shoptalk(self):
from apps.billboard.models import BudshipNote
diff --git a/src/functional_tests/test_bill_my_buds.py b/src/functional_tests/test_bill_my_buds.py
index c2baab4..8c4fd32 100644
--- a/src/functional_tests/test_bill_my_buds.py
+++ b/src/functional_tests/test_bill_my_buds.py
@@ -92,7 +92,7 @@ class MyBudsPageTest(FunctionalTest):
self.assertIn("bud-entry", cls)
# data-tt-* attrs the portal reads on row-lock.
self.assertEqual(row.get_attribute("data-tt-title"), "@alice")
- self.assertEqual(row.get_attribute("data-tt-description"), "Earthman")
+ self.assertEqual(row.get_attribute("data-tt-description"), "the Earthman")
self.assertEqual(row.get_attribute("data-tt-email"), "alice@test.io")
# Anchor routes into the bud landing page; trailing ` the
`.
anchor = row.find_element(By.CSS_SELECTOR, ".bud-name a")
@@ -109,7 +109,7 @@ class MyBudsPageTest(FunctionalTest):
portal.find_element(By.CSS_SELECTOR, ".tt-title").text, "@alice"
)
self.assertEqual(
- portal.find_element(By.CSS_SELECTOR, ".tt-description").text, "Earthman"
+ portal.find_element(By.CSS_SELECTOR, ".tt-description").text, "the Earthman"
)
def test_no_autocomplete_suggestions_on_my_buds_page(self):
diff --git a/src/functional_tests/test_bill_my_buds_tooltip.py b/src/functional_tests/test_bill_my_buds_tooltip.py
index 04c321d..6beb1aa 100644
--- a/src/functional_tests/test_bill_my_buds_tooltip.py
+++ b/src/functional_tests/test_bill_my_buds_tooltip.py
@@ -80,7 +80,7 @@ class MyBudsClickOpensTooltipPortalTest(FunctionalTest):
desc = portal.find_element(By.CSS_SELECTOR, ".tt-description").text
email = portal.find_element(By.CSS_SELECTOR, ".tt-email").text
self.assertEqual(title, "@alice")
- self.assertEqual(desc, "Earthman")
+ self.assertEqual(desc, "the Earthman")
self.assertEqual(email, "alice@test.io")
def test_tooltip_milestone_absent_when_shoptalk_never_edited(self):
diff --git a/src/functional_tests/test_game_room_gatekeeper.py b/src/functional_tests/test_game_room_gatekeeper.py
index e4336d9..98bde9b 100644
--- a/src/functional_tests/test_game_room_gatekeeper.py
+++ b/src/functional_tests/test_game_room_gatekeeper.py
@@ -30,6 +30,7 @@ class GatekeeperTest(FunctionalTest):
lambda: self.browser.find_element(By.ID, "id_applet_new_game")
)
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Test Room")
+ self.dismiss_brief_if_present() # @taxman ledger Brief renders over the gameboard top
self.browser.find_element(By.ID, "id_create_game_btn").click()
# 3. User is redirected to Gatekeeper page for new room
self.wait_for(
@@ -61,6 +62,7 @@ class GatekeeperTest(FunctionalTest):
lambda: self.browser.find_element(By.ID, "id_new_game_name")
)
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Dragon's Den")
+ self.dismiss_brief_if_present() # @taxman ledger Brief renders over the gameboard top
self.browser.find_element(By.ID, "id_create_game_btn").click()
self.wait_for(
lambda: self.assertIn("/gate/", self.browser.current_url)
@@ -94,6 +96,7 @@ class GatekeeperTest(FunctionalTest):
lambda: self.browser.find_element(By.ID, "id_new_game_name")
)
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Dragon's Den")
+ self.dismiss_brief_if_present() # @taxman ledger Brief renders over the gameboard top
self.browser.find_element(By.ID, "id_create_game_btn").click()
self.wait_for(
lambda: self.assertIn("/gate/", self.browser.current_url)
@@ -113,6 +116,7 @@ class GatekeeperTest(FunctionalTest):
lambda: self.browser.find_element(By.ID, "id_new_game_name")
)
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Dragon's Den")
+ self.dismiss_brief_if_present() # @taxman ledger Brief renders over the gameboard top
self.browser.find_element(By.ID, "id_create_game_btn").click()
self.wait_for(
lambda: self.assertIn("/gate/", self.browser.current_url)
@@ -187,6 +191,7 @@ class GatekeeperTest(FunctionalTest):
lambda: self.browser.find_element(By.ID, "id_new_game_name")
)
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Dragon's Den")
+ self.dismiss_brief_if_present() # @taxman ledger Brief renders over the gameboard top
self.browser.find_element(By.ID, "id_create_game_btn").click()
self.wait_for(
lambda: self.assertIn("/gate/", self.browser.current_url)
@@ -226,6 +231,7 @@ class GatekeeperTest(FunctionalTest):
self.browser.get(self.live_server_url + "/gameboard/")
self.wait_for(lambda: self.browser.find_element(By.ID, "id_new_game_name"))
self.browser.find_element(By.ID, "id_new_game_name").send_keys("Doomed Room")
+ self.dismiss_brief_if_present() # @taxman ledger Brief renders over the gameboard top
self.browser.find_element(By.ID, "id_create_game_btn").click()
self.wait_for(lambda: self.assertIn("/gate/", self.browser.current_url))
diff --git a/src/functional_tests/test_game_room_select_role.py b/src/functional_tests/test_game_room_select_role.py
index 3d4c329..3543a35 100644
--- a/src/functional_tests/test_game_room_select_role.py
+++ b/src/functional_tests/test_game_room_select_role.py
@@ -36,6 +36,7 @@ class RoleSelectTest(FunctionalTest):
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_new_game_name")
).send_keys("Dragon's Den")
+ self.dismiss_brief_if_present() # @taxman ledger Brief renders over the gameboard top
self.browser.find_element(By.ID, "id_create_game_btn").click()
self.wait_for(
lambda: self.assertIn("/gate/", self.browser.current_url)
diff --git a/src/static_src/scss/_room.scss b/src/static_src/scss/_room.scss
index bd570cf..689dfad 100644
--- a/src/static_src/scss/_room.scss
+++ b/src/static_src/scss/_room.scss
@@ -359,6 +359,17 @@ html:has(.role-select-backdrop) .position-strip .gate-slot { pointer-events: non
// Re-enable clicks on confirm/reject/drop-token forms inside slots
html:has(.gate-backdrop) .position-strip .gate-slot form,
html:has(.gate-backdrop) .position-strip .gate-slot button { pointer-events: auto; }
+// Occupied circles must stay HOVERABLE under the gatekeeper / role-select
+// backdrop so their rich position tooltips fire. A filled/reserved circle has
+// no click action of its own (only the OK/NVM/drop forms do, re-enabled
+// above) — but mouseenter only reaches a pointer-events:none `.gate-slot` when
+// something inside it IS hit-testable, so a filled circle lacking a button
+// silently produced no tooltip on the initial gatekeeper. Empty circles stay
+// suppressed (they carry no tooltip). (0,4,1) beats the (0,3,1) suppressor.
+html:has(.gate-backdrop) .position-strip .gate-slot.filled,
+html:has(.gate-backdrop) .position-strip .gate-slot.reserved,
+html:has(.role-select-backdrop) .position-strip .gate-slot.filled,
+html:has(.role-select-backdrop) .position-strip .gate-slot.reserved { pointer-events: auto; }
// The room-gate renewal modal renders its OWN .gate-backdrop, but its
// position circles are hover-only (tooltips) and must stay live — re-enable
// them. The doubled `.room-gate-page` makes this (0,4,1) so it UNAMBIGUOUSLY
diff --git a/src/templates/apps/billboard/_partials/_bud_add_panel.html b/src/templates/apps/billboard/_partials/_bud_add_panel.html
index 668768e..cb8e2f8 100644
--- a/src/templates/apps/billboard/_partials/_bud_add_panel.html
+++ b/src/templates/apps/billboard/_partials/_bud_add_panel.html
@@ -37,7 +37,11 @@
li.className = 'applet-list-entry bud-entry';
li.dataset.budId = bud.id;
li.dataset.ttTitle = handle;
- li.dataset.ttDescription = title;
+ // Carry the article — "the Earthman" — to mirror _my_buds_item.html's
+ // `data-tt-description="the {{ active_title_display }}"` (and the
+ // visible `.bud-row-title` below), so the async row's tooltip matches
+ // the server-rendered rows.
+ li.dataset.ttDescription = 'the ' + title;
li.dataset.ttEmail = bud.email || '';
li.dataset.ttShoptalk = ''; // fresh bud — no BudshipNote yet
diff --git a/src/templates/apps/billboard/_partials/_my_buds_item.html b/src/templates/apps/billboard/_partials/_my_buds_item.html
index e3a5f65..e1d8bea 100644
--- a/src/templates/apps/billboard/_partials/_my_buds_item.html
+++ b/src/templates/apps/billboard/_partials/_my_buds_item.html
@@ -6,7 +6,7 @@
diff --git a/src/templates/apps/gameboard/_partials/_table_positions.html b/src/templates/apps/gameboard/_partials/_table_positions.html
index a5e723a..0b0ef5f 100644
--- a/src/templates/apps/gameboard/_partials/_table_positions.html
+++ b/src/templates/apps/gameboard/_partials/_table_positions.html
@@ -9,7 +9,7 @@
{% for pos in gate_positions %}
+ data-slot="{{ pos.slot.slot_number }}"{% if pos.slot.gamer %} data-user-id="{{ pos.slot.gamer.id }}" data-tt-title="{{ pos.slot.gamer|at_handle }}" data-tt-description="the {{ pos.slot.gamer.active_title_display }}" data-tt-shoptalk="{{ pos.shoptalk }}" data-tt-tokens="{{ pos.tokens }}"{% if pos.expiry %} data-tt-expiry="{{ pos.expiry|date:'c' }}"{% endif %}{% if pos.sign_rank %} data-tt-sign-rank="{{ pos.sign_rank }}" data-tt-sign-suit="{{ pos.sign_suit_icon }}"{% endif %}{% endif %}>
{{ pos.slot.slot_number }}
{% if pos.slot.gamer %}{{ pos.slot.gamer|at_handle }}{% else %}empty{% endif %}
{% if pos.is_me_also %}