Skip to content

Move frontend state to runes and split the browse page - #136

Merged
jR4dh3y merged 6 commits into
hoplite/euhesperides-d9702ac5--parse-boundaryfrom
hoplite/euhesperides-d9702ac5--stores
Oct 1, 2026
Merged

jR4dh3y merged 6 commits into
hoplite/euhesperides-d9702ac5--parse-boundaryfrom
hoplite/euhesperides-d9702ac5--stores

Conversation

@usehoplite

@usehoplite usehoplite Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The frontend kept state in two styles. Auth, jobs, settings and the WebSocket used the old svelte/store API ($store syntax, derived stores, get()), while uploads, toasts and the clipboard already used runes. The browse page was one 876-line component that held URL state, queries, every dialog, file operations, drag-and-drop and the template, so no part could be read or changed on its own.

Change

Each step is its own commit and passed check, lint and build before the next.

  1. Jobs and WebSocket become rune classes. Unused derived stores and methods are removed. The active job list is a $derived on the store.
  2. Settings becomes a rune class with only the methods that are used (8 of 14). The pure colour and wallpaper helpers move to utils/appearance.ts. The async wallpaper lookup moves to an $effect in the layout. null as string | null is gone with the old derived store.
  3. Auth becomes a rune class, and isAuthenticated, authError and the other derived stores become fields.
  4. Browse page drops from 876 to 209 lines. Its logic moves to lib/browse/: location (URL, history, sort, search), data (queries and derived lists), actions (dialogs, create, rename, delete, paste, clipboard, preview), uploads (picker and drag-and-drop) and selection. App.PageState now declares browseHistoryIndex, which removes two casts.
  5. Fix found in the browser test. After the WebSocket migration, connect() read connectionState inside the layout's $effect, so the effect re-ran on every state change and reconnected at once. After a server restart the page opened 36 sockets in about 5 seconds. The state read is now untracked. The old store used get(), which does not track.

Old store files are deleted and every importer is migrated in this PR.

The reconnect timing is now tested (websocket.test.ts). The tests cover a connect() call from an $effect, delays that double up to the maximum, and a clean close. bun test loads .svelte.ts modules through a small preload (frontend/test/svelte-plugin.js) that runs the Svelte compiler. bun run test passes --conditions browser so that Svelte loads its client build. This adds @types/bun as a dev dependency.

Verification

Ran the built app (the stack tip with the frontend embedded) in a real browser against a real server with auth on:

  • Sign-in with a wrong password shows the error. Sign-in with the right one lands on /browse. Reload restores the session. Logout returns to /login. Unauthenticated /browse redirects to /login.
  • Drive cards, folder navigation, back and forward button state, list and grid toggle, sort by size, and search (?q=) all update the URL and the list.
  • Create folder, rename and delete (through the confirm dialog) all worked on disk.
  • Upload of an 8 MB file made 3 chunk requests at chunk_size_mb: 3 (this also covers PR 2). The stored file's SHA-256 matched.
  • Copy then paste made a job. The browser received newline-batched job_update frames, going from running to completed at 100%, and the copied file matched.
  • With the fix: after stopping the server for about 7 seconds, reconnect attempts were spaced about 1 s, 2 s and 4 s. Before the fix there were 36 attempts in 5 s.
  • Pin to favorites shows in the sidebar and persists. Changing the accent colour and saving sets the CSS variable, survives a reload, and shows no console errors or warnings.

Not tested: drag-and-drop upload, the preview modal, share dialogs, the wallpaper picker, the 14-minute token refresh and the "session expired" path, keyboard shortcuts and multi-select, --dev mode (it fails at startup on master, see below), and browsers other than Chromium.

With the untrack line removed, the effect test fails with effect_update_depth_exceeded. With it, all 7 frontend tests pass. bun run check, lint, test and build pass, and bun install --frozen-lockfile accepts the lockfile.

--dev mode already fails on master: main.go hashes the dev password at bcrypt.MinCost, and config validation requires cost 10 or more. This PR does not change it.

Stack: 5 of 6. Targets the branch of the PR below it.


Written by anthropic/claude-sonnet-5-5 in the Hoplite agent harness.

usehoplite Bot and others added 5 commits October 1, 2026 07:42
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
box-box Ready Ready Preview Oct 1, 2026 8:04am UTC

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 43f9b641-4471-492e-b824-20cfb856ddd2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment on lines +38 to +40
connect(allowUnauthenticated = false): void {
// An effect calls this. It must not depend on the state it changes, or every
// state change would reconnect at once and skip the backoff delay.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Reconnect timing lacks test coverage. The layout calls connect() from an effect, and this untrack guard keeps connection-state changes from triggering immediate retries. There is no automated test for that timing, so a future change could silently bypass the backoff again. Please add a test that drops the connection and checks the retry intervals.

Knowledge Base Used: Frontend data and real-time state

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!

@jR4dh3y
jR4dh3y added this pull request to stack #135 October 1, 2026 08:00
…lite/euhesperides-d9702ac5--stores

# Conflicts:
#	frontend/src/lib/stores/websocket.ts

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>
@jR4dh3y
jR4dh3y merged commit 8033c74 into staging/main Oct 1, 2026
8 checks passed
jR4dh3y added a commit that referenced this pull request Oct 1, 2026
* Fix auto_discover mounts being dropped at startup (#131)

* Fix auto_discover mounts being dropped at startup

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Make the auto_discover test pick a mounted directory and check it is returned

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Make chunk_size_mb control browser upload chunks (#132)

* Make chunk_size_mb control browser upload chunks

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Share one chunk size request across upload workers and respect cancel

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Resolve virtual paths in one place (#133)

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Parse WebSocket and job data at the boundary (#134)

* Parse WebSocket and job data at the boundary

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Validate job progress range, add parser tests, log rejected frames briefly

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Move frontend state to runes and split the browse page (#136)

* Migrate jobs and websocket stores to runes

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Migrate settings store to runes and split out appearance helpers

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Migrate auth store to runes

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Split the browse page into composables

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Keep the WebSocket reconnect backoff when connect runs in an effect

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Fix the auth API docs, drop stale comments, add AGENTS.md (#137)

* Fix the auth API docs, drop stale spec comments, add AGENTS.md

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Add the frontend test command to AGENTS.md

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Correct the logout docs and scope the path rule

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Fix --dev failing config validation (#139)

* Fix --dev failing config validation

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Isolate the dev credentials test from inherited BoxBox settings

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Test the WebSocket reconnect backoff (#138)

* Test the WebSocket reconnect backoff

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Keep the connection mode on WebSocket retries and test the attempt limit

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Fix browsing discovered mount points

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

* Skip inaccessible auto-discovered mounts

Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

---------

Co-authored-by: usehoplite[bot] <288093033+usehoplite[bot]@users.noreply.github.com>
Co-authored-by: Radhey Kalra <radheykalra901@gmail.com>

This branch was successfully deployed

1 active deployment
Preview — f0eda657 Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant