diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-10 10:29:12 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-10 10:29:12 +0200 |
| commit | c2bc79f71c553f3c8e76c592715266c8469e583e (patch) | |
| tree | 27e0a57231ed8b12518894526d0baee7621d261b /packages/meshbay-hub/src/meshbay_hub/static/files-app.js | |
| parent | 0b9b85bf2d434177968dfc6e27555d54d703ff7e (diff) | |
| download | meshbay-c2bc79f71c553f3c8e76c592715266c8469e583e.tar.gz | |
feat(hub): pin the app controls while a library scrolls
Files, Videos, Music and Photos are read by scrolling, and everything that
steers that reading left with the first screenful. Three bands now pin under
the navigation bar, in a group and on the Search page alike: the tab bar (the
search field, on Search), the application's own toolbar, and the file table's
column heads. The group's name and description still scroll — they say nothing
a reader needs while walking a directory, and the height they would cost is
height the list does not get.
A band's offset is the heights of the bands above it, and those are not
constants: the toolbar wraps to three rows on a phone, grows a field while a
folder is being named, and loses its filter on Search. So each band measures
itself and publishes `--chrome-h` / `--toolbar-h` (static/sticky.js) and the
stylesheet does the arithmetic in calc(), rather than a number written down
twice — the fault CLAUDE.md already records against this layout twice over.
A band publishes height *plus its own bottom margin*, and paints that margin
as a ring of page colour, so the pinned layout is pixel-identical to the flow
layout and nothing shifts at the moment a band pins.
Three overflow faults came out of it, all of the same class and all of them
what "the header does not stay" actually meant on Android — a document wider
than the screen leaves everything pinned attached to a viewport the reader can
no longer see, the navigation bar included:
- a directory's name cell was a bare <td>, so an unbreakable folder name
(`Rage_Against_The_Machine_Discography_1992-2000_FLAC`) set the column's
minimum: a 527px table in a 390px window
- Search's group column did the same at 442px with an underscored group
name. It also goes entirely below 768px, where there is no room for it and
the breadcrumb already names the group
- the shared-directories table has four columns of controls with a combined
minimum near 440px, none of it compressible. On a phone the row stops
being a row: the name and its eject/remove pair on one line, the two
switches — each carrying the column head's own string as a label — on the
next
- and, found by measuring at 360px, the tab bar itself was 19px too wide
`.file-table` moves to separated borders: a collapsed border belongs to the
table rather than to the cell, so the column heads lost their rule the moment
they pinned.
Measured, not read. tests/harness/sticky_header_probe.py drives the shipped
GroupPage and SearchPage against a stub node, walks to each application,
scrolls to the end and reports every rectangle — 11 views x 4 widths x 2
engines. Its fixture says what real data says: the first version used
`note-007.txt` and `un groupe`, which fit any screen, and found none of the
above. A fixture narrower than real data tests the fixture.
Also: `test_desktop_shell` no longer looks for the CSP after the first `-->`,
which made it fail on correct markup as soon as a comment was added above it,
and `search-page.js` joins test_hook_ordering's file list.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tx16FhyD2BUdpooGb5jcyN
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/files-app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/files-app.js | 26 |
1 files changed, 20 insertions, 6 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js index 65860ec..882a8fa 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js @@ -9,6 +9,7 @@ import { FILE_ICONS, formatSize, formatDate, canPreview, CHUNK_SIZE, MEMORY_CEILING, pipelinedDownload, downloadEntry, downloadDirectory as sharedDownloadDirectory, } from './file-utils.js'; +import { useStickyBand } from './sticky.js'; // ── Files ──────────────────────────────────────────────────────────────────── // @@ -35,6 +36,11 @@ function FilesPanel({ const [filter, setFilter] = useState(''); const [currentPath, setCurrentPath] = useState(''); const [refreshing, setRefreshing] = useState(false); + // The toolbar pins below the page's own band and tells the column heads how + // far down to pin. Its height is not a constant — it wraps to three rows on + // a phone and grows a field while a folder is being named — so it is + // measured rather than written down (sticky.js). + const toolbarBand = useStickyBand('--toolbar-h'); // Only the cross-group Search page shows this (`showRefresh`): it has no // live node connection pushing index deltas, so its file list really is @@ -406,7 +412,7 @@ function FilesPanel({ `} ${status === 'connected' && html` - <div class="file-toolbar"> + <div class="file-toolbar" ref=${toolbarBand}> <div class="toolbar-group"> ${currentPath && currentRootWritable && html` <label class="tb-btn primary"> @@ -495,7 +501,7 @@ function FilesPanel({ <th class="sortable" onClick=${() => toggleSort('size')}> ${t('group.col_size')} ${sortKey === 'size' ? (sortAsc ? '▲' : '▼') : ''} </th> - ${showGroup && html`<th>${t('search.col_group')}</th>`} + ${showGroup && html`<th class="td-group">${t('search.col_group')}</th>`} <th class="sortable th-type" onClick=${() => toggleSort('type')}> ${t('group.col_type')} ${sortKey === 'type' ? (sortAsc ? '▲' : '▼') : ''} </th> @@ -514,7 +520,7 @@ function FilesPanel({ <td>${'\u{1F4C1}'}</td> <td>..</td> <td class="file-size"></td> - ${showGroup && html`<td></td>`} + ${showGroup && html`<td class="td-group"></td>`} <td class="td-type"></td> <td class="td-date"></td> </tr> @@ -536,7 +542,15 @@ function FilesPanel({ onChange=${() => toggle(dirKey(d))} /> </td> <td>${isEjected ? '\u{23CF}' : isUnavail ? '\u{26A0}' : '\u{1F4C1}'}</td> - <td>${d}${isEjected ? html` + ${/* `file-name`, like a file's own name cell. Without it this + was a bare <td>, so a folder called + `Rage_Against_The_Machine_Discography_1992-2000_FLAC` — + one unbreakable word, which is how music libraries are + named — set the column's minimum width to the whole + string. Measured on a phone: a 527px table in a 390px + window, and on Android a page wider than the screen takes + every pinned header out of the visible area with it. */''} + <td class="file-name">${d}${isEjected ? html` <span class="root-offline"> ${t('group.root_ejected')}</span> ` : isUnavail ? html` <span class="root-offline"> ${t('group.root_unavailable')}</span> @@ -559,7 +573,7 @@ function FilesPanel({ }}>${isEjected ? '\u{1F50C}' : '\u{23CF}'}</button> ` : ''}</td> <td class="file-size">${inside.length ? formatSize(bytes) : ''}</td> - ${showGroup && html`<td></td>`} + ${showGroup && html`<td class="td-group"></td>`} <td class="td-type"></td> <td class="td-date"></td> </tr> @@ -582,7 +596,7 @@ function FilesPanel({ onClick=${() => { setFilter(''); setCurrentPath(e.path); }}>${e.path}</a>`} </td> <td class="file-size">${formatSize(e.size)}</td> - ${showGroup && html`<td> + ${showGroup && html`<td class="td-group"> <a href="#/group/${e.groupId}" class="badge">${e.groupName || ''}</a> </td>`} <td class="file-type td-type">${e.type}</td> |