Compare commits

..
6 Commits
Author SHA1 Message Date
typosaurus a7f7df7714 test(sveta): Extend vote-state tests to assert the non-color cue
DevPlace CI / test (pull_request) Failing after 1h37m29s
Outcome: done
Changed: tests/conftest.py:293-300, tests/e2e/post.py:5,74-157, tests/e2e/posts/index.py:17,396-418, tests/e2e/notifications/index.py:313,799, tests/e2e/leaderboard.py:42
Verified by: python -m pytest tests/e2e/post.py tests/e2e/posts/index.py tests/e2e/notifications/index.py tests/e2e/leaderboard.py -> 68 passed (verify() passed); make test full suite -> 3344 passed, 1 skipped, 10 failed (9x tests/api/projects/workspace.py: docker binary absent in this environment, same as prior sibling run; 1x tests/e2e/game/farm.py::test_victim_notification_names_the_raider_and_the_amount, passes standalone and 9/9 in its file, game code has no vote markup)
Findings: New vote_glyph helper (tests/conftest.py:293) reads getComputedStyle(el,'::before').content and normalizes CSS escapes; glyph assertions cover post upvote (+ -> U+2295), post downvote (U+2212 -> U+2296), and comment upvote (+ -> U+2295), each also asserting unvoted glyph absence and toggle-off restoration to the unvoted glyph. New test fails against pre-change code: on 72e11db, test_post_upvote_voted_state_persists fails with "assert 'none' == '+'" (no ::before rule existed; buttons held literal text). Fixed all three has_text="+" vote locators (notifications/index.py:313,799, leaderboard.py:42) to class-based button.post-action-btn.vote-up; the three tests now pass. test_comment_voted_state_persists was made deterministic by wrapping the click in expect_response (pre-existing reload race); it now passes reliably

Typosaurus-Run: c36faeceb55d45b6bf38a45eec4f47ab
Typosaurus-Node: 9b6a5ec294a2472e800fbfeed775b27b
Typosaurus-Agent: @sveta
Refs: #162
2026-08-16 01:12:14 +00:00
typosaurus 285bb25c06 feat(zoya): Review the vote-button change map against the acceptance criteria
Outcome: done
Changed: none
Verified by: e2e run attempted (3 tests) but blocked at browser launch by missing libglib-2.0.so.0 (environment fault, not a result); breakage proven from Playwright source elementText() in driver coreBundle.js plus empty-button markup
Findings: REJECT - the implementation empties all four vote buttons (_post_votes.html:4,9; _comment.html:6,11) and moves glyphs into CSS ::before, so has_text="+" locators at tests/e2e/notifications/index.py:313, :799 and tests/e2e/leaderboard.py:42 match zero elements (Playwright text matching reads DOM text nodes, never ::before content) and those 3 tests time out
Findings: commit 625940a claimed "Changed: none" but deleted the glyph text from post-downvote and comment-upvote, leaving HEAD with two glyph-less vote buttons (regression only repaired by the uncommitted diff adding the ::before rules)
Findings: map line references are accurate for 625940a^ (text glyphs, no ::before rules) but stale for the working tree, where glyph rules are feed.css:193-217 and post.css:141-165
Findings: the voted cue itself satisfies the ticket - `+`->U+2295 and U+2212/U+002D->U+2296 under .voted (feed.css:211-217, post.css:159-165), non-color, consistent with the star precedent base.css:1319-1325, with aria-pressed synced client-side (VoteManager.js:60-62)
Findings: unvoted downvote glyphs remain inconsistent in touched rules: post U+2212 (feed.css:198) vs comment U+002D (post.css:146)
Open: fix the 3 e2e locators to class-based .vo

