my_sea_gate burger; _bud_apparatus shared shell; CI #344 tray-anchor fix — TDD
Continuation of the burger sprint into the my-sea gatekeeper + a couple companion cleanups the visual + CI runs surfaced.
## my_sea_gate.html burger
`templates/apps/gameboard/_partials/_room_burger.html` → `_burger.html` (git mv) — now lives at a non-room-scoped path since it's reused across templates. Updated room.html's include path.
`templates/apps/gameboard/my_sea_gate.html` — includes `_burger.html` + loads `burger-btn.js`. Burger renders unconditionally on the my-sea gatekeeper, same affordance as the room gatekeeper.
`apps/gameboard/tests/integrated/test_views.py::MySeaGateViewTest` — new `test_gate_view_renders_burger_btn_and_fan` IT asserts burger_btn + burger_fan + 5 sub-btns w. correct ids + burger-btn.js loaded on `/gameboard/my-sea/gate/`.
## Burger fade rule reinstated
`static_src/scss/_burger.scss` — `html.bud-open #id_burger_btn { opacity: 0; pointer-events: none; }` reinstated, scoped to landscape only. User-confirmed: even after the z-index drop the burger needs to disappear when bud_panel is open in landscape (panel + bud_ok races vs. burger pointer-events). Portrait keeps burger visible since the panel sits BELOW the burger w. no overlap.
## "friend@example.com" → "bud@example.com" placeholder
Renamed in 4 bud-panel templates + the FT asserting it:
- `templates/apps/gameboard/_partials/_my_sea_bud_panel.html`
- `templates/apps/billboard/_partials/_bud_panel.html`
- `templates/apps/billboard/_partials/_bud_invite_panel.html`
- `templates/apps/billboard/_partials/_bud_add_panel.html`
- `functional_tests/test_core_sharing.py`
"bud" matches the broader naming convention (bud_btn, my-buds, share-w.-a-bud, etc.) — `friend` was an outlier.
## _bud_apparatus.html shared shell refactor
New `templates/apps/billboard/_partials/_bud_apparatus.html` — single shared markup partial for the four bud-btn use cases. Contains btn + panel + input + OK + (optional) suggestions div + bud-btn.js script. Each of the 4 specific partials becomes a thin wrapper that `{% include %}`s the shell + renders its own `<script>bindBudBtn({...})</script>` block w. per-use-case submitUrl / onSuccess / duplicateTargetSelector.
Context vars accepted by the shell:
- `aria_label` — string for #id_bud_btn aria-label
- `sharer_name` — optional; renders `data-sharer-name=` on #id_bud_panel (post-share only)
- `include_suggestions` — bool; renders suggestions div + autocomplete script (false on my_buds where the pool == request.user.buds == nothing useful to suggest)
Per-call wrappers are now ~10-50 lines instead of 30-120 lines of duplicate markup. Behaviour is identical; only DRY-ed up.
## CI #344 tray-anchor regression fix
`apps/epic/static/apps/epic/tray.js` — `_computeBounds` in landscape used `id_gear_btn || id_kit_btn` as the bottom anchor (wrap height = anchor.top). After the burger sprint relocated kit_btn to the TOP of the right sidebar (top:0.5rem), kit_btn.top ≈ 8px → wrap.height collapsed to 8px → tray couldn't slide → `test_dragging_tray_btn_down_opens_tray_in_landscape` failed.
Fix: anchor fallback chain is now `id_burger_btn || id_bud_btn` (the new bottom-anchored btns) → `window.innerHeight - 3.5rem` reserved fallback (for pages that have neither). Burger renders unconditionally on room.html so the SIG_SELECT tray test now finds its anchor + lays out the wrap correctly.
## Verification
- IT+UT 1357 green (+1 from MySeaGateViewTest burger).
- Jasmine specs green.
- `test_dragging_tray_btn_down_opens_tray_in_landscape` green (was the CI #344 failure).
Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -100,14 +100,20 @@ var Tray = (function () {
|
||||
// meets the gear button when open. Tray is flex:1 and fills the rest.
|
||||
// Open: wrap top = 0 (pinned to viewport top).
|
||||
// Closed: wrap top = -(gearBtnTop - handleH) = tray fully above viewport.
|
||||
// Anchor: id_gear_btn historically; id_kit_btn is the live fallback so
|
||||
// the open-state handle bottom lands at the bottom-right anchor instead
|
||||
// of overlapping it (no id_gear_btn renders on the room page today).
|
||||
var gearBtn = document.getElementById('id_gear_btn')
|
||||
|| document.getElementById('id_kit_btn');
|
||||
var gearBtnTop = window.innerHeight;
|
||||
if (gearBtn) {
|
||||
gearBtnTop = Math.round(gearBtn.getBoundingClientRect().top);
|
||||
// Anchor: the bottom-most btn on the right sidebar in landscape.
|
||||
// After the burger sprint, kit_btn + gear_btn relocated to the TOP
|
||||
// of the sidebar — so using them as anchors collapses wrap height
|
||||
// to ~8px. Burger (room.html) + bud (post.html) are now the only
|
||||
// btns reliably anchored at the BOTTOM. Fall back to a fixed
|
||||
// reserved 3.5rem if neither is present on the page.
|
||||
var anchor = document.getElementById('id_burger_btn')
|
||||
|| document.getElementById('id_bud_btn');
|
||||
var gearBtnTop;
|
||||
if (anchor) {
|
||||
gearBtnTop = Math.round(anchor.getBoundingClientRect().top);
|
||||
} else {
|
||||
var rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
||||
gearBtnTop = window.innerHeight - 3.5 * rem;
|
||||
}
|
||||
// handleH = #id_tray_handle's height (48px CSS), NOT _btn.offsetHeight.
|
||||
// The 3rem btn can be larger than the 48px handle on big-rem viewports
|
||||
|
||||
@@ -2154,6 +2154,17 @@ class MySeaGateViewTest(TestCase):
|
||||
self.assertContains(response, "id_my_sea_paid_draw_btn")
|
||||
self.assertContains(response, reverse("my_sea_refund_token"))
|
||||
|
||||
def test_gate_view_renders_burger_btn_and_fan(self):
|
||||
response = self.client.get(reverse("my_sea_gate"))
|
||||
self.assertContains(response, 'id="id_burger_btn"')
|
||||
self.assertContains(response, 'id="id_burger_fan"')
|
||||
for btn_id in (
|
||||
"id_voice_btn", "id_sky_btn", "id_earth_btn",
|
||||
"id_sea_btn", "id_text_btn",
|
||||
):
|
||||
self.assertContains(response, f'id="{btn_id}"')
|
||||
self.assertContains(response, "burger-btn.js")
|
||||
|
||||
|
||||
class MySeaInsertTokenViewTest(TestCase):
|
||||
"""Sprint 6 iter 6a — POST `/gameboard/my-sea/insert` reserves the
|
||||
|
||||
@@ -41,7 +41,7 @@ class SharingTest(FunctionalTest):
|
||||
share_box = post_page.get_share_box()
|
||||
self.assertEqual(
|
||||
share_box.get_attribute("placeholder"),
|
||||
"friend@example.com or username",
|
||||
"bud@example.com or username",
|
||||
)
|
||||
|
||||
post_page.share_post_with("alice@test.io")
|
||||
|
||||
@@ -123,8 +123,15 @@
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
// No explicit bud_panel / kit_dialog hide rules needed — burger's z=314
|
||||
// sits BELOW kit_btn / bud_btn (318), bud_panel (317), + kit_dialog (316),
|
||||
// so all those naturally cover the burger when they overlap. Earlier
|
||||
// iterations explicitly faded the burger via opacity; rendered redundant
|
||||
// by the z-index drop.
|
||||
// Burger hides when bud_panel is open — LANDSCAPE only. In portrait the
|
||||
// burger sits ABOVE the bud panel (bottom:4.2rem vs panel at bottom:0.5
|
||||
// + height:3rem); no visual conflict. In landscape they share the
|
||||
// bottom-right area + the bud OK btn ends up obscured / pointer-events
|
||||
// races against the burger even at the lower z-index — explicit fade
|
||||
// keeps the bud_panel apparatus clean.
|
||||
@media (orientation: landscape) {
|
||||
html.bud-open #id_burger_btn {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,8 @@
|
||||
{% load static %}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
{# _bud_add_panel.html — bottom-left handshake btn + slide-out add-bud #}
|
||||
{# field. Skeleton (open/close/POST) owned by bud-btn.js; this partial #}
|
||||
{# wires the add-bud success callback: {bud} → append .bud-entry to #}
|
||||
{# #id_buds_list. #}
|
||||
{# Included by my_buds.html only. #}
|
||||
{# #}
|
||||
{# No autocomplete — the bud-autocomplete pool is request.user.buds, #}
|
||||
{# which is precisely the set you can't usefully re-add. Post-share + #}
|
||||
{# gatekeeper-invite panels DO bind autocomplete. #}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
|
||||
<button id="id_bud_btn" type="button" aria-label="Add a bud">
|
||||
<i class="fa-solid fa-handshake"></i>
|
||||
</button>
|
||||
|
||||
<div id="id_bud_panel">
|
||||
<input id="id_recipient"
|
||||
name="recipient"
|
||||
type="text"
|
||||
placeholder="friend@example.com or username"
|
||||
autocomplete="off">
|
||||
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'apps/billboard/bud-btn.js' %}"></script>
|
||||
{# _bud_add_panel.html — handshake btn + slide-out add-bud field on #}
|
||||
{# my_buds.html. No autocomplete — the pool is request.user.buds, which #}
|
||||
{# is precisely the set you can't usefully re-add. On success, appends #}
|
||||
{# a .bud-entry to #id_buds_list. #}
|
||||
{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Add a bud" include_suggestions=False %}
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
43
src/templates/apps/billboard/_partials/_bud_apparatus.html
Normal file
43
src/templates/apps/billboard/_partials/_bud_apparatus.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% load static %}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
{# _bud_apparatus.html — shared markup for the four bud-btn use cases: #}
|
||||
{# • _my_sea_bud_panel.html (my-sea gatekeeper invite stub) #}
|
||||
{# • _bud_panel.html (post.html share-post) #}
|
||||
{# • _bud_invite_panel.html (room.html gatekeeper game-invite) #}
|
||||
{# • _bud_add_panel.html (my_buds.html add-bud) #}
|
||||
{# #}
|
||||
{# Each caller follows the include w. its own #}
|
||||
{# <script>bindBudBtn({...})</script> block carrying the per-use-case #}
|
||||
{# submitUrl + onSuccess + (optional) duplicateTargetSelector. The shell #}
|
||||
{# loads bud-btn.js + autocomplete (when `include_suggestions` is true) #}
|
||||
{# so the caller can call bindBudBtn() inline. #}
|
||||
{# #}
|
||||
{# Context vars: #}
|
||||
{# aria_label — string for #id_bud_btn aria-label #}
|
||||
{# sharer_name — optional; renders data-sharer-name="..." on #}
|
||||
{# #id_bud_panel (post-share only) #}
|
||||
{# include_suggestions — bool; renders #id_bud_suggestions + autocompete #}
|
||||
{# script (false only for my_buds, where the #}
|
||||
{# autocomplete pool == request.user.buds, which #}
|
||||
{# is precisely what you can't usefully re-add) #}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
|
||||
<button id="id_bud_btn" type="button" aria-label="{{ aria_label|default:'Bud' }}">
|
||||
<i class="fa-solid fa-handshake"></i>
|
||||
</button>
|
||||
|
||||
<div id="id_bud_panel"{% if sharer_name %} data-sharer-name="{{ sharer_name }}"{% endif %}>
|
||||
<input id="id_recipient"
|
||||
name="recipient"
|
||||
type="text"
|
||||
placeholder="bud@example.com or username"
|
||||
autocomplete="off">
|
||||
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
|
||||
</div>
|
||||
|
||||
{% if include_suggestions %}
|
||||
<div id="id_bud_suggestions" class="bud-suggestions" hidden></div>
|
||||
<script src="{% static 'apps/billboard/bud-autocomplete.js' %}"></script>
|
||||
{% endif %}
|
||||
|
||||
<script src="{% static 'apps/billboard/bud-btn.js' %}"></script>
|
||||
@@ -1,33 +1,8 @@
|
||||
{% load static %}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
{# _bud_invite_panel.html — bud btn + slide-out for the gatekeeper game- #}
|
||||
{# invite flow. Skeleton (open/close/POST + autocomplete) owned by #}
|
||||
{# bud-btn.js; this partial wires the invite success callback: server #}
|
||||
{# returns {brief, recipient_display} — no line_text (no Post to append a #}
|
||||
{# Line to). JS just shows the Brief banner. #}
|
||||
{# #}
|
||||
{# Caller must pass `room` in context. #}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
|
||||
<button id="id_bud_btn" type="button" aria-label="Invite a friend">
|
||||
<i class="fa-solid fa-handshake"></i>
|
||||
</button>
|
||||
|
||||
<div id="id_bud_panel">
|
||||
<input id="id_recipient"
|
||||
name="recipient"
|
||||
type="text"
|
||||
placeholder="friend@example.com or username"
|
||||
autocomplete="off">
|
||||
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
|
||||
</div>
|
||||
|
||||
{# Autocomplete suggestions — sibling because the panel has overflow:hidden #}
|
||||
{# for the slide-in scaleX animation. Pulls from request.user.buds. #}
|
||||
<div id="id_bud_suggestions" class="bud-suggestions" hidden></div>
|
||||
|
||||
<script src="{% static 'apps/billboard/bud-autocomplete.js' %}"></script>
|
||||
<script src="{% static 'apps/billboard/bud-btn.js' %}"></script>
|
||||
{# _bud_invite_panel.html — bud btn + slide-out for the gatekeeper #}
|
||||
{# game-invite flow on room.html. Server returns {brief, #}
|
||||
{# recipient_display} — no line_text (no Post to append a Line to). JS #}
|
||||
{# just shows the Brief banner. Caller must pass `room` in context. #}
|
||||
{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Invite a friend" include_suggestions=True %}
|
||||
<script>
|
||||
bindBudBtn({
|
||||
submitUrl: '{% url "epic:invite_gamer" room.id %}',
|
||||
|
||||
@@ -1,36 +1,11 @@
|
||||
{% load static %}
|
||||
{% load lyric_extras %}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
{# _bud_panel.html — bottom-left handshake btn + slide-out recipient #}
|
||||
{# field for the share-post async flow. Skeleton (open/close/POST + auto- #}
|
||||
{# complete) owned by bud-btn.js; this partial wires the share-post #}
|
||||
{# success callback: appends a Line to #id_post_table, fires Brief.show- #}
|
||||
{# Banner, and updates the .post-header shared-with prose. #}
|
||||
{# Included by post.html only. #}
|
||||
{# #}
|
||||
{# Spec lives in functional_tests/test_bud_btn.py. #}
|
||||
{# ─────────────────────────────────────────────────────────────────────── #}
|
||||
|
||||
<button id="id_bud_btn" type="button" aria-label="Share with a bud">
|
||||
<i class="fa-solid fa-handshake"></i>
|
||||
</button>
|
||||
|
||||
<div id="id_bud_panel"
|
||||
data-sharer-name="{% if request.user.is_authenticated %}{{ request.user|at_handle }}{% endif %}">
|
||||
<input id="id_recipient"
|
||||
name="recipient"
|
||||
type="text"
|
||||
placeholder="friend@example.com or username"
|
||||
autocomplete="off">
|
||||
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
|
||||
</div>
|
||||
|
||||
{# Autocomplete suggestions — sibling of #id_bud_panel because the panel #}
|
||||
{# has overflow:hidden for its scaleX slide animation. #}
|
||||
<div id="id_bud_suggestions" class="bud-suggestions" hidden></div>
|
||||
|
||||
<script src="{% static 'apps/billboard/bud-autocomplete.js' %}"></script>
|
||||
<script src="{% static 'apps/billboard/bud-btn.js' %}"></script>
|
||||
{# _bud_panel.html — bud btn + slide-out for the post-share flow on #}
|
||||
{# post.html. On success: appends a Line to #id_post_table, fires #}
|
||||
{# Brief.showBanner, + updates the .post-header shared-with prose. #}
|
||||
{# `sharer_name` (rendered onto data-sharer-name) is the author of the #}
|
||||
{# optimistically-appended Line so it matches the persisted post-refresh #}
|
||||
{# state where Line.author == request.user. #}
|
||||
{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Share with a bud" sharer_name=request.user|at_handle include_suggestions=True %}
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
@@ -1,26 +1,8 @@
|
||||
{% load static %}
|
||||
{# Sprint 6 iter 6c — bud-btn invite panel on the my-sea gatekeeper. #}
|
||||
{# Mirrors `_bud_invite_panel.html` (room) but POSTs to a stub view — #}
|
||||
{# real async multi-user invite is deferred to a future sprint, so OK #}
|
||||
{# returns a 'Multiplayer my-sea coming soon' Brief banner. #}
|
||||
|
||||
<button id="id_bud_btn" type="button" aria-label="Invite a friend">
|
||||
<i class="fa-solid fa-handshake"></i>
|
||||
</button>
|
||||
|
||||
<div id="id_bud_panel">
|
||||
<input id="id_recipient"
|
||||
name="recipient"
|
||||
type="text"
|
||||
placeholder="friend@example.com or username"
|
||||
autocomplete="off">
|
||||
<button id="id_bud_ok" type="button" class="btn btn-confirm">OK</button>
|
||||
</div>
|
||||
|
||||
<div id="id_bud_suggestions" class="bud-suggestions" hidden></div>
|
||||
|
||||
<script src="{% static 'apps/billboard/bud-autocomplete.js' %}"></script>
|
||||
<script src="{% static 'apps/billboard/bud-btn.js' %}"></script>
|
||||
{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Invite a friend" include_suggestions=True %}
|
||||
<script>
|
||||
bindBudBtn({
|
||||
submitUrl: '{% url "my_sea_invite" %}',
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
{# the gear-btn sits atop `#id_kit_btn` in the bottom-right corner. #}
|
||||
{% include "apps/gameboard/_partials/_my_sea_bud_panel.html" %}
|
||||
{% include "apps/gameboard/_partials/_my_sea_gear.html" %}
|
||||
{% include "apps/gameboard/_partials/_burger.html" %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -97,4 +98,5 @@
|
||||
{# 'Multiplayer my-sea coming soon' banner shows on a successful #}
|
||||
{# (stub) invite. #}
|
||||
<script src="{% static 'apps/dashboard/note.js' %}"></script>
|
||||
<script src="{% static 'apps/epic/burger-btn.js' %}"></script>
|
||||
{% endblock scripts %}
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<div id="id_tooltip_portal" class="tt" style="display:none;"></div>
|
||||
{% endif %}
|
||||
{% include "apps/gameboard/_partials/_room_gear.html" %}
|
||||
{% include "apps/gameboard/_partials/_room_burger.html" %}
|
||||
{% include "apps/gameboard/_partials/_burger.html" %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user