commits 4bb6ab1 web: polish pass against the workbench mock
Playwright screenshots of every page against the design handoff turned
up systemic spacing gaps, all from markup bypassing the design system:
forms sat flush against card edges (no inner padding), bare h2 section
headings and p.muted lead-ins had no margins at all, and several pages
rendered forms naked on the page background.
.card > form gets the mock composer’s 18/20 padding, action row
right-aligned; every add/define/import form now lives in a card with
a header
bare h2 / p.muted in page flow get the mock’s section-heading rhythm
.disclosure card-lets for the fold-shut edit affordances (issue edit,
login mapping)
meta index: stacked accent rows, "Repo & governance" title, real em
dashes; commit rows: sans author/when per the mock; stray lowercase
card headers dropped; form labels Title-cased; .readable centered
Joseph D. Carpinelli · 29 days ago
Reviews No reviews of this commit yet — record a verdict below.
Start a review verdict
approve request-changes comment
Body Start a Review crates/cli/ents-web/src/assets/ents.css
@@ -252,7 +252,7 @@
/* ===================== CONTENT / SPLIT ===================== */
.content { max-width: 1120px; width: 100%; margin: 0 auto; padding: 30px 34px 60px; flex: 1; }
-.readable { max-width: 760px; }
+.readable { max-width: 760px; margin-inline: auto; }
/* Master-detail (`crate::pages::layout_split`): a sticky scrollable
* sidebar beside a `min-height:0` detail pane. */
.split { display: grid; grid-template-columns: 300px minmax(0, 1fr); height: calc(100vh - 56px); }
@@ -306,6 +306,12 @@
}
/* ===================== PAGE HEADER ===================== */
+/* Bare `h2` and `p.muted` in page flow are the section-heading and
+ * lead-in idiom (mock: 14px/600 with 26px above; muted lead-ins breathe
+ * instead of sitting flush on the card below). */
+.content h2, .pane h2 { margin: 26px 0 12px; font-size: 14px; font-weight: 600; }
+.content h2:first-child, .pane h2:first-child { margin-top: 0; }
+p.muted { margin-bottom: 16px; font-size: 13px; text-wrap: pretty; }
.page-header { margin-bottom: 22px; display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.page-title { font-size: 22px; font-weight: 600; letter-spacing: -.3px; line-height: 1.25; }
.page-sub { margin-top: 5px; color: var(--ink-3); font-size: 13px; }
@@ -322,6 +328,15 @@
font-size: 13px; font-weight: 600; color: var(--ink);
}
.card-header .btn-ghost { margin-left: auto; }
+/* A form as a card's body gets the mock composer's inner padding, its
+ * action row right-aligned as the mock's composer footer. */
+.card > form { padding: 18px 20px; margin-bottom: 0; }
+.card > form .composer-buttons { justify-content: flex-end; }
+.tree-head .btn { height: 24px; }
+/* Stacked card rows (meta index): name over blurb, sans, left-aligned. */
+.card-row.stack { flex-direction: column; align-items: flex-start; gap: 2px; padding: 13px 16px; font-family: var(--sans); }
+.card-row.stack a { flex: none; font-size: 13.5px; font-weight: 600; color: var(--accent); text-transform: capitalize; }
+.card-row.stack span { font-size: 12.5px; color: var(--ink-3); }
.card-row {
display: flex; align-items: center; gap: 10px; padding: 9px 16px;
font-family: var(--mono); font-size: 12.5px;
@@ -352,6 +367,9 @@
.history .card-row a:hover { text-decoration: underline; }
.desk-subject { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--sans); font-size: 13px; color: var(--ink); }
.desk-when { flex: none; font-size: 12px; color: var(--ink-3); }
+/* Commit-row trailing author/when: sans, quiet, never wrapping (mock). */
+.row-author { flex: none; font-family: var(--sans); font-size: 12px; color: var(--ink-3); }
+.row-when { flex: none; font-family: var(--sans); font-size: 12px; color: var(--ink-4); white-space: nowrap; }
@media (max-width: 1100px) { .desk { grid-template-columns: minmax(0, 1fr); } }
/* ===================== BUTTONS ===================== */
@@ -359,6 +377,7 @@
display: inline-flex; align-items: center; gap: 6px; height: 34px; padding: 0 16px;
font-size: 13px; font-weight: 500; color: #fff; background: var(--accent);
border: none; border-radius: var(--radius-inner); cursor: pointer;
+ white-space: nowrap; flex: none;
}
.btn:hover { background: var(--accent-2); text-decoration: none; color: #fff; }
.btn-sm { height: 24px; padding: 0 10px; font-size: 11.5px; border-radius: var(--radius-chip); }
@@ -486,7 +505,7 @@
text-align: center; padding: 40px 24px; border: 1px dashed var(--line);
border-radius: var(--radius-card); background: var(--surface);
}
-.blankslate h2 { font-size: 13.5px; font-weight: 600; color: var(--ink-2); margin-bottom: 4px; }
+.blankslate h2 { font-size: 13.5px; font-weight: 600; color: var(--ink-2); margin: 0 0 4px; }
.blankslate p { color: var(--ink-3); font-size: 12.5px; }
.blankslate code { font-family: var(--mono); background: var(--surface-2); border: 1px solid var(--line); padding: 1px 6px; border-radius: 5px; font-size: 12px; }
.card .blankslate { border: none; }
@@ -544,6 +563,21 @@
}
.label-chip.on { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); font-weight: 600; }
+/* ===================== DISCLOSURES ===================== *
+ * An edit affordance folded shut (issue edit, login-mapping edit): a
+ * bordered card-let whose summary is the whole closed state. */
+.disclosure {
+ background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
+ overflow: hidden; margin-bottom: 12px;
+}
+.disclosure > summary {
+ padding: 11px 16px; cursor: pointer; list-style: none; user-select: none; -webkit-user-select: none;
+ font-size: 12.5px; font-weight: 500; color: var(--ink-2);
+}
+.disclosure > summary::-webkit-details-marker { display: none; }
+.disclosure > summary:hover { color: var(--accent); }
+.disclosure form { padding: 4px 16px 16px; margin-bottom: 0; }
+
/* ===================== CRUMBS ===================== */
.crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; margin-bottom: 16px; font-family: var(--mono); font-size: 12px; color: var(--ink-3); word-break: break-all; }
.crumbs a { color: var(--ink-3); }
crates/cli/ents-web/src/pages/account.rs
@@ -76,7 +76,7 @@
"Signed in as the member below. Every web edit is a "
"mutation commit signed with this key, exactly as "
code { "git ents" }
- " itself would sign it -- a local root has no separate login."
+ " itself would sign it — a local root has no separate login."
}
(super::members::member_card(username.as_str(), member, true))
}
@@ -104,15 +104,15 @@
p.muted {
"A hosted deployment maps an external login to an enrolled "
"member so its pushes can be attributed. A local root never "
- "needs one -- the key above is the identity."
+ "needs one — the key above is the identity."
}
(view)
- details {
+ details.disclosure {
summary { "Edit login mapping" }
form method="post" action="/account" {
(super::csrf_input(&session))
- label { "member" input type="text" name="member" value=(member_value) list="members"; }
- label { "login" input type="text" name="login" value=(login_value); }
+ label { "Member" input type="text" name="member" value=(member_value) list="members"; }
+ label { "Login" input type="text" name="login" value=(login_value); }
button type="submit" { "Save" }
}
(super::members_datalist(&state))
crates/cli/ents-web/src/pages/comments.rs
@@ -101,8 +101,10 @@
(listing_card(&state, id, comment))
}
}
- h2 { "Add a Comment" }
- (add_form(&query.rev, &session, &query.file, &query.lines))
+ div.card {
+ div.card-header { "Add a comment" }
+ (add_form(&query.rev, &session, &query.file, &query.lines))
+ }
}
},
))
@@ -484,7 +486,7 @@
form method="post" action=(format!("/comments/{id}/reply")) {
(super::csrf_input(session))
input type="hidden" name="return_to" value=(return_to);
- label { "reply" textarea name="body" {} }
+ label { "Reply" textarea name="body" {} }
button type="submit" { "Reply" }
}
@if resolved {
@@ -578,10 +580,10 @@
html! {
form method="post" action="/comments" {
(super::csrf_input(session))
- label { "path" input type="text" name="path" value=(prefill_path); }
- label { "rev" input type="text" name="rev" value=(default_rev); }
- label { "lines" input type="text" name="lines" value=(prefill_lines); }
- label { "body" textarea name="body" {} }
+ label { "Path" input type="text" name="path" value=(prefill_path); }
+ label { "Rev" input type="text" name="rev" value=(default_rev); }
+ label { "Lines" input type="text" name="lines" value=(prefill_lines); }
+ label { "Body" textarea name="body" {} }
button type="submit" { "Comment" }
}
}
crates/cli/ents-web/src/pages/commits.rs
@@ -102,7 +102,6 @@
(blankslate())
} @else {
div.card.history {
- div.card-header { "commits" }
@for row in &rows {
(commit_row(row))
}
@@ -203,8 +202,8 @@
},
None => { span.desk-subject { (row.subject) } },
}
- span.muted { (row.author) }
- span.entry-size { (row.ago) }
+ span.row-author { (row.author) }
+ span.row-when { (row.ago) }
}
}
}
@@ -530,7 +529,7 @@
form method="post" action=(format!("/reviews/{target}/{member}/comment")) {
(super::csrf_input(session))
input type="hidden" name="return_to" value=(return_to);
- label { "comment on this review" textarea name="body" {} }
+ label { "Comment on this review" textarea name="body" {} }
button type="submit" { "Comment" }
}
}
@@ -626,7 +625,7 @@
"comment"
}
}
- label { "body" textarea name="body" {} }
+ label { "Body" textarea name="body" {} }
button type="submit" { "Start a Review" }
}
}
crates/cli/ents-web/src/pages/effects.rs
@@ -57,8 +57,10 @@
html! {
(crate::render::unreadable_disclosure(&failures))
(table)
- h2 { "Define an Effect" }
- (add_form(&session))
+ div.card {
+ div.card-header { "Define an effect" }
+ (add_form(&session))
+ }
},
))
}
@@ -69,14 +71,14 @@
html! {
form method="post" action="/effects" {
(super::csrf_input(session))
- label { "name" input type="text" name="name"; }
+ label { "Name" input type="text" name="name"; }
label {
- "trigger"
+ "Trigger"
input type="text" name="trigger" placeholder="query.grammar trigger";
}
- label { "run" input type="text" name="run" placeholder="command to run"; }
+ label { "Run" input type="text" name="run" placeholder="command to run"; }
label {
- "toolchains"
+ "Toolchains"
input type="text" name="toolchains" placeholder="rust, node";
}
button type="submit" { "Define Effect" }
crates/cli/ents-web/src/pages/files.rs
@@ -343,7 +343,6 @@
});
html! {
div.card {
- div.card-header { "files" }
@if entries.is_empty() {
div.card-row.muted { "Empty directory." }
}
crates/cli/ents-web/src/pages/issues.rs
@@ -203,7 +203,7 @@
}
div.doc-body { (body) }
}
- details {
+ details.disclosure {
summary { "Edit state, assignees, labels" }
(edit_form(&session, &issue, &labels))
(super::members_datalist(&state))
@@ -217,8 +217,10 @@
} @else {
(crate::pages::comments::thread_section(&state, &session, &thread, &return_to))
}
- h2 { "Add a Comment" }
- (comment_form(&session, &id))
+ div.card {
+ div.card-header { "Add a comment" }
+ (comment_form(&session, &id))
+ }
}
},
))
@@ -412,14 +414,14 @@
html! {
form method="post" action="/issues" {
(super::csrf_input(session))
- label { "title" input type="text" name="title"; }
+ label { "Title" input type="text" name="title"; }
div {
- label { "state" }
+ label { "State" }
(state_picker("open"))
}
- label { "assignees" input type="text" name="assignees" placeholder="alice, bob" list="members"; }
+ label { "Assignees" input type="text" name="assignees" placeholder="alice, bob" list="members"; }
(label_picker(known_labels, &[]))
- label { "body" textarea name="body" {} }
+ label { "Body" textarea name="body" {} }
div.composer-buttons {
a.composer-cancel href="/issues" { "Cancel" }
button type="submit" { "Open Issue" }
@@ -437,11 +439,11 @@
form method="post" action="" {
(super::csrf_input(session))
div {
- label { "state" }
+ label { "State" }
(state_picker(&issue.state))
}
label {
- "assignees"
+ "Assignees"
input type="text" name="assignees" value=(join_members(&issue.assignees)) list="members";
}
(label_picker(known_labels, &issue.labels))
@@ -455,7 +457,7 @@
html! {
form method="post" action=(format!("/issues/{id}/comment")) {
(super::csrf_input(session))
- label { "body" textarea name="body" {} }
+ label { "Body" textarea name="body" {} }
button type="submit" { "Comment" }
}
}
@@ -585,7 +587,7 @@
fn label_picker(known: &[String], current: &[String]) -> Markup {
html! {
div {
- label { "labels" }
+ label { "Labels" }
@if !known.is_empty() {
div.picker {
@for label in known {
crates/cli/ents-web/src/pages/meta.rs
@@ -23,16 +23,16 @@
&super::RepoHeader::from_state(&state),
&super::identity_label(&state),
"/meta",
- "Meta",
+ "Repo & governance",
html! {
p.muted {
- "All project metadata -- members, effects, toolchains, "
- "redactions, and the adoption inbox -- lives in this "
+ "All project metadata — members, effects, toolchains, "
+ "redactions, and the adoption inbox — lives in this "
"repository as git objects."
}
div.card {
@for section in super::META_SECTIONS {
- div.card-row {
+ div.card-row.stack {
a href=(section.href) { (section.name) }
span { (section.blurb) }
}
crates/cli/ents-web/src/pages/toolchains.rs
@@ -70,8 +70,10 @@
html! {
(crate::render::unreadable_disclosure(&failures))
(listing)
- h2 { "Import a Toolchain" }
- (import_form(&session))
+ div.card {
+ div.card-header { "Import a toolchain" }
+ (import_form(&session))
+ }
},
))
}
@@ -82,9 +84,9 @@
html! {
form method="post" action="/toolchains" {
(super::csrf_input(session))
- label { "name" input type="text" name="name"; }
+ label { "Name" input type="text" name="name"; }
label {
- "recipe"
+ "Recipe"
textarea name="recipe"
placeholder="embedded <tree-oid>\nor:\ndownloaded\n<url> <sha256> <strip> [dest]" {}
}