Skip to content

Feat/s3 file browser v1 - #208

Open
dklOrdix wants to merge 178 commits into
mainfrom
feat/s3-file-browser-v1
Open

dklOrdix wants to merge 178 commits into
mainfrom
feat/s3-file-browser-v1

Conversation

@dklOrdix

@dklOrdix dklOrdix commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Closes #129

dklOrdix and others added 30 commits May 4, 2026 09:18
…ebar imports

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…s/utils

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
feat(storage): initial Filebrowser UI implementation
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…ation

S3 File Browser Page Navigation (Pagination)
* #96 disable the storage browser if feature flag is set

* #96 fix svelte-check

* #96 fix lint

* #96 disable storage browser by default
* #102 refactor pagination controls into a separate component

* #102 simplified pagination props
* #93 add modal preview for file display

* #93 fix broken e2e tests

* #93 add preview for parquet files, fix scrollbar visibility in preview, allow ms excel csv data for preview

* #93 improve parquet row fetch handling

* #93 fix lint

* #93 move preview endpoint logic into separate files

* #93 disable parquet preview
* refactor(ObjectTable): extract File and Folde rows into seperate components

* style: format code

* style: use camelCase

* style: use camelCase
* feat: implement single-file download functionality and error handling for S3 storage

* feat: refactor S3 download endpoint to use new API structure and improve error handling

* chore: remove obsolete note about static download route segment in tech debt documentation

* style: format code

* fix: improve type safety in S3 error handling

* fix: preview action

* feat: add context menu header and improve file selection handling in FileExplorer

* fix: reset selection state on context menu close and refine download action logic

* style: format files

* style: remove pointer events from Actions row on ContextMenu

* #91 fix lint

---------

Co-authored-by: F3l1x1vo <fli@ordix.de>
@stackable-cla

stackable-cla Bot commented Sep 10, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
0 out of 2 committers have signed the CLA.

❌ F3l1x1vo
❌ dklOrdix
You have signed the CLA already but the status is still pending? Let us recheck it.

# Conflicts:
#	TECH_DEBT.md
#	src/lib/server/trino/queries.ts
#	vite.config.ts
Comment thread src/lib/server/storage/archive.ts Outdated
Comment thread src/architecture/i18n-compliance.spec.ts Outdated
Comment thread src/architecture/code-size-limits.spec.ts Outdated
Comment thread src/lib/server/storage/preview/csv.ts Outdated
Comment thread src/lib/server/storage/preview/csv.ts Outdated
Comment thread src/lib/components/storage/modals/RenameModal.svelte Outdated
Comment thread src/lib/components/storage/modals/CreateModal.svelte Outdated
Comment thread src/lib/server/storage/preview/csv.ts Outdated
Comment thread src/routes/(app)/api/storage/archive/extract/+server.ts Outdated
Comment thread src/routes/(app)/api/storage/buckets/+server.ts Outdated
Comment thread src/lib/server/storage/wrap-provider.ts Outdated
Comment thread src/architecture/ui-pattern-enforcement.spec.ts Outdated
Comment thread src/lib/components/storage/explorer/TabBar.svelte
Comment thread src/lib/server/storage/archive.ts Outdated
Comment thread AGENTS.md
Comment thread package.json Outdated
Comment thread playwright.config.ts Outdated
const chromiumExecutablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;

