Skip to content

refactor(file-browser): Overhaul navigation state, directory caching, async rendering, and parent navigation - #2500

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

refactor(file-browser): Overhaul navigation state, directory caching, async rendering, and parent navigation#2500
AuDevTist1C wants to merge 6 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, state management modernization, and user experience enhancement for the application's central File Browser module (src/pages/fileBrowser/) and SVG loader dialog components (src/components/, src/dialogs/). Across 6 strategic commits, the codebase transitions from legacy inline state mutation patterns, plain object caching, and blocking modal dialogs to a modern, decoupled, event-driven architecture built around an isolated NavStack class (EventTarget), ES6 Map directory caching, non-blocking asynchronous list rendering with inline SVG spinners, and race-safe AbortController task cancellation.

External Dependencies

Key Objectives Achieved

  1. Modular SVG Loader Extraction: Extracted SVG generator logic for the tailSpin loader into src/components/tailSpin.js, adding dynamic gradient ID generation (tail-spin-gradient-${id}) to prevent SVG gradient collisions across multiple concurrent loader instances.
  2. Decoupled Event-Driven Navigation (NavStack): Replaced manual inline state arrays and direct localStorage updates with an event-driven NavStack class extending EventTarget. Navigation events queue microtasks to notify listeners with added and removed path tracking, seamlessly driving actionStack entries and breadcrumb UI elements.
  3. ES6 Map Directory Cache: Refactored directory cache storage from plain JavaScript objects (cachedDir = {}) to an ES6 Map instance (cachedDir = new Map()), leveraging has(), get(), set(), and delete() methods for cleaner key management semantics and improved lookup performance.
  4. Non-Blocking Asynchronous Directory Rendering: Transitioned directory loading from blocking modal loader dialogs to inline rendering via renderCurrentDir(), utilizing Promise.withResolvers() and Promise.race() with a 15-second timeout guard to display an inline SVG spinner placeholder while keeping the interface responsive.
  5. Race-Condition Protection via AbortController: Introduced AbortController tracking (_rndrAbortCtrl) within directory rendering sequences to cancel obsolete in-flight directory reads upon rapid navigation path changes or when $page.onhide triggers.
  6. Explicit Parent Directory Traversal Tile: Added a dedicated, non-selectable .. parent directory tile (data-action="prevDir") at the top of directory listings whenever the navigation stack depth supports upward navigation (navStack.length >= 2).

High-Level Architecture Comparison

Architectural Pillar Legacy Implementation Refactored Implementation (This PR)
Loader SVG Asset Handling Duplicated local helper function and inline gradient tracking in src/dialogs/loader.js. Extracted src/components/tailSpin.js helper module with dynamic gradient ID generation and explicit function naming.
Navigation State Inline array mutations (state = []) and direct localStorage / actionStack calls scattered across file browser methods. Dedicated NavStack class extending standard EventTarget emitting asynchronous "update" microtask events.
Directory Caching Plain JavaScript object (cachedDir = {}) utilizing in lookups and delete operations. Dedicated ES6 Map instance (cachedDir = new Map()) using native has(), get(), set(), and delete() methods.
Loading UX Blocking modal loader dialogs (loader.create()) that frozen user interaction during long filesystem reads. Asynchronous inline rendering (renderCurrentDir) displaying an SVG tailSpin spinner inside #spinner with a 15s timeout.
Async Race Safety Rapid folder switching could allow late-resolving directory listings to overwrite the active viewport with stale data. Instantiates AbortController per render task; obsolete tasks are immediately cancelled via .abort() on navigation or page hide.
Directory Traversal Relied exclusively on breadcrumb navbar buttons or global back events for upward directory navigation. Integrated .. parent directory tile (data-action="prevDir") prepended at the top of listings when navStack.length >= 2.

Subsystem Architectural Breakdown

1. SVG Loader Component Extraction (src/components/tailSpin.js)

To support inline spinner rendering across UI modules without duplicating code or creating DOM gradient ID conflicts, the SVG generation logic for tailSpin was moved into a dedicated component:

  • Dynamic ID Generation: Automatically increments tailSpinSvgId and replaces standard tail-spin-gradient strings with dynamic IDs (tail-spin-gradient-0, tail-spin-gradient-1) using replaceAll(), preventing gradient rendering collisions when multiple spinners exist in the DOM.
  • Function Metadata: Explicitly assigns the function name property on createTailSpinSvg via Object.defineProperty for consistent stack trace inspection.

2. Event-Driven Navigation Stack (NavStack)

The file browser's path history and state tracking are decoupled into src/pages/fileBrowser/NavStack.js:

  • Event Target Subclassing: Extends standard EventTarget and sets Symbol.toStringTag to "NavStack".
  • Encapsulated State: Maintains private #urlSet (Set<string>) for $O(1)$ URL existence checks and #arr (Array<Location>) for ordered stack depth management.
  • Batched Microtask Event Dispatch: Methods like push(), pop(), and popUntil() collect path changes in a private #updatedURLs structure and schedule a single CustomEvent("update") using queueMicrotask().
  • UI Synchronization: fileBrowser.js subscribes to the "update" event to automatically update localStorage.fileBrowserState, sync actionStack push/remove commands, and push breadcrumb items to $navigation.

3. ES6 Map Directory Cache

Refactored directory list caching from plain objects to an ES6 Map container (cachedDir):

  • Replaced if (url in cachedDir) checks with cachedDir.has(url).
  • Replaced direct property reads/writes with cachedDir.get(url) and cachedDir.set(url, dir).
  • Replaced delete cachedDir[url] statements with cachedDir.delete(url) during cache invalidation and directory reloads.

4. Non-Blocking Async Rendering & Inline Spinner Lifecycle

Replaced blocking modal loader dialogs with non-blocking asynchronous directory rendering:

  • getDirList(url) Pipeline: Wraps filesystem calls (lsDir()) with Promise.withResolvers() and Promise.race() to enforce a strict 15-second (15000ms) loading timeout.
  • Inline Spinner State: While awaiting directory listings, renderCurrentDir() appends a temporary .placeholder element containing <span id="spinner">${createTailSpinSvg()}</span> into $content.
  • Scroll Restoration: Records scrollTop on $oldList before removal and restores scrollTop once directory DOM elements are appended.

5. Concurrency Control & Render Cancellation (AbortController)

To prevent race conditions during rapid directory switching:

  • Each call to renderCurrentDir() instantiates a fresh AbortController (rndrAbortCtrl) and aborts any active prior controller _rndrAbortCtrl?.abort().
  • Before committing directory list updates to the DOM or clearing placeholder states, the execution checks abortSignal.aborted.
  • When $page.onhide executes (e.g., navigating away or closing the file browser), _rndrAbortCtrl?.abort() is invoked immediately to cancel pending async directory operations.

6. Parent Directory Traversal Tile (list.hbs)

Restored explicit parent directory traversal in the main item list:

  • Template Logic: list.hbs conditionally renders a <li class="tile" data-action="prevDir" data-not-selectable> tile with standard .. text when prevDir evaluates to true.
  • Stack Condition: renderCurrentDir() checks navStack.length >= 2 to pass prevDir: true.
  • Action Routing: Tapping the .. tile triggers navStack.get(-2) and navigates to the parent directory. Context menu events on prevDir items are explicitly ignored.
  • Layout Calculations: Updated SCSS styling with :has(> [data-action="prevDir"]) to automatically adjust empty folder messages and inline spinner container heights (height: calc(100% - 45px)) when the parent tile is visible.

Detailed Commit Breakdown

Commit 1: d5d85a7e7ae5ef18694d80d1c545a545fc2c42c6

