fix(directory-picker-browse): bot round 1 — pill cascade+corner, slow-scan close reset, asymmetry+calibration recorded

- .loadingFloat moved after the .status/.error block (its padding was
  losing the same-specificity race) and re-anchored bottom-right: the
  truncated/error rows own the bottom left and keep rendering through a
  scan, so the pill can never cover them; confirmCreate's relist now
  clears the stale failure text like every other scan launch.
- The close edge resets loading, so the slow-scan effect disarms while
  hidden and a reopened dialog waits out a fresh silence window
  (regression test added).
- The truncated note's survival through a scan is now asserted in the
  slow-scan test; the wait-bound test moved to fake timers with the
  200ms bound explicit.
- select()'s exemption from the one-frame rule and the constants' local
  calibration premise are recorded in JSDoc and the capability-seam
  Agent Note; the themed-scrollbars note's rebinding enumeration is
  replaced by a pointer to the mechanical gate (it had drifted twice).
  Both pairs re-recorded.
This commit is contained in:
creatixchu
2026-07-30 12:53:07 +08:00
parent 6ee2752cca
commit 3ba25e25c0
9 changed files with 133 additions and 59 deletions

View File

@@ -159,19 +159,6 @@
padding: 16px 16px 16px 24px;
}
/* The slow-scan indicator floats over the content's bottom-left on the card
* background instead of occupying a row: a scan must never shift the
* columns' height, and the stale view keeps rendering beneath it (it only
* appears at all once a scan outlives SLOW_SCAN_DELAY_MS). */
.loadingFloat {
position: absolute;
left: 24px;
bottom: 8px;
padding: 2px 8px;
border-radius: 6px;
background: var(--dsw-alias-bg-layer-2);
}
/* Columns split the row evenly around the divider (a solo column takes the
* whole row); 256px is the floor below which the row scrolls (scrollbar
* hidden, the effect pins the child pane into view) instead of squeezing
@@ -267,6 +254,23 @@
color: var(--dsw-alias-state-error-primary);
}
/* The slow-scan indicator floats over the content's bottom-RIGHT corner on
* the card background instead of occupying a row: a scan must never shift
* the columns' height, and the stale view keeps rendering beneath it (it
* only appears at all once a scan outlives SLOW_SCAN_DELAY_MS). Right,
* not left: the truncated/error status rows flow at the bottom LEFT and
* stay on screen through a scan, so the opposite corner keeps both
* legible. After .status in the cascade — the element carries both
* classes and this padding must win the same-specificity race. */
.loadingFloat {
position: absolute;
right: 16px;
bottom: 8px;
padding: 2px 8px;
border-radius: 6px;
background: var(--dsw-alias-bg-layer-2);
}
/* Footer: l3 separator on top, symmetric padding so the row sits vertically
* centered in the bar; New-folder and the show-hidden toggle pin left. */
.footerBar {

View File

@@ -331,7 +331,15 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
const pathInputRef = useRef<HTMLInputElement | null>(null)
const editZoneRef = useRef<HTMLButtonElement | null>(null)
/** Select a row of the listed level and preview its children on the right. */
/**
* Select a row of the listed level and preview its children on the right.
* Deliberately NOT one-frame like navigate(): a pick's first duty is the
* immediate selected state on the clicked row, and the pane split IS that
* feedback (aria-current pill, crumbs following the selection) — holding
* it back for the child listing would make clicks feel dropped. The quiet
* rule governs whole-view replacement, where nothing acknowledges the
* click but the swap itself.
*/
const select = useCallback((entry: DirectoryEntry) => {
const { seq, scan } = launchListing(entry.path)
// A pick while the path editor is open adopts the (filtered) row and
@@ -402,6 +410,11 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
return
}
supersede()
// Closing mid-scan leaves nothing to load: without this edge the
// slow-scan effect keeps arming while hidden and the reopened dialog
// would show the indicator on its first frame instead of waiting out a
// fresh silence window (reopen's navigate() produces no loading edge).
setLoading(false)
setError(null)
setPathDraft(null)
setFolderDraft(null)
@@ -438,6 +451,10 @@ export function DirectoryBrowser({ open, listDirectory, createDirectory, onOpen,
// create target becomes the listed level and the new folder its selection.
const { seq, scan } = launchListing(targetPath)
setLoading(true)
// Symmetric with navigate/select: a launched scan clears the stale
// failure text (and keeps the floating indicator's corner the only
// occupant of the content's right edge while it shows).
setError(null)
scan.then((level) => {
/* v8 ignore next -- same fence as navigate/select; the modal blocks superseding input */
if (seq !== requestSeq.current) return