Skip to content

v1.5.0 - Data rights, keyboard access, verified backups

Choose a tag to compare

@ibuilder ibuilder released this 08 Aug 14:28
· 5 commits to main since this release

Everything in this release came from looking at the product rather than reading
the code: rendering an exhibit and inspecting it, measuring against a real
database, and driving the editor from a keyboard. Each of those turned up a
defect that the 380-test suite was structurally unable to see.

The outline is reorderable without a mouse

The editor had 72 items with draggable="true", none focusable, and no
keyboard alternative. Reordering clauses — the core editing action of the
application — was impossible without a pointer. That is a WCAG 2.1.1 failure,
and no test would ever have caught it.

Every item now has move up/down buttons driving the same persistence path the
drag handler uses. Focus is restored to the moved button deliberately: moving a
node in the DOM blurs it, which would drop the user at the top of the document
after every press. The labels name the section as well as the item, because
item numbers restart in each section and "Move item 1. up" otherwise appeared
several times on one page.

Also fixed: the admin role dropdown had no accessible name, so every row
announced identically, and no table declared scope on its header cells.

Export your data, delete your account

An export is personal data, not the employer's documents. It lists the scopes
you authored — id, title, status, when — without their contents, because those
belong to the organization that paid for them. Tests assert that no password
hash, token hash, raw token or MFA secret appears anywhere in the output.

Deletion is bounded by what has to survive it:

  • The audit log keeps actor_label when the foreign key nulls out, so deleting
    an account cannot erase what it did.
  • Shared organizations keep their scopes; the documents lose only the
    authorship link.
  • The last administrator of an organization with other members is blocked,
    not warned — leaving would strand those members.
  • An organization whose only member leaves is deleted with them, because nobody
    could ever sign in to it again. The confirmation page names it, since that is
    the destructive part nobody anticipates.

Backups are rehearsed, not assumed

docs/deployment.md told operators to back up PostgreSQL and said that was all
the state there was. Nobody had ever restored one.

CI now populates a database, pg_dumps it, drops the schema, restores, and
verifies. Row counts alone would pass even with the encrypted columns coming
back as mush, so it also re-renders a reference exhibit and compares it byte for
byte. Latest run: 642 rows across 19 tables, restored and rendering identically.

Two defects found by measuring

A clause with a long sub-list jumped the page whole. Page 1 of the Division
21 exhibit was 45% blank — clause 3 has 22 specification sections under it, and
the wrapper <li> holding that sub-list inherited break-inside: avoid, making
the entire block unbreakable. The existing PDF tests asserted that text was
present and that the document paginated; both passed. There are now assertions
on how far down each page the content reaches.

The scopes list issued a query per row — 32 at 25 scopes, 16 at 12. Each row
lazily loaded its bid package. Projects repeat and were answered from the
identity map, which is exactly why a single-project dataset looked fine. Now a
constant 7, verified at 10 and 30 scopes.

The load test also runs against PostgreSQL with 8 concurrent clients on every
push now, published to the job summary. SQLite serialises writers, so the local
numbers had been measuring lock contention rather than the application.

Upgrading

No migration. No configuration change.

Full changelog: https://github.com/ibuilder/scopemaker/blob/main/CHANGELOG.md