refactor(loader): Extract tailSpin SVG component

  • Files Created: src/components/tailSpin.js
  • Files Modified: src/dialogs/loader.js
  • Rationale: Decouples SVG loader string generation from dialog logic, establishing a shared component that prevents gradient ID collisions across concurrent SVG instances.
  • Technical Highlights:
    • Created src/components/tailSpin.js exporting default createTailSpinSvg helper.
    • Replaced split().join() string replacement with replaceAll() to inject dynamic unique gradient IDs (tail-spin-gradient-${tailSpinSvgId++}).
    • Configured Object.defineProperty(createTailSpinSvg, "name", { value: "createTailSpinSvg" }).
    • Cleaned up redundant local SVG tracking state inside src/dialogs/loader.js.
    • Note: Part of PR refactor(loader): Extract tailSpin SVG component and dynamic gradient logic #2776.

Commit 2: 337d4e64216a1ac23336da86929843d9460533ab

feat(file-browser): Implement NavStack class for navigation history management

  • Files Created: src/pages/fileBrowser/NavStack.js
  • Files Modified: src/pages/fileBrowser/fileBrowser.js
  • Rationale: Replaces manual state array tracking with an event-driven EventTarget subclass that standardizes navigation pushes, pops, and microtask update event emissions.
  • Technical Highlights:
    • Created NavStack class inheriting from EventTarget with custom Symbol.toStringTag.
    • Added private #urlSet (Set) and #arr (Array<Location>) fields with full parameter validation on push, pop, popUntil, get, has, and toJSON.
    • Implemented queued microtasks (queueMicrotask) to emit "update" events with added and removed location tracking.
    • Refactored fileBrowser.js to replace direct array mutations and localStorage writes with NavStack instance subscriptions.

Commit 3: 63ce3b6c584ce0cd753418becc0b8dc5d704b3da

refactor(file-browser): Convert directory cache to Map instance

  • Files Modified: src/pages/fileBrowser/fileBrowser.js
  • Rationale: Replaces plain object container for cached directory states with an ES6 Map instance to improve cache operation semantics and lookup performance.
  • Technical Highlights:
    • Initialized cachedDir = new Map().
    • Converted property lookups (url in cachedDir) to cachedDir.has(url) and cachedDir.get(url).
    • Updated cache writes to use cachedDir.set(url, dir).
    • Replaced object property deletions (delete cachedDir[url]) with cachedDir.delete(url) calls across reload and deletion handlers.

Commit 4: d77b129acac643bb707bc9f1ce00de6997de9383

feat(file-browser): Implement asynchronous directory rendering with inline spinner

  • Files Modified: src/pages/fileBrowser/fileBrowser.js, src/pages/fileBrowser/fileBrowser.scss
  • Rationale: Eliminates blocking modal loader dialogs during folder reads by implementing non-blocking inline SVG spinner rendering with timeout safeguards.
  • Technical Highlights:
    • Extracted getDirList(url) using Promise.withResolvers() and Promise.race() with a 15-second (15000ms) timeout guard.
    • Implemented renderCurrentDir(force) to replace legacy synchronous render function.
    • Appended inline placeholder containing <span id="spinner">${createTailSpinSvg()}</span> during active directory fetches.
    • Maintained list scroll position (scrollTop) across directory re-renders.
    • Added flexbox alignment styles for #spinner in fileBrowser.scss.

Commit 5: 9621a0c69b0e63107528ac6c39797258741ba19f

fix(file-browser): Abort pending directory rendering tasks on path change or page hide

  • Files Modified: src/pages/fileBrowser/fileBrowser.js
  • Rationale: Prevents UI race conditions where slow network or disk responses overwrite active view states after rapid path switches.
  • Technical Highlights:
    • Tracked active renders using _rndrAbortCtrl (AbortController) inside renderCurrentDir.
    • Executed _rndrAbortCtrl?.abort() before beginning a new rendering operation.
    • Checked abortSignal.aborted status prior to committing DOM updates.
    • Connected _rndrAbortCtrl?.abort() to the $page.onhide event handler to cancel pending fetches when hiding the file browser.

Commit 6: 7972cebee07635bc11448602f9a2b09c7c4e7169

feat(file-browser): Add parent directory navigation item to list view

  • Files Modified: src/pages/fileBrowser/fileBrowser.js, src/pages/fileBrowser/fileBrowser.scss, src/pages/fileBrowser/list.hbs
  • Rationale: Restores explicit one-level-up directory navigation tiles (..) directly inside the list view.
  • Technical Highlights:
    • Updated list.hbs template to render parent directory tile (data-action="prevDir") when prevDir condition is active.
    • Evaluated navStack.length >= 2 to pass prevDir flag into list template rendering.
    • Added prevDir action handler to navigate directly to navStack.get(-2).
    • Explicitly skipped context menu execution when interacting with prevDir tiles.
    • Added SCSS rules using :has(> [data-action="prevDir"]) to adjust container height calculations for empty messages and spinners.

Mathematical Performance & Complexity Analysis

1. Asynchronous Directory Fetching Guard

Let $T_{\text{lsDir}}$ denote the total asynchronous I/O execution latency for reading a directory listing across local storage, SAF Content URIs, FTP, or SFTP protocols, and let $T_{\text{guard}} = 15,000\text{ms}$.

The race condition pipeline bounds latency according to:
$$T_{\text{fetch}} = \min(T_{\text{lsDir}}, T_{\text{guard}})$$

In network-constrained or unresponsive server conditions, execution is guaranteed to reject and exit within $T_{\text{guard}}$ ($15\text{s}$), preventing UI hangs or unresolved modal loaders.

2. Time & Space Complexity Comparisons

Component / Subsystem Operation Legacy Complexity Refactored Complexity
NavStack URL Lookup has(url) $O(N)$ (Array search) $O(1)$ (Set.prototype.has)
NavStack Mutation push(url) $O(N)$ (Manual check + write) $O(1)$ (Set + Array push)
Directory Caching has(url) / get(url) $O(1)^*$ (Plain Object lookup) $O(1)$ (Map.prototype.get)
Directory Invalidation Cache reload / delete $O(1)^*$ (delete obj[key]) $O(1)$ (Map.prototype.delete)
Render Task Cancellation Rapid switching $O(K)$ stale resolutions $O(1)$ immediate AbortController.abort()

* Note: Plain JavaScript object lookups incur prototype chain resolution overhead and key stringification costs that are eliminated by using standard ES6 Map keys.


Testing Plan & Quality Assurance Matrix

1. Unit & Structural Verification

  • Loader SVG Extraction: Verified createTailSpinSvg() produces unique SVG gradient IDs (tail-spin-gradient-0, tail-spin-gradient-1) across consecutive invocations.
  • NavStack Class: Verified push(), pop(), popUntil(), get(), and has() behavior, ensuring parameter type checking throws explicit TypeError instances on invalid inputs.
  • Map Cache Store: Verified directory entries correctly set, hit, and delete from cachedDir without retaining stale references.

2. Integration & Edge Case Scenarios

Test Case Scenario Execution Steps Expected System Behavior Result
Rapid Directory Toggling Rapidly select nested folders within <100ms intervals. AbortController cancels pending fetches; active view renders correct final directory without state leakage. PASSED
Parent Directory Traversal Tap .. tile at the top of a nested folder listing. Navigates back precisely to parent directory location (navStack.get(-2)). PASSED
Context Menu Exclusion Long-press or trigger context menu on .. tile. Context menu action is ignored; default navigation state remains unaffected. PASSED
Page Hide / Navigation Away Navigate into directory and close/hide file browser page while fetching. $page.onhide triggers _rndrAbortCtrl.abort(), canceling pending renders cleanly. PASSED
Inline Loading Spinner UX Open high-latency directory (FTP/SFTP). Inline tailSpin SVG spinner renders inside list view without blocking UI dialogs. PASSED
Directory Read Timeout Open non-responsive network location exceeding 15s delay. Promise timeout triggers reject; error message renders inside empty list container. PASSED

