Compare 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
4 changed files with 19 additions and 43 deletions
-1
View File
@@ -27,7 +27,6 @@ export class GameFarm {
async load() {
try {
const data = await Http.getJson(this.stateUrl);
if (window.app && window.app.dialog && window.app.dialog.isOpen) return;
this.render(data.farm);
} catch (error) {
return;
-1
View File
@@ -119,7 +119,6 @@ export class ModalManager {
e.preventDefault();
e.stopImmediatePropagation();
const proceed = () => {
if (!el.isConnected) return;
el.dataset.confirmed = "1";
el.click();
};
@@ -86,10 +86,6 @@ export class AppDialog extends Component {
});
}
get isOpen() {
return this.overlay.classList.contains("visible");
}
open(mode, options) {
const opts = options || {};
this.mode = mode;
+19 -37
View File
@@ -41,50 +41,32 @@ os.environ["DEVPLACE_RANKING_TTL"] = "0"
os.environ["DEVPLACE_MARKET_SATURATION_TTL"] = "0"
_ASYNC_LOOP = None
_ASYNC_LOOP_LOCK = threading.Lock()
def _async_loop():
# One shared background loop thread for the whole session, not one thread
# per run_async() call: dataset's NullPool caches one SQLite connection per
# OS thread ID forever (see the root CLAUDE.md NullPool note), so a fresh
# throwaway thread per call - this helper is invoked 270+ times across the
# suite - permanently claimed one file descriptor apiece for the rest of
# the session and could exhaust the process's open-file limit on a long run.
global _ASYNC_LOOP
with _ASYNC_LOOP_LOCK:
if _ASYNC_LOOP is None:
loop = asyncio.new_event_loop()
threading.Thread(
target=loop.run_forever, name="run-async-loop", daemon=True
).start()
_ASYNC_LOOP = loop
return _ASYNC_LOOP
def run_async(coro):
"""Run a coroutine from sync test code on the shared background loop."""
async def _wrapped():
from devplacepy.database import refresh_snapshot
"""Run a coroutine from sync test code on a dedicated thread and fresh loop."""
result_box = []
exc_box = []
def _run():
try:
return await coro
result_box.append(asyncio.run(coro))
except BaseException as e:
exc_box.append(e)
finally:
from devplacepy.database import refresh_snapshot
refresh_snapshot()
future = asyncio.run_coroutine_threadsafe(_wrapped(), _async_loop())
try:
return future.result(timeout=30)
except TimeoutError:
raise TimeoutError(
"run_async timed out waiting for coroutine to complete"
) from None
finally:
from devplacepy.database import refresh_snapshot
t = threading.Thread(target=_run, daemon=True)
t.start()
t.join(timeout=30)
from devplacepy.database import refresh_snapshot
refresh_snapshot()
refresh_snapshot()
if exc_box:
raise exc_box[0]
if result_box:
return result_box[0]
raise TimeoutError("run_async timed out waiting for coroutine to complete")
@pytest.fixture(autouse=True)