From d458a005506497687078cbf09d3c6c55b91adf12 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Mar 2026 13:21:03 -0700 Subject: [PATCH] fix(ux): instant bulk-delete dialog + post-login space routing Bulk delete dialog: switch from click to pointerdown for instant touch response, raise z-index above all overlays, add hover/active feedback and keyboard support. Post-login routing: reload page when logging in on a non-demo space so access gates clear and CRDT sync reconnects with auth. Silently provisions personal space in background. Co-Authored-By: Claude Opus 4.6 --- shared/components/rstack-identity.ts | 12 ++++++++-- website/canvas.html | 36 ++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/shared/components/rstack-identity.ts b/shared/components/rstack-identity.ts index 73b2616..3ee548d 100644 --- a/shared/components/rstack-identity.ts +++ b/shared/components/rstack-identity.ts @@ -264,9 +264,17 @@ function autoResolveSpace(token: string, username: string): void { // Detect current space const currentSpace = _getCurrentSpace(); - if (currentSpace !== "demo") return; // Already on a non-demo space - // Provision personal space and redirect + if (currentSpace !== "demo") { + // User followed a link to a specific space (e.g., orgname.rspace.online). + // Provision their personal space silently, then reload to apply the + // authenticated session (clears access gates, reconnects CRDT sync, etc.). + autoProvisionSpace(token); + window.location.reload(); + return; + } + + // On demo/landing — provision personal space and redirect there fetch("/api/spaces/auto-provision", { method: "POST", headers: { diff --git a/website/canvas.html b/website/canvas.html index 5a02a88..97cb3ce 100644 --- a/website/canvas.html +++ b/website/canvas.html @@ -6685,31 +6685,53 @@ function showBulkDeleteConfirm(count) { if (bulkDeleteOverlay) return; // prevent stacking from key repeat const overlay = document.createElement("div"); - overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:10000;display:flex;align-items:center;justify-content:center"; + overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:100002;display:flex;align-items:center;justify-content:center;touch-action:manipulation"; const dialog = document.createElement("div"); - dialog.style.cssText = "background:var(--rs-bg-surface,#1e1b4b);border:1px solid var(--rs-border,#334155);border-radius:12px;padding:1.5rem 2rem;max-width:380px;text-align:center;color:var(--rs-text-primary,#e2e8f0);font-family:system-ui,sans-serif"; + dialog.style.cssText = "background:var(--rs-bg-surface,#1e1b4b);border:1px solid var(--rs-border,#334155);border-radius:12px;padding:1.5rem 2rem;max-width:380px;text-align:center;color:var(--rs-text-primary,#e2e8f0);font-family:system-ui,sans-serif;user-select:none"; + const btnBase = "padding:0.5rem 1.25rem;border-radius:8px;cursor:pointer;font-size:0.875rem;touch-action:manipulation;user-select:none;-webkit-tap-highlight-color:transparent;transition:filter 0.1s"; dialog.innerHTML = `

Delete ${count} elements?

You are about to delete a large number of elements. This action cannot be easily undone.

- - + +
`; overlay.appendChild(dialog); document.body.appendChild(overlay); bulkDeleteOverlay = overlay; - overlay.addEventListener("click", (e) => { if (e.target === overlay) dismissBulkDelete(); }); - dialog.querySelector("#bulk-delete-cancel").addEventListener("click", dismissBulkDelete); - dialog.querySelector("#bulk-delete-confirm").addEventListener("click", () => { + const cancelBtn = dialog.querySelector("#bulk-delete-cancel"); + const confirmBtn = dialog.querySelector("#bulk-delete-confirm"); + + // Use pointerdown for instant response (no 300ms click delay on touch) + overlay.addEventListener("pointerdown", (e) => { + if (e.target === overlay) { e.stopPropagation(); dismissBulkDelete(); } + }); + cancelBtn.addEventListener("pointerdown", (e) => { + e.stopPropagation(); + dismissBulkDelete(); + }); + confirmBtn.addEventListener("pointerdown", (e) => { + e.stopPropagation(); dismissBulkDelete(); doDeleteSelected(); }); + + // Visual feedback on hover/active + [cancelBtn, confirmBtn].forEach(btn => { + btn.addEventListener("pointerenter", () => { btn.style.filter = "brightness(1.2)"; }); + btn.addEventListener("pointerleave", () => { btn.style.filter = ""; }); + btn.addEventListener("pointerdown", () => { btn.style.filter = "brightness(0.85)"; }); + }); + // Escape key closes dialog const escHandler = (e) => { if (e.key === "Escape") { dismissBulkDelete(); document.removeEventListener("keydown", escHandler); } }; document.addEventListener("keydown", escHandler); + + // Focus the confirm button so Enter also works + confirmBtn.focus(); } // ── Canvas pointer interaction: pan-first + hold-to-select ──