| index 4d6f932..d04e998 100644 |
| --- a/home/web/static/home.js |
| +++ b/home/web/static/home.js |
| @@ -3314,8 +3314,11 @@ async function requestSpace() { |
| // and grind it out right here, during the fader's build phase β the CPU |
| // burn IS the "Hold onβ¦" moment. bits 0 = no gate, nothing to solve. |
| const proof = await solveCreateChallenge(); |
| + // No name: a space is born nameless and Eleven asks inside it (the setup |
| + // conversation's "group" step), where the answer is a reply in a |
| + // conversation instead of a form field on the way in. Home learns the name |
| + // when the space's key is added to this account (the enrol payload's entry). |
| const body = { ...(proof || {}) }; |
| - if (planChoice && planChoice.name) body.name = planChoice.name; |
| const res = await fetch("/api/spaces", { |
| method: "POST", |
| headers: { Authorization: "Bearer " + state.token, "Content-Type": "application/json" }, |
| @@ -3355,13 +3358,13 @@ async function requestPaidSpace() { |
| const res = await fetch("/api/spaces/checkout-create", { |
| method: "POST", |
| headers: { Authorization: "Bearer " + state.token, "Content-Type": "application/json" }, |
| - body: JSON.stringify({ ...(proof || {}), name: planChoice.name, tier: planChoice.tier }), |
| + body: JSON.stringify({ ...(proof || {}), tier: planChoice.tier }), |
| }); |
| const reply = await res.json().catch(() => ({})); |
| if (!res.ok) throw new Error(reply.message || reply.error || `Couldn't start the checkout (HTTP ${res.status}).`); |
| if (!reply.url) throw new Error("The checkout link didn't come back β try again."); |
| if (reply.session_id) { |
| - rememberPendingCreate({ session: reply.session_id, name: planChoice.name, at: Date.now() }); |
| + rememberPendingCreate({ session: reply.session_id, at: Date.now() }); |
| } |
| location.href = reply.url; |
| return new Promise(() => {}); |
| @@ -3369,7 +3372,7 @@ async function requestPaidSpace() { |
| |
| // ---- the pay-first marker + claim --------------------------------------------- |
| |
| -// The localStorage marker for a checkout in flight: {session, name, at}. It |
| +// The localStorage marker for a checkout in flight: {session, at}. It |
| // exists so a tab closed on Stripe's page still finishes the create on the |
| // next visit here β the claim is idempotent, so over-firing is safe. |
| const PENDING_CREATE_KEY = "lchat-pending-create"; |
| @@ -3589,13 +3592,7 @@ function ensureFader() { |
| // showing (#1374). Everything else stays a plain inline error. |
| const code = (err && err.code) || ""; |
| if ((code === "quota_exceeded" || code === "free_pool_full") && billingTiers.length) { |
| - fader?.stop(); |
| - $("create").classList.add("hidden"); |
| - planPaidRequired = true; |
| - planTier = billingTiers[0].key; |
| - renderPlanTiers(); |
| - $("create-plan").classList.remove("hidden"); |
| - planStatus((err && err.message) || "", "error"); |
| + openPlanBounce((err && err.message) || ""); |
| return; |
| } |
| $("create-back").classList.remove("hidden"); |
| @@ -3605,8 +3602,9 @@ function ensureFader() { |
| return fader; |
| } |
| |
| -// The plan chosen on step 1 β consumed by the fader's onEleven to pick the |
| -// free create or the Stripe handoff. tier "" = free. |
| +// The plan behind the slider β consumed by the fader's onEleven to pick the |
| +// free create or the Stripe handoff. tier "" = free, which is every create |
| +// that didn't bounce off a paid gate. |
| let planChoice = null; |
| let planTier = "free"; |
| // planPaidRequired: the create bounced off a gate paying genuinely fixes |
| @@ -3616,23 +3614,22 @@ let planTier = "free"; |
| // space for?" is Eleven's question inside the space (#1374). |
| let planPaidRequired = false; |
| |
| -// openCreate opens step 1: name the space. The slider (step 2) stays the |
| -// actual commit. |
| +// openCreate opens the create: the slider, and nothing before it. Nothing is |
| +// asked on the way in β not a name (Eleven asks inside the space), not a plan |
| +// (every space is born free, #1374) β so the fastest path from "I want a |
| +// space" to having one is a single gesture. The plan screen exists only for |
| +// the bounce (openPlanBounce). |
| function openCreate() { |
| clearUndo(); |
| planChoice = null; |
| planTier = "free"; |
| planPaidRequired = false; |
| - $("plan-name").value = ""; |
| - $("plan-continue").disabled = true; |
| planStatus(""); |
| - // The ledger read still warms the price book β the paid-required error |
| - // path needs it in hand (renderPlanTiers fills in when it answers). |
| + // The ledger read warms the price book β the paid-required error path needs |
| + // it in hand (renderPlanTiers fills in when it answers). |
| if (spacesEnabled && provisioned === null && state.token) refreshProvisioned(); |
| - renderPlanTiers(); |
| hideVault(); |
| - $("create-plan").classList.remove("hidden"); |
| - $("plan-name").focus(); |
| + openCreateFader(); |
| } |
| |
| function closePlan() { |
| @@ -3640,6 +3637,20 @@ function closePlan() { |
| showVault(); |
| } |
| |
| +// openPlanBounce shows the plan screen β the ONLY thing that does. The create |
| +// hit a gate paying genuinely fixes (the free quota, the pool under budget |
| +// pressure), so the paid tiers are the way through, with the gate's own |
| +// message above them. |
| +function openPlanBounce(message) { |
| + fader?.stop(); |
| + $("create").classList.add("hidden"); |
| + planPaidRequired = true; |
| + planTier = billingTiers[0].key; |
| + renderPlanTiers(); |
| + $("create-plan").classList.remove("hidden"); |
| + planStatus(message, "error"); |
| +} |
| + |
| function planStatus(msg, kind) { |
| const el = $("plan-status"); |
| el.textContent = msg || ""; |
| @@ -3647,8 +3658,8 @@ function planStatus(msg, kind) { |
| } |
| |
| // renderPlanTiers paints the who-pays picker from the provisioner's price |
| -// book β but ONLY in paid-required mode (#1374): the happy path is just the |
| -// name, and the plan question lives in Eleven's setup conversation. Here the |
| +// book β but ONLY in paid-required mode (#1374): the happy path asks nothing |
| +// at all, and the plan question lives in Eleven's setup conversation. Here the |
| // create bounced off the quota or the pool gate, so the paid tiers (no Free |
| // card β free is exactly what bounced) are the way through, at base price |
| // with the β local figure the upgrade picker also shows. |
| @@ -3713,16 +3724,10 @@ function renderPlanTiers() { |
| fxNote.textContent = rates && fxSnapshot.date ? "Billed in euros β β at the ECB rate of " + fxSnapshot.date + "." : ""; |
| } |
| |
| -// planContinue commits step 1 and opens the slider. The name is required β |
| -// asking and then accepting silence would make the field noise. |
| +// planContinue commits the bounce's chosen plan and returns to the slider β |
| +// the same commit as the free path, with a Stripe handoff behind it. |
| function planContinue() { |
| - const name = $("plan-name").value.trim(); |
| - if (!name) { |
| - planStatus("Give your space a name first.", "error"); |
| - $("plan-name").focus(); |
| - return; |
| - } |
| - planChoice = { name, tier: planTier === "free" ? "" : planTier }; |
| + planChoice = { tier: planTier === "free" ? "" : planTier }; |
| $("create-plan").classList.add("hidden"); |
| openCreateFader(); |
| } |
| @@ -4326,18 +4331,16 @@ $("combine-skip-no").addEventListener("click", combineVaultStage); |
| $("open-create").addEventListener("click", openCreate); |
| $("plan-back").addEventListener("click", closePlan); |
| $("plan-continue").addEventListener("click", planContinue); |
| -$("plan-name").addEventListener("input", () => { |
| - $("plan-continue").disabled = !$("plan-name").value.trim(); |
| - planStatus(""); |
| -}); |
| -$("plan-name").addEventListener("keydown", (e) => { |
| - if (e.key === "Enter" && !$("plan-continue").disabled) planContinue(); |
| -}); |
| -// Step 2's back returns to step 1 (the choices survive), not the list. |
| +// Back from the slider: the list normally (it's the only step there was), or |
| +// the plan screen when a bounce put one behind us β the chosen tier survives. |
| $("create-back").addEventListener("click", () => { |
| - fader?.stop(); |
| - $("create").classList.add("hidden"); |
| - $("create-plan").classList.remove("hidden"); |
| + if (planPaidRequired) { |
| + fader?.stop(); |
| + $("create").classList.add("hidden"); |
| + $("create-plan").classList.remove("hidden"); |
| + return; |
| + } |
| + closeCreate(); |
| }); |
| $("create-done").addEventListener("click", closeCreate); |
| // (Creating a space is the fader now β mounted in openCreate; see fader.js.) |