From 6809681e5a4461d4dcf4d9fc574bbea77e73218b Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Tue, 26 May 2026 21:57:45 -0400 Subject: [PATCH] =?UTF-8?q?my=5Fsea=5Fgate=20burger;=20=5Fbud=5Fapparatus?= =?UTF-8?q?=20shared=20shell;=20CI=20#344=20tray-anchor=20fix=20=E2=80=94?= =?UTF-8?q?=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `` 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 Git commit message Co-Authored-By: Claude Sonnet 4.6 --- src/apps/epic/static/apps/epic/tray.js | 22 ++++++---- .../gameboard/tests/integrated/test_views.py | 11 +++++ src/functional_tests/test_core_sharing.py | 2 +- src/static_src/scss/_burger.scss | 17 +++++--- .../billboard/_partials/_bud_add_panel.html | 32 +++----------- .../billboard/_partials/_bud_apparatus.html | 43 +++++++++++++++++++ .../_partials/_bud_invite_panel.html | 35 +++------------ .../apps/billboard/_partials/_bud_panel.html | 39 +++-------------- .../{_room_burger.html => _burger.html} | 0 .../_partials/_my_sea_bud_panel.html | 20 +-------- src/templates/apps/gameboard/my_sea_gate.html | 2 + src/templates/apps/gameboard/room.html | 2 +- 12 files changed, 102 insertions(+), 123 deletions(-) create mode 100644 src/templates/apps/billboard/_partials/_bud_apparatus.html rename src/templates/apps/gameboard/_partials/{_room_burger.html => _burger.html} (100%) diff --git a/src/apps/epic/static/apps/epic/tray.js b/src/apps/epic/static/apps/epic/tray.js index 2d3a2ad..c8abce5 100644 --- a/src/apps/epic/static/apps/epic/tray.js +++ b/src/apps/epic/static/apps/epic/tray.js @@ -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 diff --git a/src/apps/gameboard/tests/integrated/test_views.py b/src/apps/gameboard/tests/integrated/test_views.py index 720d7aa..a27ae5d 100644 --- a/src/apps/gameboard/tests/integrated/test_views.py +++ b/src/apps/gameboard/tests/integrated/test_views.py @@ -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 diff --git a/src/functional_tests/test_core_sharing.py b/src/functional_tests/test_core_sharing.py index 173662c..4f2b662 100644 --- a/src/functional_tests/test_core_sharing.py +++ b/src/functional_tests/test_core_sharing.py @@ -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") diff --git a/src/static_src/scss/_burger.scss b/src/static_src/scss/_burger.scss index a07a9bf..3ff3b3e 100644 --- a/src/static_src/scss/_burger.scss +++ b/src/static_src/scss/_burger.scss @@ -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; + } +} diff --git a/src/templates/apps/billboard/_partials/_bud_add_panel.html b/src/templates/apps/billboard/_partials/_bud_add_panel.html index 93d673e..d05fc8e 100644 --- a/src/templates/apps/billboard/_partials/_bud_add_panel.html +++ b/src/templates/apps/billboard/_partials/_bud_add_panel.html @@ -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. #} -{# ─────────────────────────────────────────────────────────────────────── #} - - - -
- - -
- - +{# _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 %} 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) #} +{# ─────────────────────────────────────────────────────────────────────── #} + + + +
+ + +
+ +{% if include_suggestions %} + + +{% endif %} + + diff --git a/src/templates/apps/billboard/_partials/_bud_invite_panel.html b/src/templates/apps/billboard/_partials/_bud_invite_panel.html index 6046800..4e6621c 100644 --- a/src/templates/apps/billboard/_partials/_bud_invite_panel.html +++ b/src/templates/apps/billboard/_partials/_bud_invite_panel.html @@ -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. #} -{# ─────────────────────────────────────────────────────────────────────── #} - - - -
- - -
- -{# Autocomplete suggestions — sibling because the panel has overflow:hidden #} -{# for the slide-in scaleX animation. Pulls from request.user.buds. #} - - - - +{# _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 %} - +{# _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 %} - +{% include "apps/billboard/_partials/_bud_apparatus.html" with aria_label="Invite a friend" include_suggestions=True %} + {% endblock scripts %} diff --git a/src/templates/apps/gameboard/room.html b/src/templates/apps/gameboard/room.html index ef92801..8d8d68f 100644 --- a/src/templates/apps/gameboard/room.html +++ b/src/templates/apps/gameboard/room.html @@ -119,7 +119,7 @@ {% endif %} {% include "apps/gameboard/_partials/_room_gear.html" %} - {% include "apps/gameboard/_partials/_room_burger.html" %} + {% include "apps/gameboard/_partials/_burger.html" %} {% endblock content %}