From 25b5217be6c4aadf1eebfd509e5b16ddc8a4bd78 Mon Sep 17 00:00:00 2001 From: lex00 <121451605+lex00@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:38:07 -0600 Subject: [PATCH] feat(web): zoom gets an on-screen picker again, top-left MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #73 moved every picker into the ⌘K palette and left the current value on the status strip — "hide controls, never state". That reads well and fails on first contact. The strip renders "zoom: components" next to "env: dev", which looks like a dropdown because it is a value sitting among other values, and clicking it does nothing: renderStatusbar() only ever sets textContent. The one hint that a control exists is a "⌘K" glyph at the far end of the header. Reported as "the zoom drop down isnt working, cant pick a thing", which is exactly what it looks like. Zoom gets a real and the palette keeps its entry. The other axes +// stay where #73 put them: zoom is the one you reach for constantly, and one +// visible control is not the header of selects that issue was about. +function renderZoomPicker() { + // #zoom-slot, top-left after the brand — not #pickers on the far right, + // where it sat beside the theme select and read as chrome rather than as the + // control. Falls back to #pickers so a static export built from older markup + // still gets a picker rather than none. + const slot = document.getElementById("zoom-slot") || document.getElementById("pickers"); + if (!slot) return; + let sel = document.getElementById("zoom-picker"); + if (!sel) { + sel = document.createElement("select"); + sel.id = "zoom-picker"; + sel.title = "Zoom — components, logical, composites, resources, attributes, runtime (also ⌘K)"; + sel.addEventListener("change", () => { + applyZoom(sel.value); + renderStatusbar(); + load(); + }); + // Before the theme picker, which mounts into the same slot at load. + slot.insertBefore(sel, slot.firstChild); + } + const current = zoomValue(); + // Rebuilt each render because runtime is only meaningful with an env: it + // descends below the declaration boundary to owner-referenced children, + // which exist in a cluster and never in your source. + const opts = ZOOM_OPTS.filter(([, v]) => v !== "runtime" || view.env); + const want = opts.map(([label, v]) => `${v}:${label}`).join("|") + "@" + current; + if (sel.dataset.built !== want) { + sel.innerHTML = ""; + for (const [label, v] of opts) { + const o = document.createElement("option"); + o.value = v; + o.textContent = label; + if (v === current) o.selected = true; + sel.appendChild(o); + } + sel.dataset.built = want; + } + sel.value = current; +} + function renderStatusbar() { + renderZoomPicker(); const el = document.getElementById("statusbar"); if (!el) return; - const zoomLabel = (ZOOM_OPTS.find(([, v]) => v === zoomValue()) || ["zoom: " + zoomValue()])[0]; - const parts = [zoomLabel, view.env ? `env: ${view.env}` : "env: (source)"]; + // The strip looks clickable whether or not it is, so make it act like it: + // clicking opens the palette rather than doing nothing. env, stack and tier + // live only there, and this is the only affordance pointing at them. + if (!el.dataset.clickable) { + el.dataset.clickable = "1"; + el.style.cursor = "pointer"; + el.title = "env · stack · tier — click, or ⌘K, to change"; + el.addEventListener("click", () => openPalette()); + } + // No zoom here any more. #73 put the current zoom on the strip because the + // control had moved into the palette; with a real picker two slots along, + // the same value in both places reads as two pickers, one of which does not + // work — which is exactly how it was reported. The strip keeps the axes that + // still have no on-screen control. + const parts = [view.env ? `env: ${view.env}` : "env: (source)"]; if (view.stack) parts.push(`stack: ${view.stack}`); if (axes.tier) parts.push(`tier: ${axes.tier}`); if (view.radial && !view.components && !view.logical) parts.push("radial"); diff --git a/web/index.html b/web/index.html index 5f76254..f09c9a0 100644 --- a/web/index.html +++ b/web/index.html @@ -101,9 +101,9 @@ #actions button:hover, #inspect button:hover { border-color: var(--pending); } #actions button.approve { border-color: var(--managed); color: var(--managed); } #inspect button { border-color: var(--foreign); color: var(--foreign); } - #pickers select { background: var(--panel); color: var(--fg); border: 1px solid var(--line); + #pickers select, #zoom-slot select { background: var(--panel); color: var(--fg); border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px; font-size: 12px; cursor: pointer; } - #pickers select:hover { border-color: var(--pending); } + #pickers select:hover, #zoom-slot select:hover { border-color: var(--pending); } /* The observe → reconcile → apply dial (M2, #54): where the selected target sits on the lifecycle progression. Its own row below the header — a target-level control, not a per-node inspect detail. */ @@ -177,6 +177,13 @@

behold

+ + loading…