export default defineConfig({
workers: process.env.CI ? 1 : 1,

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.

I see, but the env.ci check does nothing so it can be set to 1 directly.

And please add a comment, so we know in the future

Comment thread src/lib/components/storage/landing/AddBucketModal.svelte Outdated
Comment thread dev/modules/prerequisites.sh Outdated
Comment thread src/lib/components/layout/header/UserMenu.svelte Outdated
Comment thread src/lib/editor/format-json.ts
Comment thread src/lib/components/TabBar.svelte Outdated
Comment thread src/lib/server/storage/preview/parquet.ts
Comment thread src/lib/components/storage/modals/StorageModals.svelte

@Bohreromir Bohreromir left a comment

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.

A lot of the Playwright tests fail for me, do they for you too?

Here is some claude as a starting point:

  1. storage/preview.spec.ts — PDF preview: timed out waiting for tbody tr or "This bucket is empty" after navigating to a bucket route.
  2. storage/recent-items.spec.ts — removes a deleted file from Recent Files: timed out (60s, ×3 attempts via retries) waiting for the "Toggle selection mode" button.
  3. storage/recent-items.spec.ts — removes files/location when a directory is deleted: Error: Channel closed / browser already closed mid-action.

The screenshots are the key clue. In both failures #1 and #2, the captured screenshot shows the Storage overview page (bucket card grid + Recent Files tab) — not the bucket browsing page the test expected. So page.goto(bucketRoute(...)) is redirecting back to /storage instead of opening the bucket.

This is masked by a bug in the test helper waitForObjectsLoaded() (e2e/storage/helpers.ts:185-192): it waits for tbody tr or "This bucket is empty" as a generic "did objects load" signal. But the Storage overview page's Recent Files tab also renders a

, so when the redirect happens and Recent Files has an entry, that locator matches by coincidence — the helper reports success on the wrong page. The test then tries to click "Toggle selection mode" (a bucket-only control), which never appears, and burns the full 60s test.slow() timeout, ×2 retries.

@Bohreromir Bohreromir left a comment

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.

Please also fix the new lint warnings introduced:

security/detect-non-literal-fs-filename (11)
- src/architecture/code-size-limits.spec.ts:23,38,53
- src/architecture/helpers.ts:57 (plus detect-non-literal-fs-filename for readdirSync at :29)
- src/architecture/i18n-compliance.spec.ts:39
- src/architecture/server-logging.spec.ts:29,56
- src/architecture/ui-pattern-enforcement.spec.ts:35,47,73,91

svelte/no-navigation-without-resolve (8):
- src/lib/components/storage/landing/RecentItems.svelte:132,158,195,203,216
- src/routes/(app)/settings/connections/[id]/edit/+page.svelte:77
- src/routes/(app)/storage/+error.svelte:86
- src/routes/(app)/storage/+layout.svelte:42,45

security/detect-non-literal-regexp (1):
- e2e/storage/archive.spec.ts:174

Comment thread src/architecture/circular-dependencies.spec.ts Outdated
Comment thread src/architecture/circular-dependencies.spec.ts
Comment thread src/architecture/server-client-boundary.spec.ts Outdated
Comment thread src/architecture/ui-pattern-enforcement.spec.ts Outdated
Comment thread src/architecture/ui-pattern-enforcement.spec.ts Outdated
Comment thread src/lib/components/layout/sidebar/Sidebar.svelte Outdated
Comment thread src/lib/server/storage/preview/csv.test.ts Outdated
Comment thread src/lib/components/storage/explorer/ContextMenu.svelte Outdated
Comment thread src/lib/components/storage/modals/AddBucketModal.svelte
Comment thread src/lib/server/storage/connections-db.ts
@F3l1x1vo

F3l1x1vo commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

A lot of the Playwright tests fail for me, do they for you too?

Sometimes they do fail locally for me when developing under resource strain. This flakiness also happens in the GitHub Actions. I've hopefully improved this in b94619d.

Comment thread src/routes/(app)/storage/page.server.test.ts
Comment thread src/lib/components/storage/modals/PreviewModal.svelte.spec.ts
Comment thread src/lib/storage/archive.svelte.ts Outdated
Comment thread src/lib/components/storage/sidebar/BucketList.svelte.spec.ts
Comment thread e2e/storage/file-operations.spec.ts Outdated
Comment thread src/routes/(app)/api/storage/rename/+server.ts Outdated
Comment thread src/routes/(app)/api/storage/delete/+server.ts
Comment thread src/routes/(app)/api/storage/buckets/+server.ts Outdated
Comment thread docs/DATABASE.md
Migrations are stored in `src/lib/server/migrations/`.

The current migration history is a single baseline migration. Existing databases
created with the prior migration sequence must be reset before applying it.

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.

Wait, is this deployed somewhere?

/**
* Shared helpers for architecture fitness tests.
*
* ArchUnitTS only scans TypeScript (.ts) source files. Svelte (.svelte) and

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.

its not

Comment thread eslint.config.js
'svelte/no-navigation-without-resolve': 'error',
'svelte/no-restricted-html-elements': [
'error',
{ elements: ['dialog'], message: 'Use the shared Modal component instead of <dialog>.' }

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.

ha, smart! ill see that one in the future :D

* conflict - new name already exists in the bucket
*/
export type RenameState = 'idle' | 'editing' | 'checking' | 'ok' | 'conflict';
export type { Resolution, RenameState };

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.

is this reexporting intentional?

Comment thread AGENTS.md
| Server / Client Boundary | `src/lib/client`, `stores`, `storage`, `editor`, `types` must not import `src/lib/server/**` |
| No Circular Dependencies | `src/lib/**` (excluding Trino) and `src/routes/**` must be cycle-free |
| Naming Conventions | Stores → `*.svelte.ts`; lib components → PascalCase `.svelte`; server files → no `.svelte.ts` extension |
| Code Size Limits | `.ts` files < 2 400 LOC; `.svelte` files < 1 100 LOC; test files < 1 000 LOC |

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.

outdated

const isLegacyMode = $derived(text !== '' && headers.length === 0);
const isSimpleMode = $derived(!isLegacyMode && !fetchRows && headers.length > 0);

// ── Legacy mode: text-based CSV rendered via PapaParse ──

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.

This has got nothing todo with legacy in this implementation. The "legacy" code path is used exclusive for streaming tsv. Please name it accordingly.

On another note: Whats the reason for differentiation between csv and tsv? Shouldn't make a difference what the limiter is for presenting it to the user

let name = $state('');
let inputEl = $state<HTMLInputElement | null>(null);

const NAME_INVALID_CHARS = /[^\w\s./()\-+@,:;!$*'=]/g;

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.

This also exists in StorageBreadcrumb.svelte:82-86.
Both also duplicate the === '.' || === '..' check

And Rename.modal has neither of them, so one can just create a valid file and then rename it to something bad.

And this is where it gets even worse, this is only client side validation, not server side!

Please write a shared zod schema, validate it here and in RenameModal, the breadcrumb and then finally in create/move/copy's +server.ts handlers

const connections = rows.map((row) => {
let endpoint: string | null = null;
try {
const payload = JSON.parse(decrypt(row.encryptedPayload, getKey())) as {

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.

as only verifies buildtime. This check and if (!body.sourceKeys?.length) below can be checked with a zod schema to protect the runtime too.

in api/trino/query/+server.ts via Schema.safeParse(body) its already done correct.

The other endpoints also miss zod (pi/storage/copy, move, delete, connections +server.ts)

if (rows.length === 0) return null;

try {
const payload = JSON.parse(

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.

please use zod here as well as in settings/connections/**/+page.server.ts

we already have a shcema here: src/lib/storage/schemas.ts

});

it('maps access_denied code', () => {
expect(getActionErrorMessage(new StorageError('access_denied', ''))).toBe('Access denied');

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.

not to sure about these tests. We set the label to something static and the test if the msg is realy the msg we set in this file. What are we testing in here?

key: Buffer
): string {
const hmacKey = deriveHmacSubkey(key);
const material = [

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.

this can cause collides.
claude suggests:
Fix: use an encoding where the boundary is unambiguous regardless of field content, e.g. length-prefix each field before concatenating (${endpoint.length}:${endpoint}${region.length}:${region}...), or hash each field independently and concatenate the fixed-size digests instead of the raw strings.

Comment thread src/lib/storage/errors.ts

// ── ActionError (backward-compatible alias) ──────────────────────────────────

export class ActionError extends StorageError {

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.

pls remove, and in clipboard.svelte.ts:296 remove that err instanceof ActionError as it would never have been true anyways. just check for instanceof StorageError

tabs: PersistedTab[];
activeTabId: string;
/** Fingerprint of the connection that saved these tabs.
* Absent in data saved before this field was introduced (treated as a match

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.

What Model are you using? It really does like to add backwards compatibility were its not needed.

clipboardState: ClipboardState;

/**
* Delegated clipboard data getter for backward compatibility.

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.

What backward compatibility?

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.

Epic: S3 File Browser v1

5 participants