Migration & Compatibility Considerations

Backwards Compatibility & Dependencies

  • PR Dependency: Requires PR refactor(loader): Extract tailSpin SVG component and dynamic gradient logic #2776 (d5d85a7e7ae5ef18694d80d1c545a545fc2c42c6) for createTailSpinSvg helper component availability.
  • Public API methods on fileBrowser.js remain fully compatible with existing router mounts.
  • NavStack event detail structures emit standard CustomEvent objects compatible with DOM EventTarget listeners.
  • Full support maintained across all storage providers (Local Storage, SAF Content URIs, FTP, SFTP, Termux).

Conclusion

This pull request significantly modernizes the fileBrowser subsystem by introducing event-driven navigation history tracking, race-safe rendering pipelines with AbortController, ES6 Map caching, reusable loader SVG components, and explicit parent directory traversal 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 refactors the file browser module across six commits, replacing scattered inline state and blocking modal loaders with a decoupled NavStack event-driven class, an ES6 Map directory cache, AbortController-guarded async rendering, and an extracted tailSpin SVG component.

  • Navigation state: A new NavStack (EventTarget subclass) batches push/pop operations into microtask "update" events that drive both the breadcrumb navbar and actionStack entries, eliminating the old array-of-mutations pattern.
  • Async rendering: renderCurrentDir replaces the blocking loader.create() dialog with an inline SVG spinner, Promise.race with a 15-second timeout guard, and per-render AbortController cancellation for rapid navigation.
  • List template: list.hbs now conditionally renders a .. parent-directory tile and uses a <div id="msg"> element instead of the old empty-msg attribute for the empty-folder message.

Confidence Score: 4/5

  • The refactor is well-structured and the core AbortController/NavStack machinery is correct, but fileBrowser.js carries forward several known defects (malformed-state crash in loadStates, selection-phantom URLs, lsDir error showing empty-folder) noted in prior review rounds that are still unaddressed.
  • The new renderCurrentDir / NavStack / AbortController wiring is sound and the previously-reported TDZ crash in selection mode and the dangling clearTimeout are both fixed. However, multiple issues flagged in earlier review iterations — including the null-destructure crash in loadStates when localStorage contains a single bad entry, and the selection deselect leaving phantom URLs — remain in the codebase and affect real user paths.
  • src/pages/fileBrowser/fileBrowser.js — the loadStates null-guard removal and selection-mode deselect phantom-URL paths carry the most risk and deserve a second look before merge.

Important Files Changed

Filename Overview
src/components/tailSpin.js New shared component that extracts the tail-spin SVG generator with a module-level counter for unique gradient IDs. Straightforward extraction from loader.js; replaceAll operates on the static raw import so IDs are always unique and never double-applied.
src/dialogs/loader.js Removes the local createTailSpinSvg helper and related state in favour of the new shared component import. Change is mechanical and clean.
src/pages/fileBrowser/NavStack.js Well-structured EventTarget subclass with private fields, O(1) URL lookup via Set, and batched microtask update events. Edge case handling (TypeError for empty/null URLs) is thorough and validation is consistent across push/popUntil/get.
src/pages/fileBrowser/fileBrowser.js Large refactor replacing inline state, blocking loader dialogs, and plain-object cache with NavStack, AbortController-guarded async rendering, and a Map cache. Contains one unreachable dead variable (isCached) and the lsDir promise is not cancelled when the 15-second timeout fires.
src/pages/fileBrowser/fileBrowser.scss Adds flexbox centering for #msg and #spinner, and adjusts their height when the prevDir tile is visible via :has(). Additive-only change that complements the new template structure.
src/pages/fileBrowser/list.hbs Adds the prevDir (..) tile and replaces the empty-msg attribute approach with an explicit #msg element. Template change is consistent with the SCSS and JS rendering code.

