mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-14 15:50:10 +00:00
* fix: settle which default plan an org seat resolves to, and what counts as paid Default subscription resolvers ran in registration order, which is boot order: whichever layer registered first answered first. A seat is provisioned by a team and its address is never confirmed, so the seat resolver now states a priority and wins over anything a deployment infers from an address. The seat cap asked whether the owner's plan was outside the free set, which a default plan can be without anyone having bought it. Metering now reports whether an explicit subscription answered, and the cap rests on that. * fix: the review's findings on plan resolution `fromDefault` was read off which resolver answered, not off which policy applied. An explicit id naming a policy nobody registered falls through to the default one, and that read as bought. It follows the applied policy now. A deployment with unlimited metering has no explicit resolver, so every owner on it became unpaid and its teams dropped from the paid seat cap to the free one. Unlimited answers for what a paid plan would, so it counts as one. Also: the new constants had been placed between `FREE_SUBSCRIPTION_IDS` and its docblock, the register docstring still described first-wins, and prettier had reflowed an untouched licence header. * feat: a seat confirms the address its team typed in An address on a seat is the team's claim about someone else's inbox, and nothing asked the inbox. It is now demanded and sent a code, so the address counts for nothing until the seat hands it back. Resolvers that read a domain are the reason this matters. No address is still fine and demands nothing: the team is the trust anchor for the account, not for a mailbox it does not hold. `/confirm-email` allows an unconfirmed actor, so a seat can sign in with its temporary credential and clear the demand; nothing locks it out. * fix: clear the confirmation demand with the address it names `clearUnconfirmedEmail` releases a seat's address so the inbox's owner can sign up with it. The demand to confirm that address has to go at the same write: the gate reads `requires_email_confirmation && !email_confirmed`, and once the address is gone there is nothing left to send a code to. * fix: keep a seat's address correctable, and only ask what can be answered Review of the confirmation demand turned up two ways a seat could be left holding one it had no way to meet: - With no mail transport configured, the code is never sent and a resend cannot reach the seat either, so the demand stood with nothing able to lift it. The seat is now gated only when a code can actually travel. - An address the owner mistyped gated the seat on an inbox nobody could read, and the gate itself closes `/user-protected/change-email`, the route the seat would have fixed it by. `updateSeatEmail` lets the owner who typed the address retarget it, and refuses once it is confirmed, when it belongs to whoever proved it. Also resolves the owner's plan sequentially, so the second read takes the cache the first wrote and the pair cannot come from two resolutions, and restores the licence header a jsdoc reflow had rewrapped. * fix: let the confirm-email window open before the profile has loaded Booted from a stored token, the window renders before `window.user` is populated, and reading `.email` off null threw inside the window itself -- so the one screen that could have asked for the code never drew, leaving a blank page. Reproducible without a team: sign up, then load the GUI again before confirming. The address is now encoded rather than interpolated raw, and both forms of the line are i18n keys; it was hardcoded English before.