Typosaurus-Run: c36faeceb55d45b6bf38a45eec4f47ab
Typosaurus-Node: ebdbae6c04a84a239287bd62262cb254
Typosaurus-Agent: @zoya
Refs: #162
2026-08-16 00:23:16 +00:00
typosaurus 338f023c0d feat(mila): Confirm the star precedent, enumerate test dependencies, recommend the non-color
Outcome: done
Changed: none
Verified by: not verified (read-only node; no files changed)
Findings: RECOMMENDATION TO BUILDER: swap the glyph in the voted state only — upvote `+` (U+002B) → `⊕` (U+2295), downvote `−`/`-` (U+2212/U+002D) → `⊖` (U+2296) — via template conditionals in _post_votes.html:4,9 and _comment.html:6,11 plus a VoteManager.js render() textContent mirror scoped to `.vote-up`/`.vote-down` only (VoteManager also binds `.vote-star` forms, whose `::before` glyph must not be overwritten).
Findings: Precedent confirmed at base.css:1319-1325: `.vote-star::before` swaps outline `\2606` (line 1320) to filled `\2605` (line 1324) under `.voted`; templates/CLAUDE.md:70 confirms the star glyph is pseudo-element-only, while the +/- glyphs are text nodes, so a pure-CSS swap would require moving them into `::before`.
Findings: Only 3 of 18 vote-button test hits are text-coupled — tests/e2e/notifications/index.py:313, :799 and tests/e2e/leaderboard.py:42, all `button.post-action-btn` filtered by `has_text="+"`; each resolves immediately before the click, so a voted-state-only glyph swap that preserves the unvoted `+` text breaks none of them.
Findings: The other 15 hits (posts/index.py:407,413; projects/devlog.py:133-134; feed.py:687; post.py:63,70,80,83,87,97,100,104,187) and all comment tests (post.py:117-124,218-221,274-278) use `.vote-up`/`.vote-down`/`.comment-vote-btn` classes and `\bvoted\b` assertions, so they break only if those class names change.
Findings: Empiri

Typosaurus-Run: c36faeceb55d45b6bf38a45eec4f47ab
Typosaurus-Node: 9f61b1db85c147a6b2b8376a6c8ae798
Typosaurus-Agent: @mila
Refs: #162
2026-08-16 00:23:16 +00:00
retoorandClaude Sonnet 5 8db0efff29 Fix Devii open-trigger race and close two flaky e2e waits
DevPlace CI / test (push) Failing after 1h30m15s
DeviiTerminal bound [data-devii-open] click listeners only after the
async /devii/session fetch resolved, silently dropping early clicks.
Switch to a single delegated document listener bound in the
constructor, matching the ModalManager/dp-lightbox pattern.

