fix: remove rvote proposals from space dashboard + fix protocol in redirects

1. Remove all rvote/proposals fetching from rstack-user-dashboard.
   rApp-specific data (proposals) should stay within the rVote module,
   not leak into the space-level dashboard.

2. Fix url.protocol in bare-domain redirects — TLS is terminated by
   Cloudflare/Traefik so url.protocol is always http: internally.
   Use https: for production domains.

3. Rewrite /{space}/api/... paths internally on bare domain instead
   of redirecting to subdomain (avoids CORS + mixed content issues).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-24 12:29:15 -07:00
parent 6e4d1436e0
commit 4536b5cab1
1 changed files with 1 additions and 146 deletions

View File

@ -2,7 +2,7 @@
* <rstack-user-dashboard> Space-centric dashboard shown when all tabs are closed. * <rstack-user-dashboard> Space-centric dashboard shown when all tabs are closed.
* *
* Sections: space header + stats, members, tools open, recent activity, * Sections: space header + stats, members, tools open, recent activity,
* active votes, and quick actions. * and quick actions.
* *
* Attributes: * Attributes:
* space current space slug * space current space slug
@ -26,15 +26,6 @@ interface NotificationItem {
read: boolean; read: boolean;
} }
interface ProposalInfo {
id: string;
title: string;
status: string;
score: number;
vote_count: string;
created_at: string;
}
interface TabLayer { interface TabLayer {
moduleId: string; moduleId: string;
label?: string; label?: string;
@ -47,11 +38,9 @@ export class RStackUserDashboard extends HTMLElement {
#shadow: ShadowRoot; #shadow: ShadowRoot;
#members: MemberInfo[] = []; #members: MemberInfo[] = [];
#notifications: NotificationItem[] = []; #notifications: NotificationItem[] = [];
#proposals: ProposalInfo[] = [];
#openTabs: TabLayer[] = []; #openTabs: TabLayer[] = [];
#membersLoading = true; #membersLoading = true;
#notifLoading = true; #notifLoading = true;
#proposalsLoading = true;
#lastFetch = 0; #lastFetch = 0;
constructor() { constructor() {
@ -91,7 +80,6 @@ export class RStackUserDashboard extends HTMLElement {
} }
this.#membersLoading = true; this.#membersLoading = true;
this.#notifLoading = true; this.#notifLoading = true;
this.#proposalsLoading = true;
this.#render(); this.#render();
this.#loadData(); this.#loadData();
} }
@ -101,7 +89,6 @@ export class RStackUserDashboard extends HTMLElement {
await Promise.all([ await Promise.all([
this.#fetchMembers(), this.#fetchMembers(),
this.#fetchNotifications(), this.#fetchNotifications(),
this.#fetchProposals(),
]); ]);
} }
@ -139,25 +126,6 @@ export class RStackUserDashboard extends HTMLElement {
this.#render(); this.#render();
} }
async #fetchProposals() {
this.#proposalsLoading = true;
try {
const slug = encodeURIComponent(this.space);
const res = await fetch(`/${slug}/rvote/api/proposals?space_slug=${slug}&limit=5`);
if (res.ok) {
const data = await res.json();
this.#proposals = (data.proposals || []).filter(
(p: ProposalInfo) => p.status !== "completed" && p.status !== "rejected",
);
}
} catch {
// rvote not installed or offline — hide section
this.#proposals = [];
}
this.#proposalsLoading = false;
this.#render();
}
#timeAgo(iso: string): string { #timeAgo(iso: string): string {
const diff = Date.now() - new Date(iso).getTime(); const diff = Date.now() - new Date(iso).getTime();
const mins = Math.floor(diff / 60_000); const mins = Math.floor(diff / 60_000);
@ -219,9 +187,6 @@ export class RStackUserDashboard extends HTMLElement {
if (!this.#membersLoading) { if (!this.#membersLoading) {
statPills.push(`<span class="stat-pill">${this.#members.length} member${this.#members.length !== 1 ? "s" : ""}</span>`); statPills.push(`<span class="stat-pill">${this.#members.length} member${this.#members.length !== 1 ? "s" : ""}</span>`);
} }
if (!this.#proposalsLoading && this.#proposals.length > 0) {
statPills.push(`<span class="stat-pill">${this.#proposals.length} active proposal${this.#proposals.length !== 1 ? "s" : ""}</span>`);
}
if (this.#openTabs.length > 0) { if (this.#openTabs.length > 0) {
statPills.push(`<span class="stat-pill">${this.#openTabs.length} tab${this.#openTabs.length !== 1 ? "s" : ""} open</span>`); statPills.push(`<span class="stat-pill">${this.#openTabs.length} tab${this.#openTabs.length !== 1 ? "s" : ""} open</span>`);
} }
@ -277,32 +242,7 @@ export class RStackUserDashboard extends HTMLElement {
`).join(""); `).join("");
} }
// ── Active Votes ──
let votesHTML = "";
if (!this.#proposalsLoading && this.#proposals.length > 0) {
votesHTML = `
<div class="section">
<div class="section-header"><h2>Active Votes</h2></div>
<div class="votes-list">
${this.#proposals.map(p => `
<button class="vote-item" data-navigate="rvote">
<div class="vote-info">
<div class="vote-title">${p.title}</div>
<div class="vote-meta">
<span class="vote-status">${p.status}</span>
<span class="vote-count">${p.vote_count} vote${p.vote_count !== "1" ? "s" : ""}</span>
</div>
</div>
<span class="vote-score">${p.score > 0 ? "+" : ""}${p.score}</span>
</button>
`).join("")}
</div>
</div>
`;
}
// ── Quick Actions ── // ── Quick Actions ──
const hasProposals = this.#proposals.length > 0;
this.#shadow.innerHTML = ` this.#shadow.innerHTML = `
<style>${STYLES}</style> <style>${STYLES}</style>
@ -332,8 +272,6 @@ export class RStackUserDashboard extends HTMLElement {
<div class="activity-list">${activityHTML}</div> <div class="activity-list">${activityHTML}</div>
</div> </div>
${votesHTML}
<div class="section"> <div class="section">
<div class="section-header"><h2>Quick Actions</h2></div> <div class="section-header"><h2>Quick Actions</h2></div>
<div class="actions-row"> <div class="actions-row">
@ -346,11 +284,6 @@ export class RStackUserDashboard extends HTMLElement {
<button class="action-btn" data-action-module="rspace"> <button class="action-btn" data-action-module="rspace">
<span class="action-icon">\u{1F30C}</span><span>Canvas</span> <span class="action-icon">\u{1F30C}</span><span>Canvas</span>
</button> </button>
${hasProposals ? `
<button class="action-btn" data-action-module="rvote">
<span class="action-icon">\u{1F5F3}</span><span>Vote</span>
</button>
` : ""}
</div> </div>
</div> </div>
</div> </div>
@ -388,13 +321,6 @@ export class RStackUserDashboard extends HTMLElement {
}); });
}); });
// Vote items
this.#shadow.querySelectorAll(".vote-item").forEach(el => {
el.addEventListener("click", () => {
this.#dispatch((el as HTMLElement).dataset.navigate || "rvote");
});
});
// Quick action buttons // Quick action buttons
this.#shadow.querySelectorAll(".action-btn").forEach(el => { this.#shadow.querySelectorAll(".action-btn").forEach(el => {
el.addEventListener("click", () => { el.addEventListener("click", () => {
@ -668,77 +594,6 @@ const STYLES = `
margin-top: 2px; margin-top: 2px;
} }
/* ── Active Votes ── */
.votes-list {
display: flex;
flex-direction: column;
border-radius: 10px;
background: var(--rs-bg-surface, #1e293b);
border: 1px solid var(--rs-border, rgba(255,255,255,0.08));
overflow: hidden;
}
.vote-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 16px;
cursor: pointer;
transition: background 0.15s;
border: none;
border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.06));
background: none;
text-align: left;
color: inherit;
font: inherit;
width: 100%;
}
.vote-item:last-child { border-bottom: none; }
.vote-item:hover {
background: var(--rs-bg-hover, rgba(255,255,255,0.05));
}
.vote-info { flex: 1; min-width: 0; }
.vote-title {
font-size: 0.8rem;
font-weight: 600;
color: var(--rs-text-primary, #e2e8f0);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.vote-meta {
display: flex;
gap: 8px;
margin-top: 3px;
}
.vote-status {
font-size: 0.65rem;
padding: 1px 6px;
border-radius: 4px;
background: rgba(251,191,36,0.15);
color: #fbbf24;
font-weight: 600;
text-transform: uppercase;
}
.vote-count {
font-size: 0.7rem;
color: var(--rs-text-muted, #94a3b8);
}
.vote-score {
font-size: 0.85rem;
font-weight: 700;
color: var(--rs-accent, #06b6d4);
flex-shrink: 0;
}
/* ── Quick Actions ── */ /* ── Quick Actions ── */
.actions-row { .actions-row {