diff --git a/src/components/GlanceRail.jsx b/src/components/GlanceRail.jsx
index fb0fe9f..87b0e1e 100644
--- a/src/components/GlanceRail.jsx
+++ b/src/components/GlanceRail.jsx
@@ -1,6 +1,17 @@
// GlanceRail — ALL loop stations expanded, always (task D-41, per
// docs/design/integrated-glance.md §4; supersedes the L-33 playhead accordion).
//
+// D-51 (docs/design/one-screen.md §4/§6.2) adapts the rail to the dashboard's
+// 500px right column. Margin-hardening arithmetic, worst case = a classic
+// Windows scrollbar (~17px) inside the column's overflow-y-auto wrapper:
+// row interior = 500 − 17 (scrollbar) − 18 (section border + p-2)
+// − 14 (row border + p-1.5) = 451px
+// guitar cell 89 (75 SVG + p-1.5 + border) → 4/line (374 ≤ 451; a 5th = 469 ✗)
+// piano 1-octave 156.4 / 2-octave 279.6 → the mixed pair 279.6+6+156.4 = 442
+// fits with ~9px margin even under the scrollbar (26px without) — the fit the
+// doc calls fragile at the old paddings is now robust. Without the scrollbar
+// the interior is 468px; every count above is unchanged.
+//
// One VERTICAL ROW per loop station, canonical KB order (the same order the
// banner's loop shows after rotation). Every row renders its full voicing
// gallery PERMANENTLY — the playhead HIGHLIGHTS the active row (accent ring +
@@ -149,7 +160,7 @@ function StationRow({
`${st.label}${st.rn ? ` (${st.rn})` : ''} — every ${instrument} voicing` +
`${isNow ? ', now playing' : ''}${isNext ? ', up next' : ''}`
}
- className={`rounded-lg border p-2 ${stateClass}`}
+ className={`rounded-lg border p-1.5 ${stateClass}`}
style={{ opacity: isNow || isFocused ? 1 : 0.85 }}
>
{/* ── Header line: identity + the folded roadmap education ── */}
@@ -190,11 +201,19 @@ function StationRow({
- {/* ── Gallery: own-voicing cell + the full dense browser; cells WRAP,
- never scroll horizontally (D-40 §4). ── */}
-
+ {/* ── Gallery: own-voicing cell FIRST (the recommended cell — full-accent
+ border reads as "the answer", one-screen.md §4; same size, no new
+ colour) + the full dense browser; cells WRAP, never scroll
+ horizontally (D-40 §4). Browser basis 320px: a guitar own-cell
+ (89px) still shares line one (89+6+320 = 415 ≤ 451) so the first
+ visual line shows play + 3 shapes = 4 across, while a piano
+ own-cell (≥156.4px) can never share it (156.4+6+320 = 482.4 > 468
+ even scrollbar-free) — the recommended voicing sits alone on line
+ one, prominence for free, and the gallery pairs at the FULL row
+ interior below it (the 279.6+156.4 mixed-pair fit needs all 451px). ── */}
+
{/* dense: the rail shows the mic-feedback microcopy once, below. */}
@@ -243,7 +262,7 @@ export default function GlanceRail({
return (
diff --git a/src/components/VoicingBrowser.jsx b/src/components/VoicingBrowser.jsx
index d907d7c..0002b01 100644
--- a/src/components/VoicingBrowser.jsx
+++ b/src/components/VoicingBrowser.jsx
@@ -46,8 +46,13 @@
// section chrome (border/panel background/heading — the row header
// already names the chord) and suppresses the per-mount
// mic-feedback microcopy (the rail shows it ONCE for the whole
-// rail, D-31 §2.5). Every pre-existing mount renders identically
-// with no prop.
+// rail, D-31 §2.5). D-51 (one-screen.md §4) additionally tightens
+// the DENSE cell spacing for the 500px right column — cell p-2 →
+// p-1.5 and gallery gap-2 → gap-1.5 — which hardens the column's
+// two-octave + one-octave piano pair fit (279.6 + 6 + 156.4 = 442
+// ≤ 451px row interior even with a classic Windows scrollbar in
+// the rail's scroller; it was 452 vs 456, a ~4px squeak, before).
+// Every pre-existing mount renders identically with no prop.
import { useEffect, useMemo, useRef } from 'react'
import ChordDiagram from './ChordDiagram'
@@ -126,9 +131,11 @@ function SectionHeading({ children }) {
// One gallery cell: label on top, diagram thumb, its own ▶ underneath.
// bg-surface inside the bg-panel section gives the cells a quiet inlay border;
// label is gray-300 on surface (AA comfortable at 11px semibold).
-function GalleryCell({ label, playLabel, onPlay, children }) {
+// `dense` (D-51): p-1.5 instead of p-2 — 4px off each cell's box width, part of
+// the right-column margin-hardening. Non-dense output is byte-identical.
+function GalleryCell({ label, playLabel, onPlay, dense = false, children }) {
return (
-
+
{guitarShapes.map((shape, i) => (
{/* pianoVoicing() output carries no rootPc, and without it VoicingPiano
falls back to the LOWEST voice for its "R" badge — wrong for rootless
@@ -271,6 +279,7 @@ export default function VoicingBrowser({ rootPc = 0, quality = 'maj', show = 'bo
label={voicing.label}
playLabel={`Play ${name} — ${voicing.label} piano voicing`}
onPlay={() => playPiano(voicing)}
+ dense={dense}
>