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 @@ <li class="applet-list-entry bud-entry" data-bud-id="{{ item.id }}" data-tt-title="{{ item|at_handle }}" - data-tt-description="{{ item.active_title_display }}" + data-tt-description="the {{ item.active_title_display }}" data-tt-email="{{ item.email }}" data-tt-shoptalk="{{ item.shoptalk_text|default:'' }}" {% if item.milestone_dt %}data-tt-milestone="edited {{ item.milestone_dt|relative_ts }}"{% endif %}> 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 @@ <div class="position-strip"> {% for pos in gate_positions %} <div class="gate-slot{% if pos.slot.status == 'EMPTY' %} empty{% elif pos.slot.status == 'FILLED' %} filled{% elif pos.slot.status == 'RESERVED' %} reserved{% endif %}{% if pos.role_assigned %} role-assigned{% endif %}{% if pos.state_class %} {{ pos.state_class }}{% endif %}" - 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="{{ 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 %}> + 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 %}> <span class="slot-number">{{ pos.slot.slot_number }}</span> <span class="slot-gamer">{% if pos.slot.gamer %}{{ pos.slot.gamer|at_handle }}{% else %}empty{% endif %}</span> {% if pos.is_me_also %}