/** * Template seeder for new rSpace communities. * * Every non-demo space gets generic "Getting Started" content covering * all 25+ modules so users immediately see what each rApp can do. * * Shape IDs use a `tmpl-` prefix so they never collide with `demo-` or * user-created shapes. Seeding is idempotent — only spaces with 0 shapes * are touched, and the demo space is always skipped. */ import { addShapes, getDocumentData, listCommunities, loadCommunity, } from "./community-store"; // ── Template Shapes ───────────────────────────────────────────────── const TEMPLATE_SHAPES: Record[] = [ // ─── rTrips: Itinerary ────────────────────────────────────── { id: "tmpl-itinerary", type: "folk-itinerary", x: 50, y: 50, width: 400, height: 300, rotation: 0, tripTitle: "My Trip", startDate: "", endDate: "", travelers: [], items: [ { date: "Day 1", activity: "Arrive & settle in", category: "travel" }, { date: "Day 2", activity: "Explore the area", category: "adventure" }, { date: "Day 3", activity: "Head home", category: "travel" }, ], }, // ─── rTrips: Destination ──────────────────────────────────── { id: "tmpl-destination", type: "folk-destination", x: 500, y: 50, width: 300, height: 200, rotation: 0, destName: "My Destination", country: "", lat: 0, lng: 0, arrivalDate: "", departureDate: "", notes: "Add your destination details here.", }, // ─── rNotes: Notebook ─────────────────────────────────────── { id: "tmpl-notebook", type: "folk-notebook", x: 850, y: 50, width: 300, height: 180, rotation: 0, notebookTitle: "My Notebook", description: "A shared notebook for your space. Add notes, ideas, and reference material.", noteCount: 0, collaborators: [], }, // ─── rNotes: Welcome Note ─────────────────────────────────── { id: "tmpl-note-welcome", type: "folk-note", x: 850, y: 260, width: 300, height: 250, rotation: 0, noteTitle: "Welcome to rSpace", content: "## Getting Started\n\nThis is your space! Here's what you can do:\n\n- **Explore** the canvas — drag, zoom, and click shapes\n- **Create** new items using the module sidebar\n- **Collaborate** in real-time with your team\n- **Customize** by rearranging or removing these starter shapes\n\nEach shape represents an rApp module. Click one to dive in!", tags: ["getting-started"], editor: "", editedAt: "", }, // ─── rNotes: Ideas Note ───────────────────────────────────── { id: "tmpl-note-ideas", type: "folk-note", x: 1200, y: 50, width: 300, height: 200, rotation: 0, noteTitle: "Ideas & Brainstorms", content: "## Ideas\n\nCapture your ideas here.\n\n- Idea 1\n- Idea 2\n- Idea 3\n\nUse tags to organize your notes across the space.", tags: ["ideas"], editor: "", editedAt: "", }, // ─── rVote: Poll ──────────────────────────────────────────── { id: "tmpl-poll", type: "demo-poll", x: 50, y: 400, width: 350, height: 200, rotation: 0, question: "What should we work on first?", options: [ { label: "Set up the space", votes: 0 }, { label: "Invite team members", votes: 0 }, { label: "Plan our first project", votes: 0 }, ], totalVoters: 0, status: "active", endsAt: "", }, // ─── rCart: Item ──────────────────────────────────────────── { id: "tmpl-cart-item", type: "demo-cart-item", x: 50, y: 630, width: 320, height: 60, rotation: 0, name: "Example Item", price: 0, funded: 0, status: "In Cart", requestedBy: "", store: "", }, // ─── rFlows: Budget ───────────────────────────────────────── { id: "tmpl-budget", type: "folk-budget", x: 450, y: 400, width: 350, height: 250, rotation: 0, budgetTitle: "Space Budget", currency: "USD", budgetTotal: 0, spent: 0, categories: [ { name: "Operations", budget: 0, spent: 0 }, { name: "Marketing", budget: 0, spent: 0 }, { name: "Development", budget: 0, spent: 0 }, ], }, // ─── rFlows: Expense ──────────────────────────────────────── { id: "tmpl-expense", type: "demo-expense", x: 450, y: 680, width: 320, height: 60, rotation: 0, description: "Example expense", amount: 0, currency: "USD", paidBy: "", split: "equal", category: "operations", date: "", }, // ─── rMaps: Location Marker ───────────────────────────────── { id: "tmpl-map-marker", type: "demo-map-marker", x: 500, y: 280, width: 40, height: 40, rotation: 0, name: "Home Base", lat: 0, lng: 0, emoji: "📍", category: "home", status: "", }, // ─── rTokens: Mint ────────────────────────────────────────── { id: "tmpl-mint", type: "folk-token-mint", x: 1550, y: 50, width: 320, height: 280, rotation: 0, tokenName: "Contribution Token", tokenSymbol: "CONTRIB", description: "Track and reward contributions to your space. Customize the name, symbol, and supply.", totalSupply: 1000, issuedSupply: 0, tokenColor: "#6d28d9", tokenIcon: "⭐", createdBy: "", createdAt: "", }, // ─── rTokens: Ledger ──────────────────────────────────────── { id: "tmpl-ledger", type: "folk-token-ledger", x: 1940, y: 50, width: 380, height: 400, rotation: 0, mintId: "tmpl-mint", entries: [], }, // ─── rTokens: Arrow ───────────────────────────────────────── { id: "tmpl-arrow-tokens", type: "folk-arrow", x: 0, y: 0, width: 0, height: 0, rotation: 0, sourceId: "tmpl-mint", targetId: "tmpl-ledger", color: "#6d28d9", }, // ─── rFiles: File ─────────────────────────────────────────── { id: "tmpl-file-readme", type: "folk-file", x: 1550, y: 500, width: 280, height: 80, rotation: 0, fileName: "README.md", fileSize: "0 KB", mimeType: "text/markdown", uploadedBy: "", uploadedAt: "", tags: ["getting-started"], }, // ─── rForum: Thread ───────────────────────────────────────── { id: "tmpl-forum-intro", type: "folk-forum-thread", x: 1550, y: 620, width: 320, height: 160, rotation: 0, threadTitle: "Introductions", author: "", createdAt: "", replyCount: 0, lastReply: "", preview: "Introduce yourself to the space! Share who you are and what you're excited to work on.", tags: ["introductions"], }, // ─── rBooks: Book ─────────────────────────────────────────── { id: "tmpl-book", type: "folk-book", x: 1940, y: 500, width: 280, height: 200, rotation: 0, bookTitle: "Getting Started Guide", author: "rSpace", coverColor: "#3b82f6", pageCount: 0, currentPage: 0, readers: [], status: "want-to-read", }, // ─── rPubs: Publication ───────────────────────────────────── { id: "tmpl-pub", type: "folk-pub", x: 50, y: 780, width: 300, height: 180, rotation: 0, pubTitle: "My First Publication", pubType: "zine", creator: "", format: "", status: "draft", copies: 0, price: 0, currency: "USD", description: "A placeholder publication. Edit this to create your first zine, booklet, or print artifact.", }, // ─── rSwag: Item ──────────────────────────────────────────── { id: "tmpl-swag", type: "folk-swag", x: 400, y: 780, width: 280, height: 120, rotation: 0, swagTitle: "Space Sticker", swagType: "sticker", designer: "", sizes: [], price: 0, currency: "USD", status: "draft", orderCount: 0, }, // ─── rProviders: Provider ─────────────────────────────────── { id: "tmpl-provider", type: "folk-provider", x: 400, y: 930, width: 300, height: 160, rotation: 0, providerName: "Local Print Shop", location: "", capabilities: ["print", "stickers"], substrates: ["paper", "vinyl"], turnaround: "", rating: 0, ordersFulfilled: 0, }, // ─── rWork: Task Board ────────────────────────────────────── { id: "tmpl-work-board", type: "folk-work-board", x: 750, y: 780, width: 500, height: 280, rotation: 0, boardTitle: "Getting Started Tasks", columns: [ { name: "To Do", tasks: [ { title: "Customize your space", assignee: "", priority: "high" }, { title: "Invite collaborators", assignee: "", priority: "medium" }, ], }, { name: "In Progress", tasks: [ { title: "Explore the modules", assignee: "", priority: "medium" }, ], }, { name: "Done", tasks: [], }, ], }, // ─── rCal: Calendar ───────────────────────────────────────── { id: "tmpl-calendar", type: "folk-calendar", x: 50, y: 1000, width: 350, height: 250, rotation: 0, calTitle: "My Calendar", month: "", events: [ { date: "Today", title: "Space Created", color: "#22c55e" }, ], }, // ─── rNetwork: Graph ──────────────────────────────────────── { id: "tmpl-network", type: "folk-network", x: 1300, y: 780, width: 400, height: 280, rotation: 0, networkTitle: "My Network", nodes: [ { id: "me", label: "Me", role: "organizer" }, ], edges: [], }, // ─── rTube: Video ─────────────────────────────────────────── { id: "tmpl-video", type: "folk-video", x: 1750, y: 780, width: 300, height: 180, rotation: 0, videoTitle: "Welcome Video", duration: "", creator: "", uploadedAt: "", views: 0, thumbnail: "", }, // ─── rInbox: Inbox ────────────────────────────────────────── { id: "tmpl-inbox", type: "folk-inbox", x: 1750, y: 990, width: 300, height: 160, rotation: 0, inboxTitle: "Inbox", messages: [ { from: "rSpace", text: "Welcome to your new space! Start by exploring the canvas.", time: "" }, ], }, // ─── rData: Dashboard ─────────────────────────────────────── { id: "tmpl-dashboard", type: "folk-dashboard", x: 50, y: 1280, width: 320, height: 220, rotation: 0, dashTitle: "Space Dashboard", metrics: [ { label: "Members", value: "1", trend: "neutral" }, { label: "Shapes", value: "0", trend: "neutral" }, { label: "Tasks", value: "0/3", trend: "neutral" }, ], }, // ─── rChoices: Decision Matrix ────────────────────────────── { id: "tmpl-choices", type: "folk-choice-matrix", x: 420, y: 1280, width: 320, height: 200, rotation: 0, choiceTitle: "Decision Matrix", options: [ { name: "Option A", score: 0, criteria: {} }, { name: "Option B", score: 0, criteria: {} }, ], decidedBy: "", status: "open", winner: "", }, // ─── rSplat: 3D Capture ───────────────────────────────────── { id: "tmpl-splat", type: "folk-splat", x: 790, y: 1280, width: 300, height: 160, rotation: 0, splatTitle: "My 3D Capture", pointCount: "0", capturedBy: "", capturedAt: "", fileSize: "0 KB", status: "pending", }, // ─── rNotes: Packing List ─────────────────────────────────── { id: "tmpl-packing", type: "folk-packing-list", x: 1140, y: 1280, width: 300, height: 300, rotation: 0, listTitle: "My Packing List", items: [ { name: "Item 1", packed: false, category: "general" }, { name: "Item 2", packed: false, category: "general" }, { name: "Item 3", packed: false, category: "general" }, ], }, ]; // ── Public API ────────────────────────────────────────────────────── /** * Seed template shapes into a space if it has 0 shapes. * Skips the demo space. Returns true if shapes were added. */ export function seedTemplateShapes(slug: string): boolean { if (slug === "demo") return false; const data = getDocumentData(slug); const shapeCount = data ? Object.keys(data.shapes || {}).length : 0; if (shapeCount > 0) { return false; } addShapes(slug, TEMPLATE_SHAPES); console.log(`[Template] Seeded ${TEMPLATE_SHAPES.length} template shapes into "${slug}"`); return true; } /** * Iterate all existing communities on startup and seed any * empty (0-shape) spaces with template content. */ export async function ensureTemplateSeeding(): Promise { const slugs = await listCommunities(); let seededCount = 0; for (const slug of slugs) { if (slug === "demo") continue; try { await loadCommunity(slug); if (seedTemplateShapes(slug)) { seededCount++; } } catch (e) { console.error(`[Template] Failed to seed "${slug}":`, e); } } if (seededCount > 0) { console.log(`[Template] Retroactively seeded ${seededCount} space(s)`); } }