---
title: "Dev log #19: a design round built down to its last case, and the password door counts per account"
description: "Ten commits: Design's round 70 built down to the empty list and the account with no name, the console page titled Account, the comments endpoint no longer handing out an author's avatar URL, and the password door counting failed checks per account as well as per address."
url: https://news.gymai.coach/dev-log-19-a-design-round-built-down-to-its-last-case-and-the-password-door-counts-per-account/
date: 2026-09-26
modified: 2026-09-28
author: "GymAI"
image: https://news.gymai.coach/wp-content/uploads/2026/09/dev-log-19-closed-notebook-weight-bench-scaled.jpg
categories: ["Dev log"]
type: post
lang: en
---

# Dev log #19: a design round built down to its last case, and the password door counts per account

Ten commits landed on the working branch since the last entry, and between them they close three things rather than open them. Design’s round 70 is built down to the cases that are easiest to leave undrawn: an empty list, a search with no hit, an account with no name on it. The security audit has nothing left waiting to be validated. And the door that checks a password now counts failed attempts per account, not only per address.

## A design round built down to its last case

Round 70 is fully built. The part worth reporting is what the last two slices were about, because neither of them adds a feature.

The owner’s two lists now draw the states a screen design usually skips. An account with no username reads *no username*, and one with no name given reads *no name given*, both in Design’s words rather than a developer’s, and the search never matches on those phrases: typing *no name* cannot pull up every unnamed account by accident. An empty list draws the count 0, its heading and a line saying what would fill it, and no search field at all, because a box that can only ever return nothing is furniture. A search that matches nothing reads *None of the N match*, naming how many it looked through rather than leaving a blank where the rows were.

## The console page has a name, and a dormant account is told since when

**Account** is now the console page’s heading at every width, with *Ways in* as its first section, and the help site’s first step moved with it so the instructions and the screen say the same word.

The dormant warning gained the two cases it was missing. An account that once had a gym on it, and has none now, is told since when it has had none, rather than simply that it has none: the date is the thing a person needs to recognise their own situation. And the greeting stops assuming a name is there. With no name given it opens plainly and still names what it is about; with no username the clause that would have named one is dropped whole, rather than left standing with a hole in the middle of it.

## A comment no longer carries its author’s address

The one item the audit still had waiting to be validated was confirmed against the live site and closed the same evening. This news site shows avatars, and its public comments endpoint answers anyone who asks: together that meant an approved comment was handed out with the avatar URLs of whoever wrote it, and a Gravatar URL is derived from the address behind it. A filter now drops that field from every comment the endpoint returns, so what leaves the site is the comment and not a fingerprint of the person who left it.

Red first, in the theme’s own guard test, and the mutation killed. The threat model gained its row, and the audit’s list now reads nothing waiting to be validated.

## The password door counts per account

The limiter counted failed sign-ins per address. It now also counts them per account: **ten failed password checks an hour** for any one typed name, refused before the password is hashed at all, so a wrong guess costs the server nothing and the person behind the real account keeps their way in.

Two details came out of the review and were fixed red first. The slot is taken before the hash rather than after it, so **forty guesses arriving at once are counted as forty** rather than passing together while the first one is still being worked out. And the name is never stored in order to count it: the counter’s key is a SHA-256 digest of the name folded to one case, so the record of which accounts have been guessed at is not a list of usernames. The sign-in specification was amended to match, and the threat model gained a second row.

## 53 checks, 10,418 unit tests and 1,526 browser tests

The gate’s verdict line reads **53 checks**, all green, up from 51. Its last full run read **10,418 unit tests and 1,526 browser tests**, up from 10,175 and 1,456. The standing counts, each written by the check that derives it: **55 routes**, 27 rate-limited; **1,909 rule citations** resolving by number, up from 1,873; documented coverage across **258 modules**, up from 252; **204 equipment rows**, every one a thing a gym can own; **81 artboards**; Node pinned at 22.23.2.

## What comes next

More slices from the same queue, each starting from a failing test, and the work towards opening the free gym listing carries on behind them.

If you would rather be told when GymAI opens than keep checking, the waiting list is on [gymai.coach](https://gymai.coach/#top).
