CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 42s
CI & Build / integration (push) Successful in 1m39s
CI & Build / Python tests (push) Successful in 2m4s
CI & Build / Build & push image (push) Canceled after 0s
The five auth views each defined .btn-submit identically — full-width, filled, 0.6rem — and LoginView additionally defined .btn-oauth. Those rules are now gone entirely rather than tokenised: the template composes `btn-primary btn-block` and `btn-ghost btn-block`, and there is nothing left per-file to drift. That is the difference between this and the earlier chunks. Consolidating the core four moved geometry into one place but left every semantic name defining its own; this removes the definition. Two variants added, both earned rather than invented: - .btn-text — no fill, no border. The most common shape in the dense surfaces (dismiss, cancel-beside-confirm, clear-search) where a border would draw a box around something that should read as an action on the adjacent text. Distinct from ghost, which IS a box. - .btn-danger-outline — already existed independently in three views before this sheet, which is what makes it a variant and not a one-off. It is what a delete looks like when it must not shout. .btn-block composes with a variant rather than being one, because width is orthogonal to appearance. Also corrected the sheet's own header, which claimed "no template changes" — true when it was written, false as of this commit. It now states the actual model: a button is variant + size, composed in the template. Semantic per-view names are named as the thing that drifted, and why: a name says what a button is FOR and nothing about what it should look like, so two buttons doing the same job in two views had no reason to match, and didn't. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs