New Post applet: kill #id_text remnant, add OK btn, bolden composer inputs — TDD

Prep for porting a post composer into room.html. Three changes on the New Game / New Post applets:

1. Renamed the old Percival #id_text → #id_new_post_text. _form.html (the shared post composer) is now parameterized on input_id (default id_new_post_text) + submit_id (default id_new_post_btn); the feedback div + aria-describedby track {{ input_id }}_feedback. _applet-new-post.html includes it with input_id="id_new_post_text". room.html will reuse the same partial with input_id="id_room_post_text". Refs updated: _scripts.html (initialize("#id_new_post_text")), Jasmine Spec.js, FT post_page.py. _form.html has exactly one includer.

2. Added an OK .btn-confirm submit to _form.html (flex .composer-row: line input + OK, validation feedback below), mirroring the New Game applet. ENTER still submits, so the existing add_post_line FTs stay green.

3. Composer-input styling in _applets.scss: #id_new_game_name, #id_new_post_text { font-weight: 700; &:focus { color: --terUser } } — mirrors .sky-form-col input. The duoUser fill / secUser default text / terUser border+glow on focus already come from .form-control; this adds the 700 weight + the focus text-colour shift. room's #id_room_post_text joins this selector list when it lands.

Tests: NewPostTest ITs (conventional id + aria-describedby, OK .btn-confirm). 440 dashboard+billboard ITs green; Jasmine spec green with the renamed id; test_bill_new_post FT green (renamed input + OK btn, ENTER submit).

[[project-room-scroll-of-events]]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-06-01 20:37:52 -04:00
parent 4a4e60f668
commit 39a2a49d7e
8 changed files with 78 additions and 24 deletions

View File

@@ -64,6 +64,32 @@ class NewPostTest(TestCase):
response = self.post_invalid_input() response = self.post_invalid_input()
self.assertContains(response, html.escape(EMPTY_LINE_ERROR)) self.assertContains(response, html.escape(EMPTY_LINE_ERROR))
def _new_post_form(self, response):
parsed = lxml.html.fromstring(response.content)
forms = [f for f in parsed.cssselect("form")
if f.get("action") == "/billboard/new-post"]
self.assertTrue(forms, "new-post form not rendered")
return forms[0]
def test_new_post_input_uses_conventional_id_not_percival_id_text(self):
"""The line input's id is the conventional #id_new_post_text, not the
old Percival remnant #id_text (the form is about to be reused in
room.html, which needs its own #id_room_post_text)."""
form = self._new_post_form(self.post_invalid_input())
ids = [i.get("id") for i in form.cssselect("input")]
self.assertIn("id_new_post_text", ids)
self.assertNotIn("id_text", ids)
# aria-describedby tracks the renamed feedback id too.
line_input = form.cssselect("#id_new_post_text")[0]
self.assertEqual(line_input.get("aria-describedby"), "id_new_post_text_feedback")
def test_new_post_applet_has_ok_confirm_button(self):
"""New Post gains an OK .btn-confirm submit button, like New Game."""
form = self._new_post_form(self.post_invalid_input())
buttons = form.cssselect("button.btn-confirm")
self.assertTrue(buttons, "no OK .btn-confirm in New Post applet")
self.assertEqual(buttons[0].text_content().strip(), "OK")
@override_settings(COMPRESS_ENABLED=False) @override_settings(COMPRESS_ENABLED=False)
class PostViewTest(TestCase): class PostViewTest(TestCase):
def setUp(self): def setUp(self):

View File

@@ -27,13 +27,14 @@ class PostPage:
) )
def get_line_input_box(self): def get_line_input_box(self):
# /billboard/ new-post applet uses #id_text (creates a fresh Post); # /billboard/ new-post applet uses #id_new_post_text (creates a fresh
# post.html aperture uses #id_post_line_text (appends to existing). # Post); post.html aperture uses #id_post_line_text (appends to an
# `add_post_line` is called from both contexts, so probe in order. # existing one). `add_post_line` is called from both contexts, so
# probe in order.
boxes = self.test.browser.find_elements(By.ID, "id_post_line_text") boxes = self.test.browser.find_elements(By.ID, "id_post_line_text")
if boxes: if boxes:
return boxes[0] return boxes[0]
return self.test.browser.find_element(By.ID, "id_text") return self.test.browser.find_element(By.ID, "id_new_post_text")
def add_post_line(self, line_text): def add_post_line(self, line_text):
new_line_no = len(self.get_table_rows()) + 1 new_line_no = len(self.get_table_rows()) + 1

View File

