Fix sidebar showing all modules: hide Manage panel when none disabled

- App switcher: only show "Manage rApps" when there are actually
  disabled modules or active restrictions. Move "Available to Add"
  above "Remove" to prioritize adding. Eliminates duplicate module
  listing when all modules are enabled.
- Shell: update app switcher on modules-changed event (was only
  updating tab bar and folk-rapp, not the sidebar itself).
- SMTP: use space-agent@rspace.online as From for invite/approval
  emails with proper envelope sender for DKIM alignment.
- Shell CSS: fix banner z-index, smooth header transition on banner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-01 12:32:53 -07:00
parent b54ad4b36c
commit 45372c6681
6 changed files with 47 additions and 24 deletions

View File

@ -458,6 +458,8 @@ async function executeApproval(docId: string, approvalId: string) {
replyTo: agentReplyTo,
subject: approval.subject,
text: approval.bodyText,
// Envelope override: authenticate as SMTP_USER but show mailboxEmail in From header
envelope: { from: SMTP_USER || 'noreply@rmail.online', to: approval.toAddresses },
};
if (approval.ccAddresses.length > 0) {

View File

@ -607,10 +607,19 @@ export function renderShell(opts: ShellOptions): string {
var enabledModules = detail.enabledModules;
window.__rspaceEnabledModules = enabledModules;
// Update tab bar's module list
// Recompute visible + allModules with updated enabled flags
var allMods = window.__rspaceAllModules || [];
var enabledSet = new Set(enabledModules);
var visible = allMods.filter(function(m) { return m.id === 'rspace' || enabledSet.has(m.id); });
var updatedAll = allMods.map(function(m) { return Object.assign({}, m, { enabled: m.id === 'rspace' || enabledSet.has(m.id) }); });
window.__rspaceModuleList = visible;
window.__rspaceAllModules = updatedAll;
// Update app switcher's main navigation + catalog
var switcher = document.querySelector('rstack-app-switcher');
if (switcher) { switcher.setModules(visible); switcher.setAllModules(updatedAll); }
// Update tab bar's module list
var tb = document.querySelector('rstack-tab-bar');
if (tb) tb.setModules(visible);

View File

@ -2199,11 +2199,14 @@ spaces.post("/:slug/invite", async (c) => {
metadata: { inviteToken: invite.token, role },
}).catch(() => {});
// Send invite email
// Send invite email from {slug}-agent@rspace.online
if (inviteTransport) {
try {
const agentAddr = `${slug}-agent@rspace.online`;
await inviteTransport.sendMail({
from: process.env.SMTP_FROM || "rSpace <noreply@rmail.online>",
from: `${slug} <${agentAddr}>`,
replyTo: agentAddr,
envelope: { from: process.env.SMTP_USER || "noreply@rmail.online", to: body.email },
to: body.email,
subject: `${inviterName} invited you to "${slug}" on rSpace`,
html: `
@ -2326,11 +2329,14 @@ spaces.post("/:slug/members/add", async (c) => {
metadata: { inviteToken: invite.token, role },
}).catch(() => {});
// Send invite email (non-fatal)
// Send invite email from {slug}-agent@rspace.online (non-fatal)
if (inviteTransport && targetEmail) {
try {
const agentAddr = `${slug}-agent@rspace.online`;
await inviteTransport.sendMail({
from: process.env.SMTP_FROM || "rSpace <noreply@rmail.online>",
from: `${slug} <${agentAddr}>`,
replyTo: agentAddr,
envelope: { from: process.env.SMTP_USER || "noreply@rmail.online", to: targetEmail },
to: targetEmail,
subject: `${inviterName} invited you to "${slug}" on rSpace`,
html: `

View File

@ -243,34 +243,37 @@ export class RStackAppSwitcher extends HTMLElement {
const disabledModules = this.#allModules.filter(
m => m.enabled === false && m.id !== 'rspace'
);
if (disabledModules.length > 0 || this.#allModules.length > 0) {
// Only show the Manage section when there are disabled modules to add,
// or when enabledModules is actively configured (not null/all-enabled)
const hasRestrictions = disabledModules.length > 0;
if (hasRestrictions || this.#allModules.length > this.#modules.length) {
html += `
<div class="catalog-divider">
<button class="catalog-toggle" id="catalog-toggle">
${this.#catalogOpen ? '▾' : '▸'} Manage rApps
${disabledModules.length > 0 ? `<span class="catalog-count">${disabledModules.length} available</span>` : ''}
${disabledModules.length > 0 ? `<span class="catalog-count">${disabledModules.length} more</span>` : ''}
</button>
</div>
`;
if (this.#catalogOpen) {
html += `<div class="catalog-panel" id="catalog-panel">`;
// Show enabled modules with toggle-off option
const enabledNonCore = this.#allModules.filter(
m => m.enabled !== false && m.id !== 'rspace'
);
if (enabledNonCore.length > 0) {
html += `<div class="catalog-section-label">Enabled</div>`;
for (const m of enabledNonCore) {
html += this.#renderCatalogItem(m, true);
}
}
// Show disabled modules with toggle-on option
// Show disabled modules with add option
if (disabledModules.length > 0) {
html += `<div class="catalog-section-label">Available to Add</div>`;
for (const m of disabledModules) {
html += this.#renderCatalogItem(m, false);
}
}
// Show enabled modules with remove option (compact section below)
const enabledNonCore = this.#allModules.filter(
m => m.enabled !== false && m.id !== 'rspace'
);
if (enabledNonCore.length > 0) {
html += `<div class="catalog-section-label">Remove from Space</div>`;
for (const m of enabledNonCore) {
html += this.#renderCatalogItem(m, true);
}
}
html += `</div>`;
}
}

View File

@ -5462,8 +5462,11 @@ app.post('/api/invites/identity', async (c) => {
const subjectLine = spaceSlug
? `${payload.username} invited you to join "${spaceSlug}" on rSpace`
: `${payload.username} invited you to join rSpace`;
const agentAddr = spaceSlug ? `${spaceSlug}-agent@rspace.online` : null;
await smtpTransport.sendMail({
from: CONFIG.smtp.from,
from: agentAddr ? `${spaceSlug} <${agentAddr}>` : CONFIG.smtp.from,
...(agentAddr ? { replyTo: agentAddr } : {}),
...(agentAddr ? { envelope: { from: CONFIG.smtp.user, to: email } } : {}),
to: email,
subject: subjectLine,
html: `

View File

@ -15,7 +15,7 @@ body {
.rspace-banner {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 10002;
z-index: 10000; /* above header (9999) */
display: flex;
align-items: center;
justify-content: center;
@ -68,7 +68,7 @@ body {
.rspace-banner__close:hover {
color: #fff;
}
/* Push header down when a banner is visible */
/* Push header + content down when a banner is visible */
body.rspace-banner-visible .rstack-header {
top: 36px;
}
@ -628,13 +628,13 @@ body.rspace-headers-minimized .rapp-subnav {
/* Smooth transitions for header minimize/restore */
.rstack-header {
transition: transform 0.25s ease;
transition: transform 0.25s ease, top 0.3s ease-out;
}
.rstack-tab-row {
transition: margin-left 0.25s ease, left 0.25s ease, transform 0.25s ease;
transition: margin-left 0.25s ease, left 0.25s ease, transform 0.25s ease, top 0.3s ease-out;
}
#app {
transition: margin-left 0.25s ease, left 0.25s ease, padding-top 0.25s ease;
transition: margin-left 0.25s ease, left 0.25s ease, padding-top 0.3s ease-out;
}
/* Mobile: minimized state adjustments (sticky, not fixed) */