Public: anyone can read this; the crew can change it.
A board view's manage controls draw §5's bar and btn, and .bmanage leaves the markup #4287
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4199-a-board-view-s-manage-controls-draw-5-s → main 2 commits fork 2 threads · 2 unresolved
2 files
changed +31
−14
src/web/pages/boards-view.ts +16 −14
| @@ -140,24 +140,26 @@ function pinForm(slug: string, title: string, pinned: boolean): Html { | ||
| 140 | 140 | } |
| 141 | 141 | |
| 142 | 142 | function manage(slug: string, board: Board, adding: Html, pins: ReturnType<typeof listPins>): Html { |
| 143 | const one = (p: Panel, i: number) => html`<li class="row bmanage"> | |
| 144 | <details> | |
| 145 | <summary><strong>${p.title}</strong> <span class="muted small">${CHART_NAMES[p.chart]} · ${String(p.w)}×${String(p.h)}</span></summary> | |
| 143 | const one = (p: Panel, i: number) => html`<li class="row"> | |
| 144 | <details class="row-main"> | |
| 145 | <summary><strong class="break">${p.title}</strong> <span class="muted small">${CHART_NAMES[p.chart]} · ${String(p.w)}×${String(p.h)}</span></summary> | |
| 146 | 146 | ${panelForm(slug, "edit", p, i, queryFields(paramsOf(p.query, p.chart)))} |
| 147 | 147 | </details> |
| 148 | ${pinForm(slug, p.title, isPinned(pins, slug, p.title))} | |
| 149 | <form method="post" action="/boards/${slug}/move" class="row-actions"> | |
| 150 | <input type="hidden" name="index" value="${String(i)}" /> | |
| 151 | ${btn("Up", { size: "sm", name: "by", value: "-1", label: `Move ${p.title} up` })} | |
| 152 | ${btn("Down", { size: "sm", name: "by", value: "1", label: `Move ${p.title} down` })} | |
| 153 | </form> | |
| 154 | <form method="post" action="/boards/${slug}/remove"> | |
| 155 | <input type="hidden" name="index" value="${String(i)}" /> | |
| 156 | ${btn("Remove", { size: "sm", variant: "danger", label: `Remove ${p.title}` })} | |
| 157 | </form> | |
| 148 | <div class="row-trail bar"> | |
| 149 | ${pinForm(slug, p.title, isPinned(pins, slug, p.title))} | |
| 150 | <form method="post" action="/boards/${slug}/move" class="row-actions"> | |
| 151 | <input type="hidden" name="index" value="${String(i)}" /> | |
| 152 | ${btn("Up", { size: "sm", name: "by", value: "-1", label: `Move ${p.title} up` })} | |
| 153 | ${btn("Down", { size: "sm", name: "by", value: "1", label: `Move ${p.title} down` })} | |
| 154 | </form> | |
| 155 | <form method="post" action="/boards/${slug}/remove"> | |
| 156 | <input type="hidden" name="index" value="${String(i)}" /> | |
| 157 | ${btn("Remove", { size: "sm", variant: "danger", label: `Remove ${p.title}` })} | |
| 158 | </form> | |
| 159 | </div> | |
| 158 | 160 | </li>`; |
| 159 | 161 | return html`${card( |
| 160 | html`<ul class="list">${board.panels.map(one)}</ul>`, | |
| 162 | html`<ul class="list setting-list">${board.panels.map(one)}</ul>`, | |
| 161 | 163 | { head: html`<h2>Panels</h2>`, pad: false }, |
| 162 | 164 | )} |
| 163 | 165 | ${when( |
test/boards-store.test.ts +15
| @@ -168,6 +168,21 @@ describe("/boards", () => { | ||
| 168 | 168 | expect(body).not.toContain('data-live-src="/boards'); |
| 169 | 169 | }); |
| 170 | 170 | |
| 171 | test("manage controls draw §5's row and bar, not .bmanage, and keep every form", async () => { | |
| 172 | const body = await (await asAdmin("/boards/fleet-health")).text(); | |
| 173 | expect(body).not.toContain("bmanage"); | |
| 174 | expect(body).toContain('class="row-trail bar"'); | |
| 175 | expect(body).toContain('class="list setting-list"'); | |
| 176 | expect(body).toContain('action="/boards/pin-panel"'); | |
outdated
Simplify: the ternary builds one odd path. Assert the pin-panel action separately and the board-scoped ones in the loop. Add an assertion tied to the wrapping rule once it exists. |
||
| 177 | for (const op of ["move", "remove", "rename", "settings", "duplicate", "delete"]) { | |
| 178 | expect(body).toContain(`action="/boards/fleet-health/${op}"`); | |
| 179 | } | |
| 180 | for (const word of ["Panels", "Add a panel", "Up", "Down", "Remove", "Pin to home", "Save settings", "Duplicate", "Delete this board"]) { | |
| 181 | expect(body).toContain(word); | |
| 182 | } | |
| 183 | expect(body).toContain('<strong class="break">'); | |
| 184 | }); | |
| 185 | ||
| 171 | 186 | test("settings turn on refresh, and the grid answers alone with an etag", async () => { |
| 172 | 187 | const saved = await post("/boards/fleet-health/settings", { range: "24h", refresh: "10s" }); |
| 173 | 188 | expect(saved.status).toBe(303); |
new above
offline
This .row-trail.bar is flex:none inside a nowrap .row, so at 360px it keeps ~330px and crushes .row-main (title and edit form). Add the .setting-list-style rule (flex-basis:100% for the trail under 640px, main with flex:1 1 18rem) so the controls drop below the title.