@@ -1,7 +1,7 @@
console.log("Spec.js is loading"); console.log("Spec.js is loading");
describe("GameArray JavaScript", () => { describe("GameArray JavaScript", () => {
const inputId= "id_text"; const inputId= "id_new_post_text";
const errorClass = "invalid-feedback"; const errorClass = "invalid-feedback";
const inputSelector = `#${inputId}`; const inputSelector = `#${inputId}`;
const errorSelector = `.${errorClass}`; const errorSelector = `.${errorClass}`;
@@ -20,10 +20,10 @@ describe("GameArray JavaScript", () => {
class="form-control form-control-lg is-invalid" class="form-control form-control-lg is-invalid"
placeholder="Enter a to-do item" placeholder="Enter a to-do item"
value="Value as submitted" value="Value as submitted"
aria-describedby="id_text_feedback" aria-describedby="id_new_post_text_feedback"
required required
/> />
<div id="id_text_feedback" class="${errorClass}">An error message</div> <div id="id_new_post_text_feedback" class="${errorClass}">An error message</div>
</form> </form>
`; `;
document.body.appendChild(testDiv); document.body.appendChild(testDiv);

View File

@@ -98,6 +98,20 @@
} }
#id_bud_menu { @extend %applet-menu; } #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.
#id_new_game_name,
#id_new_post_text {
font-weight: 700;
&:focus {
color: rgba(var(--terUser), 1);
}
}
// Page-level gear buttons — fixed to viewport bottom-right // Page-level gear buttons — fixed to viewport bottom-right
.gameboard-page, .gameboard-page,
.dashboard-page, .dashboard-page,

View File

@@ -1,7 +1,7 @@
console.log("Spec.js is loading"); console.log("Spec.js is loading");
describe("GameArray JavaScript", () => { describe("GameArray JavaScript", () => {
const inputId= "id_text"; const inputId= "id_new_post_text";
const errorClass = "invalid-feedback"; const errorClass = "invalid-feedback";
const inputSelector = `#${inputId}`; const inputSelector = `#${inputId}`;
const errorSelector = `.${errorClass}`; const errorSelector = `.${errorClass}`;
@@ -20,10 +20,10 @@ describe("GameArray JavaScript", () => {
class="form-control form-control-lg is-invalid" class="form-control form-control-lg is-invalid"
placeholder="Enter a to-do item" placeholder="Enter a to-do item"
value="Value as submitted" value="Value as submitted"
aria-describedby="id_text_feedback" aria-describedby="id_new_post_text_feedback"
required required
/> />
<div id="id_text_feedback" class="${errorClass}">An error message</div> <div id="id_new_post_text_feedback" class="${errorClass}">An error message</div>
</form> </form>
`; `;
document.body.appendChild(testDiv); document.body.appendChild(testDiv);

View File

@@ -4,5 +4,5 @@
> >
<h2>New Post</h2> <h2>New Post</h2>
{% url "billboard:new_post" as form_action %} {% url "billboard:new_post" as form_action %}
{% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action %} {% include "apps/dashboard/_partials/_form.html" with form=form form_action=form_action input_id="id_new_post_text" submit_id="id_new_post_btn" %}
</section> </section>

View File

@@ -1,17 +1,30 @@
{% comment %}
Shared post-composer form. `input_id` lets each surface give the line input a
distinct id (new-post applet → id_new_post_text; room.html → id_room_post_text);
`submit_id` likewise for the OK button. Mirrors the New Game applet: a flex row
with the line input + an OK .btn-confirm submit. The validation feedback sits
below the row (and its id tracks `input_id` for aria-describedby).
{% endcomment %}
{% with input_id=input_id|default:"id_new_post_text" submit_id=submit_id|default:"id_new_post_btn" %}
<form method="POST" action="{{ form_action }}"> <form method="POST" action="{{ form_action }}">
{% csrf_token %} {% csrf_token %}
<div class="composer-row" style="display:flex; gap:0.5rem; align-items:center;">
<input <input
id="id_text" id="{{ input_id }}"
name="text" name="text"
class="form-control form-control-lg{% if form.errors.text %} is-invalid{% endif %}" class="form-control form-control-lg{% if form.errors.text %} is-invalid{% endif %}"
placeholder="Enter a post line" placeholder="Enter a post line"
value="{{ form.text.value | default:'' }}" value="{{ form.text.value | default:'' }}"
aria-describedby="id_text_feedback" aria-describedby="{{ input_id }}_feedback"
style="flex:1; min-width:0;"
required required
/> />
<button type="submit" id="{{ submit_id }}" class="btn btn-confirm">OK</button>
</div>
{% if form.errors %} {% if form.errors %}
<div id="id_text_feedback" class="invalid-feedback"> <div id="{{ input_id }}_feedback" class="invalid-feedback">
{{ form.errors.text.0 }} {{ form.errors.text.0 }}
</div> </div>
{% endif %} {% endif %}
</form> </form>
{% endwith %}

View File

@@ -9,9 +9,9 @@
<script src="{% static "apps/gameboard/gameboard.js" %}"></script> <script src="{% static "apps/gameboard/gameboard.js" %}"></script>
<script> <script>
window.onload = () => { window.onload = () => {
// #id_text — new-post applet on billboard.html; // #id_new_post_text — new-post applet on billboard.html;
// #id_post_line_text — post.html bottom-anchored aperture. // #id_post_line_text — post.html bottom-anchored aperture.
initialize("#id_text"); initialize("#id_new_post_text");
initialize("#id_post_line_text"); initialize("#id_post_line_text");
bindPaletteSwatches(); bindPaletteSwatches();
bindPaletteWheel(); bindPaletteWheel();