fix(tabs): review fixes for the detailed rail — width-dialog default, compact wrap pass, rich-aware resets

Three review findings on the detailed-rows feature, all in its edge cases:

- The App Settings width select consulted the handheld defaults blob
  (tabRailWidth: 256) BEFORE the rich-aware default, which the renderer
  never reads — so a tablet's unsized rich rail rendered 320 while the
  dialog said 256, and a routine Save persisted the 256 (below the 288px
  tight threshold, permanently). The chain now mirrors
  applyTabRailWidth()'s actual resolution.

- _setTabRailWidth() re-rendered on a compact flip but never re-ran
  applyTabWrapSettings(), the one owner of the folder line, whose railRich
  input reads the compact class this function just toggled. A rich rail
  dragged below 240px kept emitting folder rows — persistently, for a
  stored width < 240, since the boot wrap pass runs before the class is
  first applied. The wrap pass now re-runs on the flip, with exactly one
  render either way.

- Both reset affordances (handle dblclick, Enter on the handle) reset to
  the hardcoded 256 even on a rich rail, landing it below the tight
  threshold; both now resolve the rich-aware default (320), via a new
  optional defaultWidth input on resolveTabRailKeyboardWidth().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-25 18:26:07 +02:00
parent 14de2b7012
commit 33f77c4680
4 changed files with 87 additions and 5 deletions
+3 -1
View File
@@ -188,7 +188,9 @@ function resolveTabRailKeyboardWidth(input = {}) {
let width;
if (input.key === 'Home') width = TAB_RAIL_MIN_WIDTH;
else if (input.key === 'End') width = TAB_RAIL_MAX_WIDTH;
else if (input.key === 'Enter') width = TAB_RAIL_DEFAULT_WIDTH;
// Enter resets to the caller's effective default (the rich rail's is the
// Wide preset, not 256 — see _defaultTabRailWidth); absent, the base default.
else if (input.key === 'Enter') width = Number(input.defaultWidth) || TAB_RAIL_DEFAULT_WIDTH;
else if (input.key === 'ArrowLeft' || input.key === 'ArrowRight') {
const direction = input.key === 'ArrowLeft' ? -1 : 1;
width = (Number(input.currentWidth) || TAB_RAIL_DEFAULT_WIDTH) + direction * (input.shiftKey ? 32 : 8);
+6 -2
View File
@@ -405,8 +405,12 @@ Object.assign(CodemanApp.prototype, {
const tabRailWidth = window.CodemanTabRail?.resolveWidth({
// Same default resolution as applyTabRailWidth(): a rail that has never
// been sized shows the width it is actually rendering at, which for
// detailed rows is the Wide preset rather than 256.
width: settings.tabRailWidth ?? defaults.tabRailWidth ?? this._defaultTabRailWidth?.() ?? 256,
// detailed rows is the Wide preset rather than 256. The rich-aware
// default must come BEFORE the per-device defaults blob: the handheld
// blob carries tabRailWidth: 256, which applyTabRailWidth() never reads,
// so consulting it first showed a tablet's unsized rich rail as 256 while
// it rendered at 320 — and a routine Save then PERSISTED the 256.
width: settings.tabRailWidth ?? this._defaultTabRailWidth?.() ?? defaults.tabRailWidth ?? 256,
}) ?? 256;
this.syncTabRailWidthSetting?.(tabRailWidth);
document.getElementById('appSettingsTabRailDetail').value =
+19 -2
View File
@@ -76,7 +76,21 @@ Object.assign(CodemanApp.prototype, {
// list is ordered by) and its pill intact. No re-render — unlike the rows
// themselves, this is a display toggle on markup that is already there.
root.classList.toggle('tab-rail-tight', resolved < 288);
if (wasCompact !== compact) this._fullRenderSessionTabs?.();
if (wasCompact !== compact) {
// The folder line is owned by applyTabWrapSettings(), whose railRich
// input reads the compact class this function just toggled — without
// re-running it, a rich rail dragged below 240px kept emitting folder
// rows (and, for a stored width < 240, kept them across reloads: the
// boot-time wrap pass runs before this function first applies the
// class). It re-renders only when the folder flag actually flipped, so
// cover the flip-without-folder-change case (a simple-detail rail
// crossing 240px still changes the row-action affordance) without
// rendering twice.
const prevTall = this._tallTabsEnabled;
this.applyTabWrapSettings?.();
const wrapRendered = prevTall !== undefined && this._tallTabsEnabled !== prevTall;
if (!wrapRendered) this._fullRenderSessionTabs?.();
}
const handle = document.getElementById('tabRailResizeHandle');
if (handle) {
handle.setAttribute('aria-valuemax', String(effectiveMax));
@@ -223,6 +237,7 @@ Object.assign(CodemanApp.prototype, {
key: event.key,
shiftKey: event.shiftKey,
currentWidth: this._getCurrentTabRailWidth(),
defaultWidth: this._defaultTabRailWidth?.(),
...this._getTabRailBounds(),
});
if (width === null || width === undefined) return;
@@ -265,7 +280,9 @@ Object.assign(CodemanApp.prototype, {
handle.addEventListener('dblclick', (event) => {
event.preventDefault();
this._claimTabRailResize();
const preferred = window.CodemanTabRail?.DEFAULT_WIDTH || 256;
// Rich-aware: resetting a detailed rail to 256 would land it below the
// 288px tight threshold and silently drop the created stamp.
const preferred = this._defaultTabRailWidth?.() ?? (window.CodemanTabRail?.DEFAULT_WIDTH || 256);
const effective = this._setTabRailWidth(preferred);
this._scheduleTabRailSettle(effective, preferred);
});