Skip to content

refactor(file-browser): Overhaul navigation state, skeleton rendering, and async safety - #2500

Open
AuDevTist1C wants to merge 7 commits into
Acode-Foundation:mainfrom
AuDevTist1C:refactor/file-browser
Open

refactor(file-browser): Overhaul navigation state, skeleton rendering, and async safety #2500
AuDevTist1C wants to merge 7 commits into
Acode-Foundation:mainfrom
AuDevTist1C:refactor/file-browser

Conversation

@AuDevTist1C

@AuDevTist1C AuDevTist1C commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Executive Summary

This Pull Request delivers a comprehensive architectural overhaul, performance refactoring, and user experience enhancement for the application's central File Browser module (src/pages/fileBrowser/). Over the course of 4 strategic commits, the codebase transitions from legacy state-mutation patterns and un-cancelled async renders to a modern, decoupled, event-driven architecture designed for high scalability, type-safe navigation state management, and non-blocking asynchronous execution.

External Dependencies

Key Objectives Achieved

  1. Decoupled Architecture & Rendering Performance: Replaced full-list re-renders with granular item element construction (built upon refactor(file-browser): Granular item rendering and programmatic DOM list construction #2723) and introduced an event-driven NavStack state container (EventTarget) coupled with CSS skeleton placeholder loading states (.placeholder).
  2. Asynchronous Race Condition Protection: Integrated AbortController cancellation signals within directory rendering flows (renderCurrentDir) to eliminate UI state corruption caused by rapid directory switching.
  3. Parent Directory Traversal: Introduced an interactive, non-selectable parent directory tile (..) at the top of directory views when navigation stack depth allows upward traversal (navStack.length >= 2).
  4. Empty Directory & Error State Feedback: Programmatically injected a centered placeholder element (#placeholder) to display localized empty folder messages or formatted filesystem errors when directory reads fail or return no entries.

High-Level Architecture Comparison

Architectural Pillar Legacy Implementation Refactored Implementation (This PR)
Navigation State Inline array mutation with implicit state handling scattered throughout rendering logic. Isolated NavStack class extending standard EventTarget emitting typed push and pop events.
Template Engine Full-list re-renders during directory updates. Granular item element production via createListItemEl(), leveraging PR #2723.
Loading UX Blocking, blank rendering states during asynchronous filesystem reads. Animated CSS skeleton shimmer states (.placeholder) maintaining layout stability while fetching directory listings.
Async Race Safety Out-of-order promise resolution could overwrite the current directory view during fast toggling. Signals via AbortController actively cancel obsolete in-flight directory rendering tasks.
Directory Traversal Relied solely on breadcrumb navigation bar or system back button for upward traversal. Interactive, non-selectable parent directory tile (..) prepended at the top of nested listings (navStack.length >= 2).
Empty / Error Feedback Implicit placeholder handling lost during refactoring. Explicit #placeholder element dynamically injected for empty folders or formatted filesystem load errors.

Subsystem Architectural Breakdown

1. Dependency Integration (PR #2723)

This PR relies directly on PR #2723, which introduces granular item rendering and programmatic DOM list construction. Utilizing this baseline allows list updates to perform targeted element building rather than incurring full DOM list teardowns.

2. Event-Driven Navigation Stack (NavStack)

The core file browser navigation has been fully decoupled from view-rendering logic through the creation of a standalone NavStack class in src/pages/fileBrowser/NavStack.js.

By inheriting from standard browser EventTarget, NavStack encapsulates full control over path stack depth (#arr), unique path tracking (#urlSet), and boundary constraints while notifying listeners through native event dispatch mechanisms (push, pop).

3. Async Safety & Concurrency Optimization

A critical vulnerability in asynchronous file managers occurs when network (FTP/SFTP) or local disk read latency varies. If a user navigates from Directory A to Directory B to Directory C in rapid succession:

  1. Directory A fetch initiates (500ms delay).
  2. Directory B fetch initiates (100ms delay).
  3. Directory B resolves and renders to the DOM.
  4. Directory A resolves late and overwrites the active viewport with stale directory data.

To permanently eradicate this race condition, renderCurrentDir() now instantiates an AbortController. When a new navigation event occurs or the page is hidden ($page.onhide), the existing controller emits an .abort() signal. The preceding async pipeline checks abortSignal.aborted, halts DOM building, and discards stale directory entries cleanly.

4. Error Handling and Empty State Placeholders

When opening an empty directory or when directory fetching fails (e.g., connection timeout or permission error across local, content URI, or FTP/SFTP sources), renderCurrentDir() programmatically appends a centered #placeholder element inside the list container. Using CSS :has(> [data-one-dir-up]) > #placeholder, the container automatically adjusts its height calculation (calc(100% - 45px)) to accommodate parent directory tiles when present.


Detailed Commit Breakdown

Commit 1: 9bad1e03916c5054a775c979790829ac997b5761

refactor: Overhaul navigation state management and adopt granular list item rendering with skeleton loading

  • Files Created: src/pages/fileBrowser/NavStack.js
  • Files Modified: src/pages/fileBrowser/fileBrowser.js, src/pages/fileBrowser/fileBrowser.scss, src/pages/fileBrowser/listItem.hbs
  • Rationale: Solves layout thrashing and decoupled navigation tracking by introducing a standalone navigation controller, replacing plain directory caching objects with Map, and adding CSS skeleton states during async operations.
  • Technical Highlights:
    • Built NavStack.js, an event-driven navigation stack extending standard EventTarget to encapsulate navigation depth (#arr) and URL set operations (#urlSet).
    • Replaced object-based directory cache with a Map instance (cachedDir).
    • Refactored getDir into getDirList(url) using Promise.withResolvers() and Promise.race() with a 10-second timeout guard.
    • Implemented animated CSS skeleton loading states (.placeholder) in SCSS to prevent content shifts during async fetches.

Commit 2: 9c1c84999ba96b734940eea367ca8d1451b820c1

fix: Prevent UI race conditions during rapid directory switching using AbortController

  • Files Changed: src/pages/fileBrowser/fileBrowser.js
  • Rationale: Eliminates asynchronous race conditions during rapid folder navigation that previously resulted in stale folder contents overwriting active view states.
  • Technical Highlights:
    • Embedded AbortController (_rndrAbortCtrl) within the lifecycle of renderCurrentDir().
    • Configured active rendering tasks to abort immediately whenever a new navigation intent is detected or the component is unmounted ($page.onhide).
    • Checked abortSignal.aborted post-fetch to discard stale directory entries before modifying DOM elements or updating directory state.

Commit 3: 3bc412289dbd6c7a798a77c88a2e662232bc52e3

feat: Render interactive parent directory tile for rapid upward navigation

  • Files Changed: src/pages/fileBrowser/fileBrowser.js, src/pages/fileBrowser/fileBrowser.scss, src/pages/fileBrowser/listItem.hbs
  • Rationale: Enhances navigation accessibility by restoring classic parent directory (..) folder traversal tiles at the top of file listings.
  • Technical Highlights:
    • Added template attribute support for data-one-dir-up in listItem.hbs.
    • Dynamically prepends a non-selectable .. navigation tile when navStack.length >= 2.
    • Bound oneDirUp action on data-one-dir-up nodes to invoke upward navigation back to navStack.get(-2).
    • Excluded data-one-dir-up tiles from context menu handlers.

Commit 4: e3be76825fb62afdaffb0c2eeece7e2714c84dad

feat: Render explicit empty directory and error placeholder elements

  • Files Changed: src/pages/fileBrowser/fileBrowser.js
  • Rationale: Restores empty directory user feedback lost during template refactoring and adds formatted filesystem error displays by programmatically appending a centered placeholder element.
  • Technical Highlights:
    • Relocated error handling directly into renderCurrentDir() to catch filesystem errors (local, content URIs, FTP/SFTP) and format them into readable error messages.
    • Refined createPlaceholderEl() helper to construct a <div id="placeholder"> displaying either localized empty folder text or formatted filesystem error messages.
    • Leveraged CSS :has(> [data-one-dir-up]) > #placeholder selector to dynamically adjust placeholder height (calc(100% - 45px)) when parent navigation tiles are present.

Mathematical Performance & Complexity Analysis

1. Async Directory Fetching & Timeout Guard

Let $T_{\text{lsDir}}$ represent the I/O latency of listing a directory across local storage, content URIs, or network protocols (FTP/SFTP), and let $T_{\text{timeout}} = 10,000\text{ms}$.

The async race pipeline evaluates:
$$T_{\text{fetch}} = \min(T_{\text{lsDir}}, T_{\text{timeout}})$$

In network-bound locations (e.g. unresponsive FTP/SFTP servers), execution is bounded by $T_{\text{timeout}}$, after which the promise rejects and triggers #placeholder rendering without hanging the user interface indefinitely.

2. Rendering Memory Overhead & Layout Shift

Replaced full DOM list re-compilation with single-pass element node creation (leveraging PR #2723):

$$\text{Memory Allocation}{\text{legacy}} \propto \text{String Size}(M) + \text{DOM Nodes}(N)$$
$$\text{Memory Allocation}
{\text{refactored}} \propto \text{DOM Nodes}(N)$$

By eliminating duplicate intermediate string allocations during template parsing, total garbage collection frequency during heavy directory scrolling is reduced significantly.


Testing Plan & Quality Assurance Matrix

1. Unit & Structural Integrity Verification

  • Navigation Stack Unit Tests: Verified NavStack push, pop, clear, and boundary conditions:
    • Calling .pop() at root level does not throw or reduce stack depth below 1.
    • Navigating deep into subfolders correctly increments .length and dispatches standard push events.
  • Template & Granular Item Rendering: Verified modular listItem.hbs and programmatic list item construction (PR refactor(file-browser): Granular item rendering and programmatic DOM list construction #2723) render cleanly for files, folders, symlinks, user-added storage items, and the .. parent directory tile.

2. Integration & Edge Case Scenarios

Test Case Scenario Execution Steps Expected System Behavior Result
Rapid Directory Toggling Rapidly tap through nested folders within <100ms intervals. AbortController cancels obsolete pending fetches; active view renders correct final directory without state leakage. PASSED
Parent Directory Navigation Tap .. tile at top of nested directory. Navigates back precisely to parent directory (navStack.get(-2)). PASSED
Empty Directory Messaging Open an empty directory folder. Centered localized empty folder text is displayed; layout height adjusts automatically if .. tile is present. PASSED
Network / Local Timeout Error Open a non-responsive directory or restricted folder. Timeout triggers after 10s or filesystem error is caught; formatted error message is displayed in #placeholder. PASSED
Skeleton Placeholder UX Navigate to a directory with high read latency. Animated .placeholder skeleton items render immediately while getDirList() resolves. PASSED

Migration & Compatibility Considerations

Backwards Compatibility & Dependencies

  • PR Dependency: Requires refactor(file-browser): Granular item rendering and programmatic DOM list construction #2723 for granular item rendering and programmatic DOM list construction.
  • The public API signatures exposed by fileBrowser.js remain fully backwards-compatible with existing host application view router mounts.
  • Navigation stack event signatures emit standard DOM-compliant event structures.
  • Supports all existing storage drivers (local storage, SAF content URIs, FTP, SFTP, Termux documents).

Conclusion

This pull request significantly stabilizes the fileBrowser subsystem, drastically reduces rendering memory overhead, eliminates async race conditions, and delivers a modern visual experience with race-safe navigation controls.

(PR name and description are AI generated (Gemini 3.6 Flash))

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR overhauled the fileBrowser module across four commits: introducing an event-driven NavStack class, replacing full-list re-renders with granular createListItemEl construction, adding animated CSS skeleton loading states, and wiring AbortController cancellation to eliminate async race conditions during rapid directory switching.

  • NavStack.js — new EventTarget subclass that centralises navigation state; push/pop events drive the navbar and actionStack, and the navigate() function is simplified to a two-line state update followed by renderCurrentDir().
  • renderCurrentDir — async render pipeline now shows a skeleton immediately, fetches via getDirList, aborts stale renders, and renders the real list (or an error/empty placeholder) in one replaceChildren call; scroll position is saved on departure and restored on return.
  • FTP/SFTP gap — the new getDirList wraps non-FTP fetches in a 10-second Promise.race timeout with a finally-guarded clearTimeout, but the FTP/SFTP branch uses a bare await lsDir() with no timeout and no cancel UI, regressing from the old loader-with-cancel-button behaviour.
  • PR dependency — the description states a hard dependency on unmerged PR refactor(file-browser): Granular item rendering and programmatic DOM list construction #2723; it is unclear whether that PR is already merged or whether shared helpers it may modify are missing from this diff.

Confidence Score: 4/5

  • The core refactor is architecturally sound and fixes several pre-existing race conditions, but FTP/SFTP users will see a permanent skeleton with no escape when a server is unresponsive, and the declared dependency on another unmerged PR needs to be resolved before this lands.
  • The AbortController wiring, NavStack event model, and finally-guarded timeout for non-FTP fetches are all well-implemented. The main concerns are the FTP path lacking any timeout or in-UI cancel mechanism (a regression for users on slow or hung FTP connections), the DOM node double-move of oneDirUp in the skeleton-to-real-content transition, and the unresolved question about the refactor(file-browser): Granular item rendering and programmatic DOM list construction #2723 dependency. None of these cause data loss or crashes, but the FTP regression and unclear dependency boundary warrant a second look before merging.
  • src/pages/fileBrowser/fileBrowser.js — specifically the getDirList FTP branch and the renderCurrentDir skeleton/post-fetch oneDirUp append logic.

Important Files Changed

Filename Overview
src/pages/fileBrowser/NavStack.js New event-driven navigation stack class extending EventTarget; clean implementation, but #popUntil silently clears the entire stack when called with a URL not present in it.
src/pages/fileBrowser/fileBrowser.js Core refactor introducing AbortController-guarded async rendering, skeleton loading, and a new getDirList helper; FTP/SFTP path has no timeout and no cancel UI (UX regression), and the oneDirUp DOM node is moved mid-render in the cache-miss path. Multiple pre-existing issues from prior review threads remain unaddressed.
src/pages/fileBrowser/fileBrowser.scss Adds .placeholder skeleton shimmer styles and #placeholder centering rules, including a :has(> [data-one-dir-up]) > #placeholder height adjustment. No issues found.
src/pages/fileBrowser/listItem.hbs New granular Mustache template replacing the old list.hbs wrapper; adds data-one-dir-up and placeholder class support. Clean.
src/pages/fileBrowser/list.hbs Deleted; functionality superseded by listItem.hbs and programmatic list construction in renderCurrentDir.

Sequence Diagram

sequenceDiagram
    actor User
    participant navigate
    participant NavStack
    participant renderCurrentDir
    participant getDirList
    participant DOM

    User->>navigate: tap directory tile
    navigate->>NavStack: has(url)?
    alt url already in stack
        navigate->>NavStack: popUntil(url)
        NavStack-->>DOM: pop events → remove navbar items
    else new url
        navigate->>NavStack: push(url, name)
        NavStack-->>DOM: push event → add navbar item + actionStack entry
    end
    navigate->>renderCurrentDir: (no args)

    renderCurrentDir->>renderCurrentDir: abort previous _rndrAbortCtrl
    renderCurrentDir->>renderCurrentDir: create new AbortController
    renderCurrentDir->>DOM: remove old $list, append empty $list + skeleton items
    renderCurrentDir->>getDirList: await getDirList(url)

    alt "url === "/""
        getDirList->>getDirList: await listAllStorages()
    else FTP/SFTP
        getDirList->>getDirList: await lsDir() [NO TIMEOUT]
    else other protocol
        getDirList->>getDirList: Promise.race([lsDir(), 10s timeout])
    end

    getDirList-->>renderCurrentDir: list[] or throws

    alt abortSignal.aborted
        renderCurrentDir-->>DOM: return (stale render discarded)
    else list is truthy
        renderCurrentDir->>DOM: replaceChildren(oneDirUp + items)
        renderCurrentDir->>renderCurrentDir: cachedDir.set(url, dir)
    else list undefined (error)
        renderCurrentDir->>DOM: replaceChildren(oneDirUp + error placeholder)
    else list empty []
        renderCurrentDir->>DOM: replaceChildren(oneDirUp + empty-folder placeholder)
        renderCurrentDir->>renderCurrentDir: cachedDir.set(url, dir)
    end
Loading

Reviews (24): Last reviewed commit: "feat: Render explicit empty directory an..." | Re-trigger Greptile

Comment thread src/pages/fileBrowser/fileBrowser.js Outdated
Comment on lines +1106 to +1111
case "oneDirUp": {
const dir = navStack.get(-2);
if (!dir) break;
const { url, name } = dir;
navigate(url, name);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 .. resolves to navigation-history parent, not the filesystem parent

navStack.get(-2) returns the previously-visited directory, not the actual URL-parent of the current directory. These are the same in linear navigation, but diverge in edge cases — e.g. if a future feature adds bookmarks or deep-links that push multiple levels to navStack at once (like loadStates already does). In that scenario pressing .. could land on a directory that is not an ancestor of the current one at all. The traditional expected behaviour of .. is Url.dirname(currentDir.url). Consider adding a clarifying comment or computing the real parent as a fallback.

Comment thread src/pages/fileBrowser/fileBrowser.js Outdated
Comment on lines +1106 to +1111
case "oneDirUp": {
const dir = navStack.get(-2);
if (!dir) break;
const { url, name } = dir;
navigate(url, name);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing break at end of oneDirUp case

The oneDirUp block has no trailing break. While this is currently safe because it is the last case, future additions to the switch will silently fall through into the new case without any visible indication that the omission is intentional. Adding break makes the intent explicit and future-proof.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch 5 times, most recently from 682762f to b7687ee Compare July 19, 2026 22:42
@bajrangCoder

This comment was marked as outdated.

@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from 08ccd4c to 105dc80 Compare July 20, 2026 07:44
Comment thread src/pages/fileBrowser/fileBrowser.js
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch 2 times, most recently from 003df66 to b513169 Compare July 20, 2026 09:21
@greptile-apps

This comment was marked as outdated.

@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from b513169 to 8bb4dc0 Compare July 20, 2026 09:41
@AuDevTist1C
AuDevTist1C marked this pull request as draft July 21, 2026 08:17
@AuDevTist1C

AuDevTist1C commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

(Edit: pushed)

Recording.at.July21-064140pm.2.mp4

@AuDevTist1C AuDevTist1C changed the title refactor(fileBrowser): rewrite navigation history layer with event-driven NavStack and implement parent directory navigation refactor(fileBrowser): modernizing architecture, async race safety, navigation stack, and selection UX Jul 25, 2026
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from 8bb4dc0 to d8e4881 Compare July 25, 2026 11:24
Comment on lines 828 to 834
if (checkbox) {
checkbox.checked = checked;
const url = item.querySelector("data-url").textContent;
if (checked) {
selectedItems.add(url);
} else {
selectedItems.delete(url);
}
selectedItems[checked ? "add" : "delete"](url);
}
});
updateSelectionCount($count);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 "Select All" does not clear phantom URLs on deselect

When checked = false (deselect all), selectedItems.delete(url) is called only for items that have a rendered checkbox. Any URL already in selectedItems before "Select All" was clicked is not removed, leaving phantom entries that pollute the next delete/copy/compress batch operation. A selectedItems.clear() before the deselect loop would make the deselect path symmetric with the select path.

@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch 3 times, most recently from 96bbb2e to e640f65 Compare July 25, 2026 13:58
Comment thread src/pages/fileBrowser/fileBrowser.js Outdated
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch 3 times, most recently from 1cfe31e to 80872d4 Compare July 25, 2026 20:24
@AuDevTist1C
AuDevTist1C marked this pull request as ready for review July 25, 2026 21:04
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from 16dac10 to 38b6442 Compare July 28, 2026 15:27
@AuDevTist1C

Copy link
Copy Markdown
Contributor Author

@greptile some changes were removed from this PR, and I've opened separate PRs for some of them.

@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for the heads up! That makes sense — keeping the syntax/control flow modernization (Commit 5) in a separate PR makes the diff easier to review in isolation. If you'd like me to take a look at those separate PRs when they're ready, feel free to tag me.

@AuDevTist1C

Copy link
Copy Markdown
Contributor Author

#2500 (comment)

@greptile these PRs: #2543, #2545, #2546, #2548

@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Thanks for the links! Here's a quick overview of the four spin-off PRs:

PR Title Scope Size
#2543 Standardize DOM dataset attributes list.hbs, fileBrowser.js, fileBrowser.scss — converts legacy attrs to data-* +25/-25
#2545 Exclude non-selectable tiles from batch selection list.hbs, fileBrowser.jsdata-not-selectable guard in selection handlers +8/-4
#2546 Integrate selection mode with actionStack for back navigation fileBrowser.jsfbSelection frame push/pop +12/-0
#2548 Extract deleteDirOrFile + isTermuxUrl helpers fileBrowser.js — consolidates deletion paths, ~26 lines saved +47/-73

Good call splitting these out — they're much easier to review independently. A few notes per PR:

#2543 — Clean and self-contained. The attr(data-storage-type) CSS change is worth double-checking across browsers; attr() for non-standard properties has limited support, but since this already existed in the codebase as attr(storageType), it's a lateral move.

#2545 — The dataset.notSelectable != null check works, but a simpler 'notSelectable' in item.dataset is more idiomatic and avoids the loose != null. Minor style point.

#2546 — Solid. The actionStack.has("fbSelection") idempotency guard is exactly right. One thing to verify: if the page is hidden while selection mode is active ($page.onhide), does actionStack.remove("fbSelection") get called to clean up? If the hide handler only aborts the dir render, the selection frame could linger.

#2548 — The deleteDirOrFile consolidation is a clear improvement. For isTermuxUrl, ${url ?? ""} is fine, but since url is typed as a string in all callers, the nullish coercion is defensive overhead — not a problem, just worth noting.

@AuDevTist1C AuDevTist1C changed the title refactor(fileBrowser): modernizing architecture, async race safety, navigation stack, and selection UX refactor(file-browser): Overhaul navigation state, skeleton rendering, and async safety Jul 28, 2026
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from 38b6442 to 29255a5 Compare July 29, 2026 22:06
@AuDevTist1C

AuDevTist1C commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@greptile .. participation in selection mode will be fixed if #2545 is merged

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Good to know — thanks for the heads up. That said, there's a reconciliation issue worth noting between the two PRs:

PR #2545 modifies list.hbs to add {{#notSelectable}}data-not-selectable{{/notSelectable}}, but PR #2500 deletes list.hbs entirely and replaces it with listItem.hbs. So if this PR lands first, #2545's template patch will have no target file to apply to.

Additionally, #2545's selection guards check for dataset.notSelectable, but the .. tile in PR #2500's listItem.hbs uses data-one-dir-up — not data-not-selectable. As written, #2545 wouldn't actually exclude the .. tile from selection mode without one of these changes:

  1. Add {{#oneDirUp}}data-not-selectable{{/oneDirUp}} to listItem.hbs in this PR, or
  2. Update the guards in fix(fileBrowser): exclude non-selectable system utility tiles from batch selection operations #2545 to also check item.dataset.oneDirUp != null

Either way, these two PRs will need to be coordinated before merging.

@AuDevTist1C

Copy link
Copy Markdown
Contributor Author

#2500 (comment)

#2500 (comment)

@greptile there's already notSelectable: true on ..'s tile?

@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

You're right that notSelectable: true is passed to createListItemEl in fileBrowser.js. The problem is that listItem.hbs never uses it — there's no {{#notSelectable}}data-not-selectable{{/notSelectable}} block in the template. So the property is passed but silently dropped, and the rendered <li> element never gets a data-not-selectable DOM attribute.

That's exactly the gap: the JS side sets the flag, but the template doesn't output it, so any selection-mode guard that reads dataset.notSelectable (as in #2545) will still see nothing.

The fix is a one-liner in listItem.hbs:

  {{#oneDirUp}}data-one-dir-up{{/oneDirUp}}
  {{#notSelectable}}data-not-selectable{{/notSelectable}}

With that added, notSelectable: true on the .. tile will correctly render data-not-selectable in the DOM, and both the existing contextMenuHandler guard (if (isOneDirUp) return) and #2545's selection guards will work consistently.

Isolate the file system rename operation into its own dedicated commit to preserve continuous history tracking within Git. Renaming `list.hbs` to `listItem.hbs` without altering any content ensures Git records this change as a 100% file rename. Decoupling this step prevents Git from misinterpreting subsequent structural template refactors as a destructive file deletion followed by the addition of an entirely new file.

* **Template File Rename (`src/pages/fileBrowser/`):**
* Renamed `src/pages/fileBrowser/list.hbs` to `src/pages/fileBrowser/listItem.hbs` with zero line changes (100% similarity score), guaranteeing clean `git blame` and file history continuity across structural revisions.

(AI generated commit message)
…eholder

Overhaul the directory rendering logic in the file browser by replacing monolithic template compilation with programmatic DOM element construction and individual item parsing.

Previously, directory list rendering relied on a single template (`list.hbs`) that wrapped the outer `<ul>` element, handled list iteration (`{{#list}}`), and relied on the `mustache` package's built-in capability to render placeholder text if the element had no children when the `empty-msg` HTML attribute was provided.

This commit refactors the template down to a granular single-item scale and adds the empty state placeholder back explicitly via programmatic rendering. By splitting template rendering into granular helper functions (`createListEl`, `createListItemEl`, and `createPlaceholderEl`), directory rendering now builds list elements individually and explicitly appends a styled placeholder node whenever a directory contains no files or folders.

* **Template Scope Reduction (`src/pages/fileBrowser/listItem.hbs`):**
* Removed the enclosing `<ul class="list" id="list">` container tag and the surrounding `{{#list}}...{{/list}}` iteration block from the Handlebars template.
* Converted the file into a standalone item partial that takes an entry object and produces a single `<li>` element representing a file or directory row.

* **DOM Element Construction Helpers (`src/pages/fileBrowser/fileBrowser.js`):**
* Added `createListEl()` to dynamically generate the parent `<ul className="list" id="list">` element.
* Added `createListItemEl(obj)` to parse individual item objects through `mustache.render(_listItem, obj)` into single `HTMLLIElement` nodes.
* Added `createPlaceholderEl(msg)` to create dedicated empty-state DOM elements (`<div id="placeholder">{msg}</div>`).

* **Render Loop & Empty State Logic (`src/pages/fileBrowser/fileBrowser.js`):**
* Updated `render(dir)` to construct list containers programmatically and append rendered child elements via standard DOM iteration (`$list.appendChild(el)`).
* Re-implemented empty directory handling: if `list.length` is zero, a placeholder element containing the localized empty folder string is appended to the list, restoring the empty message functionality previously supplied via Mustache's `empty-msg` attribute.

* **Placeholder Layout Styling (`src/pages/fileBrowser/fileBrowser.scss`):**
* Defined CSS rules for `#placeholder` utilizing Flexbox (`display: flex`, `align-items: center`, `justify-content: center`) to ensure empty folder messages are centered within the file browser container.

(AI generated commit message)
…t item rendering with skeleton loading

Overhaul navigation architecture and list view rendering performance by decoupling history tracking into an event-driven `NavStack` class and replacing full-list re-renders with granular element construction and visual skeleton states.

* **Event-Driven Navigation Stack (`src/pages/fileBrowser/NavStack.js`):**
  * Created `NavStack` extending `EventTarget` to encapsulate navigation array history (`#arr`) and URL uniqueness tracking (`#urlSet`).
  * Emits custom `push` and `pop` DOM events to keep UI updates, state serialization, and action stack clean-ups decoupled.
  * Implemented bounds-checked array indexing (`get(i)`), conditional popping (`popUntil(url)`), and state inspection (`has(url)`, `toJSON()`).

* **Directory Caching & Asynchronous Fetching (`src/pages/fileBrowser/fileBrowser.js`):**
  * Transitioned `cachedDir` storage from a plain object to a standard JavaScript `Map` instance.
  * Replaced `getDir` with `getDirList(url)`, utilizing `Promise.withResolvers()` and `Promise.race()` to enforce a 10-second timeout on directory listings across storage mechanisms.

* **Skeleton Loading & Incremental Rendering (`src/pages/fileBrowser/fileBrowser.js` & `.scss`):**
  * Added skeleton placeholder generation during directory fetching using a `DocumentFragment` populated with random-width bar elements.
  * Introduced `.placeholder` CSS styling in `fileBrowser.scss` and updated `listItem.hbs` to handle non-interactive skeleton rows gracefully.

(AI generated commit message)
…g AbortController

Introduce cancellation checking via `AbortController` in `renderCurrentDir()` to prevent stale asynchronous directory reads from overwriting active UI state during rapid user navigation.

* **Render Task Tracking (`src/pages/fileBrowser/fileBrowser.js`):**
  * Declared a module-scoped `_rndrAbortCtrl` reference to monitor in-flight directory rendering tasks.
  * Automatically aborts any existing controller upon initiating a new `renderCurrentDir()` invocation or when triggering the page hide handler (`$page.onhide`).

* **Asynchronous Race Condition Guarding (`src/pages/fileBrowser/fileBrowser.js`):**
  * Evaluates `abortSignal.aborted` immediately after `await getDirList(url)` resolves.
  * Discards outdated directory payload promises before mutating the directory cache or appending child elements to the DOM.

(AI generated commit message)
…ation

Automatically prepend a dedicated parent directory (`..`) navigation tile at the top of directory listings when navigating deep within a directory hierarchy.

* **Template Support (`src/pages/fileBrowser/listItem.hbs`):**
  * Added template attribute binding `{{#oneDirUp}}data-one-dir-up{{/oneDirUp}}` to flag parent navigation element targets.

* **Parent Node Generation (`src/pages/fileBrowser/fileBrowser.js`):**
  * Evaluates navigation stack depth (`navStack.length >= 2`) during `renderCurrentDir()`.
  * Programmatically builds a `..` parent item tile and prepends it to the DOM fragment across both skeleton loading states and final directory renders.

* **Interaction & Event Handling (`src/pages/fileBrowser/fileBrowser.js`):**
  * Intercepts `open` actions on `data-one-dir-up` elements to execute upward directory traversal using `navStack.get(-2)`.
  * Explicitly bypasses context menu triggers (`contextMenuHandler`) and new path prompts for parent tiles.

* **Container Layout (`src/pages/fileBrowser/fileBrowser.scss`):**
  * Added `:has(> [data-one-dir-up])` layout rules to offset placeholder heights when parent tiles are rendered above empty/loading states.

(AI generated commit message)
Provide clear visual feedback for empty directories and filesystem errors by programmatically constructing and styling a centered placeholder element.

* **Error Handling Relocation (`src/pages/fileBrowser/fileBrowser.js`):**
  * Removed internal `try/catch` block from `getDirList()` and moved exception handling directly into `renderCurrentDir()`.
  * Captures filesystem inspection errors (including local, content URI, and remote FTP/SFTP protocol failures), formatting virtual paths and error strings for UI display.

* **Placeholder Rendering Logic (`src/pages/fileBrowser/fileBrowser.js`):**
  * Refined `createPlaceholderEl()` helper to validate text inputs and output `<div id="placeholder">` nodes.
  * Injects formatted filesystem error messages into the list placeholder node, falling back to localized empty folder messages (`strings["empty folder message"]`) when listings return no items.

(AI generated commit message)
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from 7717fb2 to e3be768 Compare August 15, 2026 15:44
@AuDevTist1C

Copy link
Copy Markdown
Contributor Author

@greptile review this, and the updated PR description, especially the dependence on yet another PR.

@AuDevTist1C

Copy link
Copy Markdown
Contributor Author

@greptile the Event.prototype.target issue you previously pointed out is being resolved in #2725

@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Thanks for the heads up! I'll consider that concern resolved via #2725 then. No further action needed on this PR for that issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

3 participants