feat(spaces): blank canvas init + team inbox provisioning
New spaces start with an empty canvas instead of 25+ template shapes. Each space gets a {slug}@rspace.online team inbox (multi-sig ready) via the rinbox onSpaceCreate hook. Fix EncryptID auto-provision passing raw string instead of SpaceLifecycleContext to module hooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
97bf2d7987
commit
d31e8fdca4
|
|
@ -1630,6 +1630,44 @@ routes.get("/", (c) => {
|
|||
|
||||
// ── Seed template data ──
|
||||
|
||||
/**
|
||||
* Initialize a clean team inbox for a space: {space}@rspace.online
|
||||
* No demo threads — just an empty mailbox ready for multi-sig approval.
|
||||
*/
|
||||
function initSpaceInbox(space: string, ownerDid: string) {
|
||||
if (!_syncServer) return;
|
||||
// Skip if space already has mailboxes
|
||||
const prefix = `${space}:inbox:mailboxes:`;
|
||||
const existing = _syncServer.listDocs().filter((id) => id.startsWith(prefix));
|
||||
if (existing.length > 0) return;
|
||||
|
||||
const mbId = crypto.randomUUID();
|
||||
const docId = mailboxDocId(space, mbId);
|
||||
const now = Date.now();
|
||||
|
||||
const doc = Automerge.change(Automerge.init<MailboxDoc>(), 'init space inbox', (d) => {
|
||||
d.meta = { module: 'inbox', collection: 'mailboxes', version: 2, spaceSlug: space, createdAt: now };
|
||||
d.mailbox = {
|
||||
id: mbId, workspaceId: null, slug: space, name: `${space.charAt(0).toUpperCase() + space.slice(1)} Inbox`,
|
||||
email: `${space}@rspace.online`,
|
||||
description: `Team inbox for the ${space} space. Multi-sig approval can be configured via Gnosis Safe.`,
|
||||
visibility: 'members', ownerDid,
|
||||
safeAddress: null, safeChainId: null, approvalThreshold: 1, createdAt: now,
|
||||
};
|
||||
d.members = [];
|
||||
d.threads = {};
|
||||
d.approvals = {};
|
||||
d.personalInboxes = {};
|
||||
d.agentInboxes = {};
|
||||
});
|
||||
|
||||
_syncServer.setDoc(docId, doc);
|
||||
console.log(`[Inbox] Team inbox provisioned for "${space}": ${space}@rspace.online`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed demo mailbox with sample threads (for demo/template spaces only).
|
||||
*/
|
||||
function seedTemplateInbox(space: string) {
|
||||
if (!_syncServer) return;
|
||||
// Skip if space already has mailboxes
|
||||
|
|
@ -1714,7 +1752,7 @@ export const inboxModule: RSpaceModule = {
|
|||
if (SMTP_USER) getSmtpTransport().catch(() => {});
|
||||
},
|
||||
async onSpaceCreate(ctx) {
|
||||
seedTemplateInbox(ctx.spaceSlug);
|
||||
initSpaceInbox(ctx.spaceSlug, ctx.ownerDID || 'did:unknown');
|
||||
},
|
||||
standaloneDomain: "rinbox.online",
|
||||
feeds: [
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import {
|
|||
} from "./community-store";
|
||||
import type { NestPermissions, SpaceRefFilter } from "./community-store";
|
||||
import { ensureDemoCommunity } from "./seed-demo";
|
||||
import { seedTemplateShapes, ensureTemplateSeeding } from "./seed-template";
|
||||
import { seedTemplateShapes } from "./seed-template";
|
||||
// Campaign demo moved to rsocials module — see modules/rsocials/campaign-data.ts
|
||||
import type { SpaceVisibility } from "./community-store";
|
||||
import {
|
||||
|
|
@ -3076,7 +3076,6 @@ ensureDemoCommunity().then(() => console.log("[Demo] Demo community ready")).cat
|
|||
import { initTokenService, seedCUSDC } from "./token-service";
|
||||
loadAllDocs(syncServer)
|
||||
.then(async () => {
|
||||
ensureTemplateSeeding();
|
||||
// Seed all modules' demo data so /demo routes always have content
|
||||
for (const mod of getAllModules()) {
|
||||
if (mod.seedTemplate) {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ import type { EncryptIDClaims } from "@encryptid/sdk/server";
|
|||
import { getAllModules, getModule } from "../shared/module";
|
||||
import type { SpaceLifecycleContext } from "../shared/module";
|
||||
import { syncServer } from "./sync-instance";
|
||||
import { seedTemplateShapes } from "./seed-template";
|
||||
|
||||
import { notify, notifySpaceAdmins } from "./notification-service";
|
||||
|
||||
const ENCRYPTID_URL = process.env.ENCRYPTID_INTERNAL_URL || "http://encryptid:3000";
|
||||
|
|
@ -169,23 +169,9 @@ export async function createSpace(opts: CreateSpaceOpts): Promise<CreateSpaceRes
|
|||
}
|
||||
}
|
||||
|
||||
// Seed generic template content (non-fatal)
|
||||
try {
|
||||
seedTemplateShapes(slug);
|
||||
} catch (e) {
|
||||
console.error(`[createSpace:${source}] Template seeding failed for ${slug}:`, e);
|
||||
}
|
||||
|
||||
// Seed module-specific demo data (calendar events, sample tasks, etc.)
|
||||
for (const mod of getAllModules()) {
|
||||
if (mod.seedTemplate) {
|
||||
try {
|
||||
mod.seedTemplate(slug);
|
||||
} catch (e) {
|
||||
console.error(`[createSpace:${source}] Module ${mod.id} seedTemplate failed for ${slug}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE: New spaces start with a blank canvas.
|
||||
// Template shapes and module demo data are NOT seeded here.
|
||||
// The rinbox onSpaceCreate hook provisions a {slug}@rspace.online mailbox.
|
||||
|
||||
console.log(`[createSpace:${source}] Created space: ${slug}`);
|
||||
return { ok: true, slug, name, visibility, ownerDID };
|
||||
|
|
|
|||
|
|
@ -637,10 +637,17 @@ app.post('/api/register/complete', async (c) => {
|
|||
nestPolicy: DEFAULT_USER_NEST_POLICY,
|
||||
});
|
||||
|
||||
// Fire module hooks
|
||||
// Fire module hooks with proper lifecycle context
|
||||
const { syncServer } = await import('../../server/sync-instance');
|
||||
const lifecycleCtx = {
|
||||
spaceSlug,
|
||||
ownerDID: did,
|
||||
enabledModules: DEFAULT_USER_MODULES,
|
||||
syncServer,
|
||||
};
|
||||
for (const mod of getAllModules()) {
|
||||
if (mod.onSpaceCreate) {
|
||||
try { await mod.onSpaceCreate(spaceSlug); } catch (e) {
|
||||
try { await mod.onSpaceCreate(lifecycleCtx); } catch (e) {
|
||||
console.error(`[EncryptID] Module ${mod.id} onSpaceCreate for user space:`, e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue