The way that it works, at a high level, is similar to how SRP works. Two random salts are generated (let's say, A and B), where A is used for authentication (and hence public) and B is used for deriving the other cryptographic keys.
When you authenticate, you retrieve A and then you prove to the server that you know what scrypt(A, password) is. At this point, the server provides you with B, and you can use this information to derive scrypt(B, password), which in turn you use to derive other cryptographic keys.
It being an oblivious password store, there are other steps taken to make the protocol stateless (from the perspective of the server) and to make parties commit to random values used so that runs of the protocol cannot be replayed.
> but the password database itself is still vulnerable to dictionary attacks once it’s stolen
This is correct, and I'm not sure there are good ways to prevent this or equivalent scenarios from occurring. Therefore, you should see this as an additional layer on top of your already secure password and not as a substitute for a secure password.
The reason for having this mechanism rather than not having it is to protect your password from brute-forcing by the public at large, in a scenario where the server operator is semi-trusted. Without this implementation, you have three alternatives: (1) Forego passwords entirely; (2) make the salted password 'public', along with the salt, which is all the information you need to brute-force it or (3) make more "normal" authentication flows without PAKE, in which case you need to trust the server even more. If you insist on using passwords, this is a compromise solution between (2) and (3), i.e., between anyone can break it and trust the server entirely.
[1] <https://github.com/okTurtles/group-income/blob/e2e-protocol/...>
[2] <https://github.com/okTurtles/group-income/blob/e2e-protocol/...>