The steal-confirm and comment-vote e2e tests asserted DOM state right
after a click with no wait for the triggering POST to land, racing the
server under CI load. Wrap those clicks in page.expect_response.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 02:02:34 +02:00
retoor fa8751a4ca Uppdate
DevPlace CI / test (push) Failing after 1h27m55s
2026-08-15 20:38:24 +02:00
retoorandClaude Sonnet 5 682be0861f Fail fast with one clear diagnostic when app_server dies mid-session
A shared-server crash mid-suite previously cascaded into hundreds of
opaque connection-refused errors across every later api/e2e test,
making the real cause invisible. pytest_runtest_setup now polls the
tracked subprocess and, on the first test after it exits, reports the
exit code plus the server's own log tail once instead of forcing every
subsequent test to fail blind.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:25:39 +02:00
5 changed files with 50 additions and 10 deletions
+2
View File
@@ -57,8 +57,10 @@
}
.auth-field-check input {
width: auto;
margin-top: 0.2rem;
flex: none;
accent-color: var(--accent);
}
.auth-field-check a {
+11 -7
View File
@@ -10,6 +10,7 @@ export class DeviiTerminal {
this.avatar = null;
this.bindEscapeTrigger();
this.bindDoubleClickTrigger();
this.bindOpenTrigger();
this.ready = this.init();
}
@@ -29,16 +30,19 @@ export class DeviiTerminal {
this.element.setAttribute("avatar", "devii");
document.body.appendChild(this.element);
this.bindTriggers();
this.maybeAutoOpen();
}
bindTriggers() {
document.querySelectorAll("[data-devii-open]").forEach((trigger) => {
trigger.addEventListener("click", (event) => {
event.preventDefault();
this.open(trigger.dataset.deviiPrompt || "");
});
bindOpenTrigger() {
document.addEventListener("click", (event) => {
const trigger = event.target.closest("[data-devii-open]");
if (!trigger) return;
event.preventDefault();
this.open(trigger.dataset.deviiPrompt || "");
});
}
maybeAutoOpen() {
const stored = this.element && this.element.hasStoredState && this.element.hasStoredState();
if (document.querySelector("[data-devii-autoopen]") && !stored) {
this.open();
+1 -1
View File
@@ -69,7 +69,7 @@
<div class="auth-field auth-field-check">
<label for="accept_terms">
<input type="checkbox" id="accept_terms" name="accept_terms" value="1" required aria-required="true"{% if accept_terms %} checked{% endif %}>
I accept the <a href="/docs/terms.html" target="_blank" rel="noopener">Terms of Service</a> and the <a href="/docs/community-guidelines.html" target="_blank" rel="noopener">Community Guidelines</a>, and I have read the <a href="/docs/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.
<span>I accept the <a href="/docs/terms.html" target="_blank" rel="noopener">Terms of Service</a> and the <a href="/docs/community-guidelines.html" target="_blank" rel="noopener">Community Guidelines</a>, and I have read the <a href="/docs/privacy.html" target="_blank" rel="noopener">Privacy Policy</a>.</span>
</label>
</div>
+32
View File
@@ -125,6 +125,13 @@ def _port_in_use(port):
return sock.connect_ex(("127.0.0.1", port)) == 0
# Set once app_server has successfully started, so pytest_runtest_setup can
# detect a mid-session crash (e.g. resource exhaustion on a constrained CI
# runner) and fail every subsequent HTTP-dependent test with one clear
# diagnostic instead of a wall of opaque connection-refused errors.
_APP_SERVER_STATE = {"proc": None, "log_path": None}
@pytest.fixture(scope="session")
def app_server(test_db_path):
if _port_in_use(PORT):
@@ -192,7 +199,11 @@ def app_server(test_db_path):
_set_setting("rate_limit_per_minute", "1000000")
_set_setting("rate_limit_window_seconds", "60")
_APP_SERVER_STATE["proc"] = proc
_APP_SERVER_STATE["log_path"] = log_file.name
yield proc
_APP_SERVER_STATE["proc"] = None
try:
proc.terminate()
proc.wait(timeout=10)
@@ -207,6 +218,27 @@ def app_server(test_db_path):
pass
def pytest_runtest_setup(item):
proc = _APP_SERVER_STATE["proc"]
if proc is None or "tests/unit/" in item.nodeid:
return
exit_code = proc.poll()
if exit_code is None:
return
log_path = _APP_SERVER_STATE["log_path"]
try:
with open(log_path, "r", errors="replace") as f:
log_tail = f.read()[-5000:]
except OSError:
log_tail = "(log unavailable)"
pytest.fail(
f"app_server died mid-session (exit code {exit_code}). It was healthy "
f"earlier in this run and has since crashed - this is a shared-server "
f"failure, not a bug in {item.nodeid}. Server log tail:\n{log_tail}",
pytrace=False,
)
@pytest.fixture(scope="session")
def playwright_instance():
from playwright.sync_api import sync_playwright
+4 -2
View File
@@ -128,7 +128,8 @@ def test_steal_takes_build_and_pays_thief(bob):
steal.click()
confirm_btn = page.locator(".dialog-confirm")
confirm_btn.wait_for(state="visible")
confirm_btn.click()
with page.expect_response(lambda r: "/steal" in r.url and r.request.method == "POST"):
confirm_btn.click()
expect(page.locator("form[data-game-action='steal']")).to_have_count(0)
page.wait_for_timeout(400)
assert _farm_coins("bob_test") == 18
@@ -165,7 +166,8 @@ def test_victim_notification_names_the_raider_and_the_amount(bob):
steal.click()
confirm_btn = page.locator(".dialog-confirm")
confirm_btn.wait_for(state="visible")
confirm_btn.click()
with page.expect_response(lambda r: "/steal" in r.url and r.request.method == "POST"):
confirm_btn.click()
expect(page.locator("form[data-game-action='steal']")).to_have_count(0)
page.wait_for_timeout(400)
note = get_table("notifications").find_one(