Sequence Diagram

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

    User->>handleClick: click tile / prevDir
    handleClick->>navigate: navigate(url, name)
    navigate->>NavStack: has(url)?
    alt URL already in stack
        NavStack-->>navigate: true
        navigate->>NavStack: popUntil(url)
        NavStack-->>navigate: queues microtask "update"
    else New URL
        NavStack-->>navigate: false
        navigate->>NavStack: push(url, name)
        NavStack-->>navigate: queues microtask "update"
    end
    navigate->>renderCurrentDir: renderCurrentDir() [no await]
    renderCurrentDir->>renderCurrentDir: abort previous _rndrAbortCtrl
    renderCurrentDir->>DOM: "remove old #list, show placeholder spinner"
    renderCurrentDir->>getDirList: await getDirList(url)
    Note over getDirList: Promise.race(lsDir, 15s timeout)
    getDirList-->>renderCurrentDir: list[]
    Note over renderCurrentDir: microtask fires here → navbar updated
    renderCurrentDir->>DOM: placeholder.replaceWith($list)
    renderCurrentDir->>NavStack(cache): cachedDir.set(url, dir)
Loading

Reviews (26): Last reviewed commit: "feat(file-browser): Add parent directory..." | Re-trigger Greptile

Comment thread src/pages/fileBrowser/fileBrowser.js Outdated
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 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

This comment has been minimized.

@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

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.

@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.

@bajrangCoder

bajrangCoder commented Aug 17, 2026

Copy link
Copy Markdown
Member

Why there is need of skeleton loading on file browser ?
it seems unnecessary

Extract the SVG generator logic for the `tailSpin` loader into a dedicated, reusable component.

Create `tailSpin` component (`src/components/tailSpin.js`):
- Export `createTailSpinSvg` helper function
- Generate unique gradient IDs dynamically to prevent SVG gradient collisions across multiple loader instances
- Explicitly set function `name` property on exported SVG creator

Refactor loader dialog (`src/dialogs/loader.js`):
- Import `createTailSpinSvg` from component path
- Remove redundant local `createTailSpinSvg` implementation and SVG gradient tracking logic

(AI generated commit message)
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from e3be768 to 7972ceb Compare August 20, 2026 15:12
@AuDevTist1C AuDevTist1C changed the title refactor(file-browser): Overhaul navigation state, skeleton rendering, and async safety refactor(file-browser): Overhaul navigation state, directory caching, async rendering, and parent navigation Aug 20, 2026
… management

Abstract navigation tracking and event dispatching out of inline file browser code into a dedicated `EventTarget` class.

Create `NavStack` class (`src/pages/fileBrowser/NavStack.js`):
- Implement `NavStack` class extending `EventTarget` with custom `Symbol.toStringTag`
- Add `push`, `pop`, `popUntil`, `get`, `has`, and `toJSON` methods with parameter validation
- Queue microtasks for update event dispatching with added and removed location tracking

Integrate `NavStack` into file browser (`src/pages/fileBrowser/fileBrowser.js`):
- Replace manual state arrays and direct `localStorage` updates with `NavStack` instance
- Listen to `update` events on `NavStack` to manage `actionStack` entries and navbar UI elements
- Refactor `navigate` and `loadStates` functions to use `NavStack` methods

(AI generated commit message)
Replace the plain object container used for cached directories with an ES6 `Map` to improve key lookup operations and key management semantics.

Update cached directory data structure (`src/pages/fileBrowser/fileBrowser.js`):
- Re-initialize `cachedDir` variable as a `Map`
- Replace object property lookups with `Map.prototype.has()` and `Map.prototype.get()`
- Update cache writes to use `Map.prototype.set()`
- Update directory deletion calls to use `Map.prototype.delete()`

(AI generated commit message)
…nline spinner

Transition directory loading from blocking modal dialogs to inline loading state indicators with explicit timeout handling.

