From 91f48384ff40538a1262a7aedb47ba896b52f3cf Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Mon, 1 Jun 2026 21:06:51 -0400 Subject: [PATCH] =?UTF-8?q?post=20composer:=20OK=20btn=20+=20orientation-a?= =?UTF-8?q?ware=20right=20clamp;=20#id=5Fpost=5Fline=5Ftext=20styling=20?= =?UTF-8?q?=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Billpost composer (#id_post_line_text) now matches the other composers and prepares the pattern for room.html. 1. OK .btn-confirm (#id_post_line_btn) added to the editable post-line form, in a flex .composer-row beside the input (read-only system Posts — note_unlock / tax_ledger / mail_acceptance — invite no response, so get no OK). #id_post_line_text also joins the composer-input styling selector in _applets.scss (700 weight + duoUser fill + terUser focus + priUser placeholder). 2. Orientation-aware right clamp on .post-line-form so the OK btn clears the bottom-right corner button: - portrait: margin-right 3.5rem — clears the gear (.post-page > .gear-btn, right:0.5rem, 3rem wide → 3.5rem slot). - landscape: margin-right 7.2rem — clears the burger slot (#id_burger_btn lands at right:4.2rem, 3rem wide → its left edge sits 7.2rem from the viewport's right edge; this also clears the bud at right:0.5rem). post.html carries no burger (room.html will) — the slot is reserved for parity. .composer-row is flex (input flex:1 + OK); the read-only input keeps width:100%. Bundled: rootvars --terPer felt retune (82,71,138). Tests: PostViewTest ITs (editable post renders the OK .btn-confirm in .composer-row; read-only system post does not); functional_tests/test_bill_post_composer FTs (portrait OK right edge <= gear left edge; landscape OK right edge >= 7.2rem in from the viewport edge). 12 PostViewTest ITs + 442 dashboard/billboard ITs + 2 composer FTs + test_bill_new_post FT (ENTER-submit through the composer-row) green. [[project-room-scroll-of-events]] Co-Authored-By: Claude Opus 4.8 (1M context) --- .../dashboard/tests/integrated/test_views.py | 27 ++++++++++ .../test_bill_post_composer.py | 54 +++++++++++++++++++ src/static_src/scss/_applets.scss | 13 ++--- src/static_src/scss/_billboard.scss | 22 ++++++++ src/static_src/scss/rootvars.scss | 4 +- src/templates/apps/billboard/post.html | 21 ++++---- 6 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 src/functional_tests/test_bill_post_composer.py diff --git a/src/apps/dashboard/tests/integrated/test_views.py b/src/apps/dashboard/tests/integrated/test_views.py index 38b771d..93e4076 100644 --- a/src/apps/dashboard/tests/integrated/test_views.py +++ b/src/apps/dashboard/tests/integrated/test_views.py @@ -115,6 +115,33 @@ class PostViewTest(TestCase): inputs = form.cssselect("input") self.assertIn("text", [input.get("name") for input in inputs]) + def test_editable_post_composer_has_ok_confirm_button(self): + """The post-line composer gains an OK .btn-confirm beside + #id_post_line_text (in a flex .composer-row), like the other + composers.""" + mypost = Post.objects.create() + response = self.client.get(f"/billboard/post/{mypost.id}/") + parsed = lxml.html.fromstring(response.content) + [form] = parsed.cssselect("form.post-line-form") + buttons = form.cssselect("button.btn-confirm") + self.assertTrue(buttons, "no OK .btn-confirm beside #id_post_line_text") + self.assertEqual(buttons[0].text_content().strip(), "OK") + # input + OK ride together in the flex composer-row + self.assertTrue(form.cssselect(".composer-row #id_post_line_text")) + self.assertTrue(form.cssselect(".composer-row button.btn-confirm")) + + def test_readonly_system_post_composer_has_no_ok_button(self): + """Read-only system Posts (note-unlock / tax-ledger / mail) invite no + response, so they get no OK button.""" + mypost = Post.objects.create(kind=Post.KIND_NOTE_UNLOCK) + response = self.client.get(f"/billboard/post/{mypost.id}/") + parsed = lxml.html.fromstring(response.content) + [form] = parsed.cssselect("form.post-line-form") + self.assertFalse( + form.cssselect("button.btn-confirm"), + "read-only system post must not invite a response", + ) + def test_displays_only_lines_for_that_post(self): # Given/Arrange correct_post = Post.objects.create() diff --git a/src/functional_tests/test_bill_post_composer.py b/src/functional_tests/test_bill_post_composer.py new file mode 100644 index 0000000..68d08b1 --- /dev/null +++ b/src/functional_tests/test_bill_post_composer.py @@ -0,0 +1,54 @@ +"""Post-composer OK-button + right-edge clamp FTs. + +The #id_post_line_text composer at the bottom of a Billpost gains an OK +.btn-confirm to its right. The composer's right boundary is clamped so the OK +button clears the bottom-right corner button in each orientation: + • portrait — the gear (.post-page > .gear-btn, right:0.5rem, 3rem wide), + • landscape — the burger slot (#id_burger_btn lands at right:4.2rem, 3rem + wide → its left edge is 7.2rem from the viewport's right edge; clearing it + also clears the bud at right:0.5rem). post.html itself carries no burger + (room.html will), so the landscape test asserts the OK edge clears that + 7.2rem slot rather than a live element. + +FT bucket: bill. +""" +from selenium.webdriver.common.by import By + +from .base import FunctionalTest +from .post_page import PostPage + + +class PostComposerOkButtonTest(FunctionalTest): + def _open_post_page(self): + # Adding a line from /billboard/ navigates to the fresh post's page, + # where the editable #id_post_line_text composer renders. + self.create_pre_authenticated_session("disco@test.io") + self.browser.get(self.live_server_url + "/billboard/") + PostPage(self).add_post_line("How does this look") + return self.wait_for(lambda: self.browser.find_element( + By.CSS_SELECTOR, ".post-line-form button.btn-confirm")) + + def _rect(self, el, prop): + return self.browser.execute_script( + f"return arguments[0].getBoundingClientRect().{prop};", el) + + def test_ok_button_clears_gear_in_portrait(self): + self.browser.set_window_size(800, 1200) + ok = self._open_post_page() + gear = self.browser.find_element(By.CSS_SELECTOR, ".post-page > .gear-btn") + self.assertLessEqual( + self._rect(ok, "right"), self._rect(gear, "left"), + "post-composer OK button overlaps the gear in portrait") + + def test_ok_button_clears_burger_slot_in_landscape(self): + self.browser.set_window_size(1400, 800) + ok = self._open_post_page() + rem = self.browser.execute_script( + "return parseFloat(getComputedStyle(document.documentElement).fontSize);") + vw = self.browser.execute_script("return window.innerWidth;") + ok_right = self._rect(ok, "right") + # OK's right edge must sit at least 7.2rem in from the viewport's right + # edge — clearing where #id_burger_btn lands in landscape (and the bud). + self.assertGreaterEqual( + vw - ok_right, 7.2 * rem - 2, + "post-composer OK button doesn't clear the landscape burger slot") diff --git a/src/static_src/scss/_applets.scss b/src/static_src/scss/_applets.scss index 7639694..1d6600e 100644 --- a/src/static_src/scss/_applets.scss +++ b/src/static_src/scss/_applets.scss @@ -98,13 +98,14 @@ } #id_bud_menu { @extend %applet-menu; } -// New Game / New Post composer line-inputs — mirror the My Sky form-col inputs -// (`_sky.scss` .sky-form-col input): bolder text + a terUser focus shift. The -// duoUser fill, secUser default text, and terUser border/glow on focus already -// come from `.form-control`; these only add the 700 weight and the focus -// text-colour. room.html's #id_room_post_text joins this list when it lands. +// New Game / New Post / Bill Post composer line-inputs — mirror the My Sky +// form-col inputs (`_sky.scss` .sky-form-col input): bolder text, a duoUser +// fill, a terUser focus shift + a priUser placeholder. The secUser text and +// terUser border/glow on focus already come from `.form-control`. +// room.html's #id_room_post_text joins this list when it lands. #id_new_game_name, -#id_new_post_text { +#id_new_post_text, +#id_post_line_text { font-weight: 700; background-color: rgba(var(--duoUser), 1); diff --git a/src/static_src/scss/_billboard.scss b/src/static_src/scss/_billboard.scss index be88a22..fa30fc6 100644 --- a/src/static_src/scss/_billboard.scss +++ b/src/static_src/scss/_billboard.scss @@ -343,6 +343,28 @@ body.page-billposts { flex-shrink: 0; margin: 0; padding-top: 0.25rem; + // Clamp the composer's right edge so the OK btn clears the bottom-right + // corner button: the gear (right:0.5rem, 3rem wide → 3.5rem slot) in + // portrait; the burger (#id_burger_btn lands at right:4.2rem in + // landscape → 7.2rem slot, also clearing the bud at right:0.5rem) in + // landscape. (room.html carries the burger; post.html only the bud, but + // the slot is reserved for parity.) + margin-right: 3.5rem; + @media (orientation: landscape) { + margin-right: 7.2rem; + } + + // Editable composer: line input + OK button on one row. + .composer-row { + display: flex; + gap: 0.5rem; + align-items: center; + } + .composer-row input.form-control { + flex: 1; + min-width: 0; + width: auto; + } input.form-control { width: 100%; diff --git a/src/static_src/scss/rootvars.scss b/src/static_src/scss/rootvars.scss index 2dfc6b2..71913b4 100644 --- a/src/static_src/scss/rootvars.scss +++ b/src/static_src/scss/rootvars.scss @@ -231,7 +231,7 @@ // forest --priFor: 124, 156, 89; --secFor: 94, 124, 61; - --terFor: 74, 102, 43; + --terFor: 69, 102, 43; /* Technoman Hue */ // carbon steel @@ -286,7 +286,7 @@ // crumbling perse (Contrition) --priPer: 34, 30, 77; --secPer: 52, 45, 99; - --terPer: 88, 77, 145; + --terPer: 82, 71, 138; --quaPer: 127, 116, 194; --quiPer: 164, 160, 222; --sixPer: 206, 201, 242; diff --git a/src/templates/apps/billboard/post.html b/src/templates/apps/billboard/post.html index 1415c0b..eeb7a97 100644 --- a/src/templates/apps/billboard/post.html +++ b/src/templates/apps/billboard/post.html @@ -70,15 +70,18 @@ {% else %}
{% csrf_token %} - +
+ + +
{% if form.errors %}
{{ form.errors.text.0 }}