mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-17 14:08:19 +00:00
feat(stack-files): cap directory listings at 1000 + add file-tree filter (#1208)
* feat(stack-files): cap directory listings at 1000 + add file-tree filter
The file-tree route returned every entry in a directory unbounded.
A logs/ or data/ subfolder with rotated artifacts could produce a
multi-megabyte response and a frontend cap at 500 entries silently
hid the rest with no way for the user to find a specific file.
The list route now caps the response at 1000 entries (the audit's
recommended bound), advertises the unfiltered total via
X-Total-Count, and sets X-Truncated when truncation happened. The
service exposes both a bare-array listStackDirectory (unchanged
contract for callers that just want the array) and a paginated
listStackDirectoryPage that returns {entries, total, truncated}.
The FileTree now offers a search input above the scroll area that
filters loaded entries by name (case-insensitive substring). Clearing
the filter restores the full listing. A non-matching filter shows a
short hint instead of an empty pane. The client-side MAX_ENTRIES
matches the server cap so a perfectly-sized directory never shows
the truncation hint.
* fix(stack-files): filter keeps parent dirs when loaded descendants match
The original filter applied per-render-level inside renderEntries, so a
parent directory whose name did not match was filtered out even when one
of its already-loaded children did. The match was then unreachable: the
parent had been removed from the visible list and its children never got
a chance to render.
Compute matching-descendant once per directory by walking the loaded
dirContents map (no extra fetch, bounded by what the user already
expanded). Keep ancestors of any match in the visible list. Auto-expand
those ancestors for the duration of the filter so the match comes into
view without a manual click on every parent.
Filter scope is still 'what is already loaded'; unexpanded subtrees do
not contribute to ancestor-keep until the user expands them. Two new
tests pin both behaviours.
This commit is contained in:
@@ -613,8 +613,25 @@ export class FileSystemService {
|
||||
}
|
||||
|
||||
async listStackDirectory(stackName: string, relPath: string): Promise<FileEntry[]> {
|
||||
const page = await this.listStackDirectoryPage(stackName, relPath, {});
|
||||
return page.entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pagination-aware variant. Returns the sorted entries (optionally truncated
|
||||
* to `limit`) along with the unfiltered `total` so the route can advertise
|
||||
* how much was elided. Callers that just want the unbounded array should
|
||||
* keep using listStackDirectory; the route uses this variant to cap the
|
||||
* payload for unusually large directories without losing the count.
|
||||
*/
|
||||
async listStackDirectoryPage(
|
||||
stackName: string,
|
||||
relPath: string,
|
||||
opts: { limit?: number },
|
||||
): Promise<{ entries: FileEntry[]; total: number; truncated: boolean }> {
|
||||
const safePath = await this.resolveSafeStackPath(stackName, relPath);
|
||||
const dirents = await fsPromises.readdir(safePath, { withFileTypes: true });
|
||||
const total = dirents.length;
|
||||
|
||||
const entries = await Promise.all(
|
||||
dirents.map(async (dirent): Promise<FileEntry> => {
|
||||
@@ -643,11 +660,16 @@ export class FileSystemService {
|
||||
})
|
||||
);
|
||||
|
||||
return entries.sort((a, b) => {
|
||||
const sorted = entries.sort((a, b) => {
|
||||
if (a.type === 'directory' && b.type !== 'directory') return -1;
|
||||
if (a.type !== 'directory' && b.type === 'directory') return 1;
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
});
|
||||
|
||||
if (opts.limit !== undefined && sorted.length > opts.limit) {
|
||||
return { entries: sorted.slice(0, opts.limit), total, truncated: true };
|
||||
}
|
||||
return { entries: sorted, total, truncated: false };
|
||||
}
|
||||
|
||||
async readStackFile(
|
||||
|
||||
Reference in New Issue
Block a user