Skip to content

Add multi-bucket support - #209

Merged
Rakesh-46-VR merged 7 commits into
mainfrom
feat/multi-bucket-ui
Sep 3, 2026
Merged

Add multi-bucket support#209
Rakesh-46-VR merged 7 commits into
mainfrom
feat/multi-bucket-ui

Conversation

@yash-sangwan

@yash-sangwan yash-sangwan commented Sep 3, 2026

Copy link
Copy Markdown
Member

Before this, you were stuck in one bucket. The bucket was set when you connected, and the only way to reach another one was to log out and type your keys again.

What you can do now

Switch buckets. The top bar shows the bucket you are in. Click it to see your other buckets, search them, and pick one.

Make a new bucket. The menu has a "New bucket" button. Type a name, pick a region, done.

Delete a bucket. Every bucket in the list has a delete button, except the one you are currently in.

Things we were careful about

  • Switching checks first. If the bucket does not exist, is in another region, or your keys cannot read it, nothing changes and you stay where you were. The message says what went wrong.
  • Your uploads are safe. Switching cancels uploads and deletes that are still running, so we ask you first and show how many there are.
  • Bad names are caught early. Bucket names follow strict S3 rules. We check the name as you type and say which rule was broken, instead of letting S3 reject it with an unhelpful error.
  • Region only when it is a real choice. On AWS you can pick any region. Other providers (R2, Wasabi, MinIO, and so on) lock the location to their endpoint, so we just tell you where the bucket will go.
  • Deleting is careful. It asks first, it never touches the bucket you are working in, and S3 only removes empty buckets, so a bucket with files in it is reported, not emptied.
  • We do not waste requests. Listing buckets costs money, so we only ask when you open the menu, and after making or deleting a bucket we update the list ourselves instead of asking again.

The bucket was fixed at connect time: reaching another one meant clearing the
session and typing the keys again. `switchBucket(bucketName, region?)` moves
the session to another bucket on the credentials it already has.

It builds a new BYOS3ApiProvider rather than calling setBucketName, because
identity is what everything downstream watches to know its world has changed -
the search service memoises on it, the download store keys off it, the upload
executor compares against it. Renaming the bucket underneath them leaves all
three serving the old bucket while the app shows the new one.

The candidate is proved with the same one-object listing /connect uses before
anything of the current session is disposed, so a bucket that is missing, in
another region, or unreadable by these keys costs the user nothing. Only after
it verifies does the existing initializeUploadManagers teardown run, which
stays the one canonical path for rebuilding bucket-scoped services.

Switching cancels every upload and delete in flight, so it refuses to do that
unasked: it answers with the counts instead, and proceeds once the caller
passes discardActiveWork. The prefix is dropped rather than carried across,
since it names a folder in the bucket being left.
The s3-api has listed buckets since 3.0 and nothing in the app ever called it.
`useBuckets` is the data layer the switcher will read from: it discovers,
searches and pages, and does nothing else - selecting a bucket stays
`useAuth().switchBucket`, which owns the S3 identity outright.

Nothing is requested until the switcher asks. The dashboard mounts this on
every page while hardly anyone opens it, and ListBuckets is billed, so the
fetch is gated on the provider discovery was started for rather than a flag -
a flag still reads true during the render that swapped the provider, and fired
a request for a switcher nobody had opened. A request already in flight is
joined rather than repeated, which also stops React Strict Mode buying every
development open twice.

Searching is layered because the server side of it is weaker than it looks.
ListBuckets matches Prefix byte for byte, so it finds production-eu and misses
my-production, and several S3-compatible providers ignore the parameter
entirely. The server narrows, then a case-insensitive contains decides what is
shown. A term with capitals is not sent at all: bucket names are lowercase, so
the server would have thrown away the very rows the filter exists to find.

Whether more pages exist is the server's word and never the number of rows the
filter left visible, or a page whose matches are all further down would be
unreachable. Provider identity is the cache identity, checked as the list is
read rather than in an effect, so a switch cannot show the previous session's
buckets even for one frame.

A failure to list is not a broken session: listing needs s3:ListAllMyBuckets,
which the connect guides never ask for, and some providers do not implement the
call. That surfaces as `unavailable` for the switcher to offer a bucket name
field instead, and touches neither the credentials nor the current bucket.
The bucket a session works in was only visible on the connect form that set it.
A switcher now sits beside the menu button, naming the current bucket and
opening a list of the others: search at the top, the current one marked, the
region beside each bucket that reported one.