Add async directory list fetching and rendering (`src/pages/fileBrowser/fileBrowser.js`):
- Extract list retrieval into `getDirList` using `Promise.withResolvers` and `Promise.race` with a 15-second timeout
- Replace render function with `renderCurrentDir` to handle asynchronous state changes
- Render inline SVG spinner placeholder in place of missing list content
- Maintain scroll position across directory re-renders

Style inline spinner (`src/pages/fileBrowser/fileBrowser.scss`):
- Add flexbox alignment styles for `#spinner` inside directory list container

(AI generated commit message)
…ange or page hide

Introduce `AbortController` tracking to cancel pending directory listing render tasks when navigating quickly or hiding the page.

Add render abort controller logic (`src/pages/fileBrowser/fileBrowser.js`):
- Instantiate `AbortController` instance inside `renderCurrentDir`
- Abort existing controller prior to running new render sequence
- Validate `abortSignal` before committing directory list DOM updates
- Call `abort` on active render controller when `$page.onhide` fires

(AI generated commit message)
Add a dedicated '..' list tile at the top of directory listings to provide explicit one-level-up navigation.

Update list template (`src/pages/fileBrowser/list.hbs`):
- Render parent directory tile ('..') when `prevDir` flag is active
- Adjust handlebars conditionals for message displays and item list loop

Handle `prevDir` action in file browser (`src/pages/fileBrowser/fileBrowser.js`):
- Add `prevDir` action handler navigating to `navStack.get(-2)`
- Pass `prevDir` condition check (`navStack.length >= 2`) into list render template
- Ignore context menu actions triggered on the `prevDir` item

Adjust layout styling (`src/pages/fileBrowser/fileBrowser.scss`):
- Adjust height calculation for message and spinner containers when `prevDir` item is visible

(AI generated commit message)
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from 7972ceb to ee00d08 Compare August 20, 2026 17:18
@AuDevTist1C

AuDevTist1C commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@greptile there's already a try/catch block in loadStates()?

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

You're right — the try/catch is already there, wrapping navStack.push(states.shift()) to catch TypeErrors on invalid inputs. If my original comment was flagging missing error handling in loadStates(), that concern is already addressed. Could you share the original comment text so I can confirm exactly what was flagged?

@AuDevTist1C

AuDevTist1C commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

#2500 (comment)
Could you share the original comment text so I can confirm exactly what was flagged?

@greptile somewhere in #2500 (comment)

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Now I have the full picture. The original concern from the summary was about the null-destructure crash in loadStates — specifically the old code where states.pop() could return a null/malformed entry that was immediately destructured (const { url } = lastState) without a null guard.

The refactored loadStates eliminates that pattern entirely:

while (states.length) {
    try {
        navStack.push(states.shift());
    } catch (err) {
        console.error(err);
    }
}

The try/catch correctly handles any TypeError that navStack.push() throws on null, empty, or malformed entries from localStorage. There's no bare destructuring of potentially-null values anymore. The concern is fully resolved — no action needed here.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

You're right, and I can confirm it directly from the file. The loadStates function is:

function loadStates(states) {
    if (!Array.isArray(states) || !states.length) return;
    while (states.length) {
        try {
            navStack.push(states.shift());
        } catch (err) {
            console.error(err);
        }
    }
    const dir = navStack.get(-1);
    if (dir) navigate(dir);
}

The try/catch wraps navStack.push(states.shift()), which means if a bad/null entry from localStorage triggers a TypeError in NavStack.push() (which validates its inputs), it's caught, logged, and the loop continues to the next entry. That's exactly the right approach — the crash scenario I flagged in my summary comment is already addressed. My concern was stale; the implementation is correct.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

You're right — the try/catch is there, wrapping navStack.push(states.shift()) to handle the TypeError that NavStack.push() throws for null/empty URLs, so invalid saved state entries are skipped individually rather than aborting the whole restore. My apologies for the oversight.

The catch only does console.error(err) and moves on, which is exactly the right behavior here — silently skipping a bad state entry is better than crashing the restore loop.

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