Skip to content

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

Open
AuDevTist1C wants to merge 5 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 5 commits into
Acode-Foundation:mainfrom
AuDevTist1C:refactor/file-browser

Conversation

@AuDevTist1C

@AuDevTist1C AuDevTist1C commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Executive Summary

Prerequisite Dependency: This PR depends on #2793 (Commit 524da7c2eb01a4d36cf5e64b92156f6564e45e71), which introduces the NavStack navigation class and must be merged prior to this PR.

This Pull Request delivers an architectural overhaul, performance refactoring, state management modernization, and user experience enhancement for the application's central File Browser module (src/pages/fileBrowser/). Building upon the decoupled NavStack history foundation introduced in PR #2793, this PR spans 4 strategic commits to transition the directory cache to an ES6 Map, implement non-blocking asynchronous list rendering with inline SVG spinners, introduce race-safe AbortController task cancellation, and add explicit parent directory traversal controls.

Key Objectives Achieved

  1. 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.
  2. 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.
  3. 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.
  4. 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)
Navigation State (PR #2793 Dependency) 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 froze 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. Event-Driven Navigation Stack (NavStack) — PR #2793 Dependency

The file browser's path history and state tracking rely on src/pages/fileBrowser/NavStack.js (from PR #2793):

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

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

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

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

5. 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: 1a6bf82821012da039439e8e749228fa91997f36

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 2: ec6b7f087f40ca43a1409e89cea87d5d75431288

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 3: 6b2fb3cb5cfab3155c79cc1c29295c73781a2341

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 4: 81ac2ab6c846fda4a9976232dddfc13867d1aa52

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 (PR #2793) has(url) $O(N)$ (Array search) $O(1)$ (Set.prototype.has)
NavStack Mutation (PR #2793) 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

  • NavStack Class (PR feat(file-browser): Modularize navigation history management #2793): 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


Conclusion

This pull request significantly modernizes the fileBrowser subsystem by introducing event-driven navigation history tracking (via PR #2793), race-safe rendering pipelines with AbortController, ES6 Map caching, 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 overhauls the file browser by replacing the inline state[] array with a new NavStack event-driven class, converting the directory cache from a plain object to an ES6 Map, introducing non-blocking async rendering with an inline SVG spinner and AbortController-based race-condition protection, and adding an explicit .. parent-directory tile.

  • NavStack.js (new): encapsulates navigation history with a private #urlSet for O(1) lookups and emits batched microtask "update" events consumed by fileBrowser.js to sync localStorage, actionStack, and the breadcrumb navbar.
  • renderCurrentDir: replaces the old synchronous render() + modal loader with an async pipeline — spinner placeholder → getDirList with a 15 s timeout guard → DOM swap — guarded by an AbortController that is aborted on path change or $page.onhide.
  • pushToNavbar: contains a merge artifact where both the manual if (!el) block and the getOrInsertComputed one-liner ended up in the same function body, causing a parse-time SyntaxError (duplicate let/const el in the same scope) that prevents the entire module from loading.

Confidence Score: 1/5

  • The file browser module will not load at all due to a parse-time error introduced in pushToNavbar.
  • pushToNavbar declares el twice in the same block — once with let and once with const — which JavaScript parsers reject before executing a single line of the module. Every navigation event goes through this function, so the entire file browser is broken from first load. Fixing it also requires removing the undeclared f reference used as a factory argument.
  • src/pages/fileBrowser/fileBrowser.js — specifically the pushToNavbar function at lines 1627–1647 which must have the duplicate el declaration and the undeclared f variable resolved before anything else in this PR can be tested.

Important Files Changed

Filename Overview
src/pages/fileBrowser/NavStack.js New NavStack class extending EventTarget with private #urlSet / #arr state, microtask-batched "update" events, and bounds-checked negative-index get(). Logic is sound; the class is self-contained and well-structured.
src/pages/fileBrowser/fileBrowser.js Major refactor converting state[] to NavStack, plain object cache to Map, and synchronous rendering to async renderCurrentDir. Contains a parse-time SyntaxError in pushToNavbar (duplicate let/const el declaration) that prevents the entire module from loading, plus an undeclared f variable used as an argument in the same function.
src/pages/fileBrowser/fileBrowser.scss Adds flexbox centering for #msg / #spinner and a :has(> [data-action="prevDir"]) height-offset rule for the parent-dir tile. Straightforward and self-contained.
src/pages/fileBrowser/list.hbs Template refactored to add a conditional .. parent-directory tile (data-action="prevDir") and move the empty-dir message inside a {{^list}}{{#msg}} guard. Logic matches the new renderCurrentDir rendering contract.

Sequence Diagram

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

    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)
    else new URL
        NavStack-->>navigate: false
        navigate->>NavStack: push(url, name)
    end
    NavStack-->>navigate: fires "update" microtask
    navigate->>renderCurrentDir: renderCurrentDir()
    renderCurrentDir->>AbortController: abort previous _rndrAbortCtrl
    renderCurrentDir->>AbortController: new AbortController
    alt list not cached
        renderCurrentDir->>renderCurrentDir: append spinner placeholder
        renderCurrentDir->>getDirList: getDirList(url)
        getDirList->>getDirList: Promise.race([lsDir, 15s timeout])
        getDirList-->>renderCurrentDir: list[] or throws
        renderCurrentDir->>AbortController: check abortSignal.aborted
        alt aborted
            renderCurrentDir-->>User: (no DOM update)
        else not aborted
            renderCurrentDir->>renderCurrentDir: replace placeholder with $list
        end
    else list cached
        renderCurrentDir->>renderCurrentDir: render immediately from cache
    end
    renderCurrentDir->>renderCurrentDir: cachedDir.set(url, dir)
Loading

Reviews (31): 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

@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

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

@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from f127b23 to 6e88a94 Compare August 21, 2026 06:03
@AuDevTist1C

Copy link
Copy Markdown
Contributor Author

@greptile review this, because #2776 has been merged.

Also be sure to review from the base branch all the way to every last commit and diff in this PR so you don't have to miss any new changes, like you've missed with loadStates() last time. Don't rely on old conmits that have been git push -f'ed out. Also, please reference a commit if you're pointing out any issues.

@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch 2 times, most recently from 3ca79e9 to 6e02451 Compare August 22, 2026 12:03
Comment thread src/pages/fileBrowser/fileBrowser.js
@AuDevTist1C
AuDevTist1C force-pushed the refactor/file-browser branch from 6e02451 to e036de8 Compare August 22, 2026 12:18
Comment thread src/pages/fileBrowser/fileBrowser.js
@AuDevTist1C

AuDevTist1C commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

@greptile review commit 81ac2ab6c846fda4a9976232dddfc13867d1aa52 and #2793

… management

Abstract navigation tracking, history state management, and navbar UI syncing into a dedicated `EventTarget` class.

Create `NavStack` class (`src/pages/fileBrowser/NavStack.js`):
- Implement `NavStack` extending `EventTarget` with a custom `Symbol.toStringTag` property
- Add `push`, `pop`, `popUntil`, `get` (supporting negative indexing), `has`, `on`, `off`, and `toJSON` methods with parameter validation
- Maintain an internal `#urlSet` to prevent duplicate stack entries
- Queue microtasks for `update` event dispatching, providing read-only `added` and `removed` location diffs in event details

Integrate `NavStack` into file browser (`src/pages/fileBrowser/fileBrowser.js`):
- Replace manual `state` array and direct `localStorage` persistence with a `NavStack` instance
- Listen to `update` events on `NavStack` to persist state to `localStorage`, clean up removed navbar elements and `actionStack` entries, and register new back-navigation actions
- Cache navbar DOM elements using a `navBarEls` Map with `getOrInsertComputed`
- Refactor `navigate` to accept location objects or strings and manage stack state using `navStack.has`, `navStack.popUntil`, and `navStack.push`
- Refactor `loadStates` to push history entries into `navStack` and navigate directly to the top item (`navStack.get(-1)`)
- Update folder selection button state (`$openFolder.disabled`) in `render()` and remove obsolete `pushState()` helper function

(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 e036de8 to 81ac2ab Compare August 22, 2026 14:11
Comment on lines +1628 to 1647
/** @type {() => HTMLSpanElement} */
let el = navBarEls.get(url, f);
if (!el) {
el = (
<span
id={getNavId(url)}
className="nav"
data-url={url}
data-name={name}
data-action="navigation"
attr-text={name}
tabIndex={-1}
></span>
);
navBarEls.set(url, el);
}
const el = navBarEls.getOrInsertComputed(url, f);
$navigation.append(el);
$navigation.scrollLeft = $navigation.scrollWidth;
}

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.

P0 SyntaxError — duplicate el declaration crashes entire module

pushToNavbar contains both let el (line 1629) and const el (line 1644) in the same block scope. JavaScript throws a parse-time SyntaxError: Identifier 'el' has already been declared before any code in the file runs, so the file browser never loads at all. This is a merge artifact: the manual if (!el) guard approach and the getOrInsertComputed one-liner were both left in. Additionally, f is referenced as a second argument to both calls but is never declared anywhere in scope — once the duplicate-declaration error is fixed, every call to pushToNavbar would immediately throw a ReferenceError: f is not defined.

The fix is to remove the manual let el / if (!el) { ... } navBarEls.set(url, el) block and keep only the getOrInsertComputed line (with a proper inline factory instead of the undeclared f), or conversely keep the manual block and remove the getOrInsertComputed call entirely.

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