It is strictly a view. `useBuckets` finds the candidates and
`useAuth().switchBucket` performs the switch and owns everything behind it, so
nothing here verifies, navigates, disposes managers or keeps a second opinion
about which bucket is current. The list is asked for when the menu opens and
never on mount: ListBuckets is billed and the dashboard mounts this on every
page.

Choosing a bucket that has uploads or deletes running is answered with a
question rather than silently cancelling them, and the second call only goes
out once someone has said yes. A bucket the provider will not verify leaves the
session where it was and says why. Where a connection cannot list buckets at
all - the permission for it is not one the connect guides ask for - the panel
offers a name to type instead of implying the session is broken.

Two things a menu does not expect an input to be inside it for had to be
arranged deliberately: the field is given the opening focus, because taking it
back afterwards loses a race with the menu and sends every keystroke to its
typeahead, and the first arrow key hands the list a row to start its own
navigation from, because a menu only acts on arrows aimed at itself.

Switching also now clears the operations card. Aborting the work in flight left
every record of it behind, so the previous bucket's uploads and deletes came
across with it, naming files that are not in the bucket on screen.
Bucket regions were missing from the one listing that matters most. S3 fills in
a bucket's region only when the ListBuckets request carries at least one valid
parameter, so searching returned regions and simply opening the switcher did
not - and switching to a bucket in another region built a client for the region
being left, which then failed to verify for no reason the user could see.
getBuckets now sends MaxBuckets on every request. It is 10,000, the page size S3
already applies by default, so it changes what comes back rather than how much.

Providers that ignore parameters they do not implement are unaffected, and a
region that goes unreported stays unreported rather than being guessed at: the
frontend passes undefined through to switchBucket, which keeps the current
region, and a bucket that turns out to be elsewhere fails verification with the
session intact.

Documents the permission split the feature introduced. Listing buckets needs
s3:ListAllMyBuckets, granted on the account rather than a bucket, and none of
the four permissions the connect guides ask for imply it. Switching does not
need it: the check run against the bucket being moved to is the same single
listing the connect form makes. So the guide now says which of the three things
- browsing one bucket, seeing a list to pick from, switching by typing a name -
needs what, and the developer notes record the search, pagination, region and
provider-compatibility contract of getBuckets.
The switcher's footer offered a field for typing the name of a bucket that
already existed, which only helped people who already had one. It now opens a
form that creates one, and each row in the list can be deleted.

Names are checked against S3's own rules before a request is spent on them, and
each rule says which one was broken - an InvalidBucketName error names none of
them. A dot is legal and stays legal, but is warned about: the wildcard
certificate does not match a dotted bucket, so HTTPS to one fails in some tools.

Region is a real choice only on AWS. S3 refuses a CreateBucket whose
LocationConstraint does not match the regional endpoint the request arrived at,
and the SDK has no per-request region override, so a region argument passed down
to createBucket would produce the very error it appears to solve. A provider is
built for the chosen region instead, the same way switchBucket already does it,
and thrown away afterwards - creating a bucket elsewhere does not move the
session there. Every other provider stores a resolved endpoint, and that URL
sets the location, so the region is named rather than offered. The absence of a
stored endpoint is the signal, since the provider slug is not persisted.

Deleting is offered on every bucket but the one in use, which would leave the
session addressing something that is not there. It asks first, and S3's refusal
to remove a non-empty bucket is reported rather than worked around: nothing here
empties a bucket on someone's behalf.

Both update the loaded list in place rather than listing buckets again, which is
billed and would answer with what the call that just succeeded already knows.
The delete control is a sibling menu item rather than a button nested in the
row, because a menu's roving focus only visits its own items - nested, it would
have been clickable and unreachable by keyboard.

Also defines --popover, which nothing did. DropdownMenuContent paints itself
with bg-popover, so every menu in the app rendered as text over whatever was
behind it, and only the profile menu escaped by passing a background of its own.
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
opndrive Ready Ready Preview Sep 3, 2026 6:38pm UTC
opndrive-docs Ready Ready Preview Sep 3, 2026 6:38pm UTC

Comment thread frontend/src/context/auth-context.tsx Dismissed
@Rakesh-46-VR
Rakesh-46-VR merged commit 96e58e2 into main Sep 3, 2026
11 checks passed
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.

3 participants