git-ents.gitmain
⌘K
foforge
commit 013aea2
feat: make Settings a read-only projection over the typed meta refs

Settings now reads real values from refs/meta/config (General, topics), refs/meta/members (Members), and refs/meta/checks (Checks), and shows derived feature status instead of fake switches. Drop the stub Visibility radios and Danger-zone buttons, which backed no data, and their dead CSS.

feat: project config/members/checks into the Settings page refactor: show derived feature status instead of stub toggles Assisted-by: Claude:claude-opus-4-8

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/git-ents-server/src/web/pages.rs @@ -814,12 +814,13 @@ .map_err(|err| err.to_string()) } -/// The Settings tab. Persisting changes needs a config store that does not -/// exist yet, so the controls reflect the repository's current real values and -/// are presented read-only. +/// The Settings tab: a read-only projection over the repository's typed meta +/// refs — `refs/meta/config` (General), `refs/meta/members` (Members), and the +/// derived feature and check status. Editing is a members-gated write path that +/// does not exist yet, so the values are presented as the current configuration. pub(super) async fn settings_page(repo: &Path, meta: &RepoMeta) -> Markup { - let name = meta.name(); let signers = load_signers(repo).await; + let checks = load_checks(repo).await; repo_shell( meta, Tab::Settings, @@ -827,21 +828,28 @@ html! { div.settings { div.page-header { h1.page-title { "Repository settings" } } - p.shell-note { "These reflect the repository's current configuration." } + p.shell-note { + "The repository's configuration on " code { "refs/meta/config" } + " and " code { "refs/meta/members" } "." + } div.card { div.card-header { "General" } - div.field { - label { "Repository name" } - input type="text" value=(name) disabled title="Not editable yet"; - } - div.field { - label { "Description" } - textarea rows="2" disabled title="Not editable yet" { (meta.description.as_deref().unwrap_or_default()) } - } - div.field { - label { "Default branch" } - input type="text" value=(meta.branch.as_deref().unwrap_or("—")) disabled title="Not editable yet"; + (setting_row("Repository name", meta.name())) + (setting_row("Description", meta.description.as_deref().unwrap_or("—"))) + (setting_row("Homepage", meta.homepage.as_deref().unwrap_or("—"))) + (setting_row("Default branch", meta.branch.as_deref().unwrap_or("—"))) + div.card-row { + span.setting-label { "Topics" } + @if meta.topics.is_empty() { + span.muted { "—" } + } @else { + div.topics { + @for topic in &meta.topics { + span.topic { (topic) } + } + } + } } } @@ -849,8 +857,7 @@ div.card-header { "Features" } (feature_row("Bug reports", "Track and triage bugs.", meta.issues > 0)) (feature_row("Releases", "Publish tagged releases.", meta.releases > 0)) - (feature_row("Checks (CI)", "Run signed CI records on push.", false)) - (feature_row("Wiki", "A separate documentation space.", false)) + (feature_row("Checks (CI)", "Run signed CI records on push.", matches!(&checks, Ok(c) if !c.is_empty()))) } div.card { @@ -878,26 +885,24 @@ } div.card { - div.card-header { "Visibility" } - (visibility_row("Public", "Anyone can read this repository.", true)) - (visibility_row("Private", "Only collaborators can read it.", false)) - } - - div.card.danger { - div.card-header { "Danger zone" } - div.danger-row { - div { - strong { "Archive this repository" } - p.muted { "Make it read-only." } - } - button.btn-danger-outline type="button" disabled title="Not available yet" { "Archive" } + div.card-header { + "Checks" + @if let Ok(checks) = &checks { span.count { (checks.len()) } } } - div.danger-row { - div { - strong { "Delete this repository" } - p.muted { "This cannot be undone." } + p.shell-note { + "Commands on " code { "refs/meta/checks" } " run against each push " + "(" code { "git ents checks list" } ")." + } + @match &checks { + Err(err) => div.card-row.muted { "Could not read checks: " (err) } + Ok(checks) if checks.is_empty() => { + div.card-row.muted { "No checks configured." } + } + Ok(checks) => { + @for check in checks { + (check.render()) + } } - button.btn-danger type="button" disabled title="Not available yet" { "Delete" } } } } @@ -915,7 +920,18 @@ .map_err(|err| err.to_string()) } -/// A Features row with a static toggle reflecting `on`. +/// A read-only setting row: a label and its current value. +fn setting_row(label: &str, value: &str) -> Markup { + html! { + div.card-row { + span.setting-label { (label) } + span.muted { (value) } + } + } +} + +/// A Features row showing the derived, read-only status of `title`: active when +/// the feature has backing data, empty otherwise. fn feature_row(title: &str, desc: &str, on: bool) -> Markup { html! { div.feature-row { @@ -923,20 +939,7 @@ strong { (title) } p.muted { (desc) } } - span.toggle.on[on].stub title="Not editable yet" { span.knob {} } - } - } -} - -/// A Visibility radio row, selected when `on`. -fn visibility_row(title: &str, desc: &str, on: bool) -> Markup { - html! { - div.visibility-row.sel[on] { - span.radio.on[on].stub title="Not editable yet" {} - div { - strong { (title) } - p.muted { (desc) } - } + span.feature-status.on[on] { @if on { "Active" } @else { "Empty" } } } } }
crates/git-ents-server/src/web/style.css @@ -323,33 +323,13 @@ .subtab.active .icon { color: var(--s-func); } .settings { max-width: 46rem; } -.field { padding: .85rem 1.1rem; } -.field + .field { border-top: 1px solid var(--color-border); } -.field label { display: block; font-size: .82rem; font-weight: 600; margin-bottom: .35rem; } -.field input, .field textarea, .field select { width: 100%; font-family: var(--font-sans); font-size: .9rem; color: var(--color-text); background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 8px; padding: .5rem .7rem; resize: vertical; } -.field input:focus, .field textarea:focus { outline: none; border-color: var(--color-accent); } -.field input[readonly], .field textarea[readonly] { color: var(--color-text-muted); } -.feature-row, .danger-row { display: flex; align-items: center; gap: 1rem; padding: .85rem 1.1rem; } -.feature-row + .feature-row, .danger-row + .danger-row { border-top: 1px solid var(--color-border); } -.feature-row > div:first-child, .danger-row > div:first-child { flex: 1; } -.feature-row p, .danger-row p { font-size: .82rem; margin: .1rem 0 0; } -.toggle { flex-shrink: 0; position: relative; width: 42px; height: 24px; border-radius: var(--radius-pill); background: var(--color-border); transition: background .18s; } -.toggle .knob { position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: left .18s; } -.toggle.on { background: var(--color-accent); } -.toggle.on .knob { left: 21px; } -.visibility-row { display: flex; align-items: flex-start; gap: .7rem; padding: .85rem 1.1rem; } -.visibility-row + .visibility-row { border-top: 1px solid var(--color-border); } -.visibility-row.sel { background: var(--color-accent-subtle); } -.visibility-row p { font-size: .82rem; margin: .1rem 0 0; } -.radio { flex-shrink: 0; margin-top: .2rem; width: 16px; height: 16px; border-radius: 50%; border: 1px solid var(--color-border); position: relative; } -.radio.on { border-color: var(--color-accent); } -.radio.on::after { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--color-accent); } -.card.danger { border-color: var(--s-keyword); } -.card.danger .card-header { color: var(--s-keyword); } -.btn-danger-outline, .btn-danger { font-family: var(--font-sans); font-size: .82rem; font-weight: 600; border-radius: var(--radius-sm); padding: .4rem .9rem; cursor: pointer; flex-shrink: 0; } -.btn-danger-outline { color: var(--s-keyword); background: transparent; border: 1px solid var(--s-keyword); } -.btn-danger-outline:hover { background: var(--diff-del); } -.btn-danger { color: #fff; background: var(--s-keyword); border: none; } +.setting-label { flex-shrink: 0; min-width: 11rem; font-weight: 600; font-family: var(--font-sans); } +.feature-row { display: flex; align-items: center; gap: 1rem; padding: .85rem 1.1rem; } +.feature-row + .feature-row { border-top: 1px solid var(--color-border); } +.feature-row > div:first-child { flex: 1; } +.feature-row p { font-size: .82rem; margin: .1rem 0 0; } +.feature-status { flex-shrink: 0; font-family: var(--font-mono); font-size: .76rem; color: var(--color-text-muted); background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-pill); padding: .2rem .7rem; } +.feature-status.on { color: var(--color-bg); background: var(--color-accent); border-color: var(--color-accent); } .site-footer { border-top: 1px solid var(--color-border); color: var(--color-text-muted); font-size: .8rem; margin-top: auto; } .footer-inner { max-width: var(--max-width); margin: 0 auto; padding: 2rem 1.5rem; text-align: center; }