fabrica

hanna/fabrica

style(web): align inline admin controls to a uniform height

c13693a · hanna committed on 2026-07-26

The inline submit buttons in the admin form rows (Set password, Create
invite) rendered shorter than the taller native select/inputs beside
them, leaving lopsided vertical gaps. Give every control in an
.admin-form-row the same explicit 2.4rem height (the convention already
used by .collab-add), and tighten the empty-state line above each create
form so it sits snug against the form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
2 files changed · +16 −3UnifiedSplit
assets/base.css +13 −0
@@ -998,6 +998,7 @@ body.drawer-open .drawer-backdrop {
998}998}
999.admin-form-row {999.admin-form-row {
1000 display: flex;1000 display: flex;
1001 align-items: center;
1001 gap: 0.5rem;1002 gap: 0.5rem;
1002 flex-wrap: wrap;1003 flex-wrap: wrap;
1003 margin-bottom: 0.5rem;1004 margin-bottom: 0.5rem;
@@ -1007,6 +1008,14 @@ body.drawer-open .drawer-backdrop {
1007 flex: 1;1008 flex: 1;
1008 min-width: 9rem;1009 min-width: 9rem;
1009}1010}
1011/* Give every control the same explicit height so inputs, selects, and the
1012 inline submit button line up exactly (native selects render taller than a
1013 button would otherwise, so flex `stretch` alone is not enough). */
1014.admin-form-row > input,
1015.admin-form-row > select,
1016.admin-form-row > button {
1017 height: 2.4rem;
1018}
1010/* A control row that is the last thing in its form carries no trailing gap. */1019/* A control row that is the last thing in its form carries no trailing gap. */
1011.admin-form-row-last {1020.admin-form-row-last {
1012 margin-bottom: 0;1021 margin-bottom: 0;
@@ -1029,6 +1038,10 @@ body.drawer-open .drawer-backdrop {
1029.form-field label {1038.form-field label {
1030 margin-top: 0;1039 margin-top: 0;
1031}1040}
1041/* Empty-state line above a create form: keep it snug against the form. */
1042.empty-note {
1043 margin: 0 0 0.75rem;
1044}
10321045
1033/* Settings: a left tab rail and the active tab's content. */1046/* Settings: a left tab rail and the active tab's content. */
1034.settings-layout {1047.settings-layout {
crates/web/src/admin.rs +3 −3
@@ -439,7 +439,7 @@ pub async fn repos(
439 h2 { "Repositories" }439 h2 { "Repositories" }
440 section class="listing" {440 section class="listing" {
441 div class="card" {441 div class="card" {
442 @if repos.is_empty() { p class="muted" { "No repositories." } }442 @if repos.is_empty() { p class="muted empty-note" { "No repositories." } }
443 ul class="entry-list admin-list" {443 ul class="entry-list admin-list" {
444 @for r in &repos {444 @for r in &repos {
445 @let owner = owners.get(&r.owner_id).cloned().unwrap_or_else(|| r.owner_id.clone());445 @let owner = owners.get(&r.owner_id).cloned().unwrap_or_else(|| r.owner_id.clone());
@@ -510,7 +510,7 @@ pub async fn groups(
510 h2 { "Groups" }510 h2 { "Groups" }
511 section class="listing" {511 section class="listing" {
512 div class="card" {512 div class="card" {
513 @if groups.is_empty() { p class="muted" { "No groups." } }513 @if groups.is_empty() { p class="muted empty-note" { "No groups." } }
514 ul class="entry-list admin-list" {514 ul class="entry-list admin-list" {
515 @for g in &groups {515 @for g in &groups {
516 @let owner = owners.get(&g.owner_id).cloned().unwrap_or_else(|| g.owner_id.clone());516 @let owner = owners.get(&g.owner_id).cloned().unwrap_or_else(|| g.owner_id.clone());
@@ -573,7 +573,7 @@ pub async fn invites(
573 }573 }
574 section class="listing" {574 section class="listing" {
575 div class="card" {575 div class="card" {
576 @if invites.is_empty() { p class="muted" { "No invites." } }576 @if invites.is_empty() { p class="muted empty-note" { "No invites." } }
577 ul class="entry-list admin-list" {577 ul class="entry-list admin-list" {
578 @for iv in &invites {578 @for iv in &invites {
579 li class="entry-row" {579 li class="entry-row" {