Remove rnotes.online routing — re-deployed standalone with Memory Card spec
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4afce2dc37
commit
4cd985c29a
|
|
@ -83,10 +83,7 @@ services:
|
|||
- "traefik.http.routers.rspace-rvote.entrypoints=web"
|
||||
- "traefik.http.routers.rspace-rvote.priority=120"
|
||||
- "traefik.http.routers.rspace-rvote.service=rspace-online"
|
||||
- "traefik.http.routers.rspace-rnotes.rule=Host(`rnotes.online`)"
|
||||
- "traefik.http.routers.rspace-rnotes.entrypoints=web"
|
||||
- "traefik.http.routers.rspace-rnotes.priority=120"
|
||||
- "traefik.http.routers.rspace-rnotes.service=rspace-online"
|
||||
# rnotes.online — re-deployed standalone with Memory Card spec (Feb 22)
|
||||
- "traefik.http.routers.rspace-rwork.rule=Host(`rwork.online`)"
|
||||
- "traefik.http.routers.rspace-rwork.entrypoints=web"
|
||||
- "traefik.http.routers.rspace-rwork.priority=120"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,108 @@ async function initDB() {
|
|||
}
|
||||
}
|
||||
|
||||
initDB();
|
||||
async function seedDemoIfEmpty() {
|
||||
try {
|
||||
const count = await sql.unsafe("SELECT count(*)::int as cnt FROM rcal.events");
|
||||
if (parseInt(count[0].cnt) > 0) return;
|
||||
|
||||
// Create calendar sources
|
||||
const community = await sql.unsafe(
|
||||
`INSERT INTO rcal.calendar_sources (name, source_type, color, is_active, is_visible)
|
||||
VALUES ('Community Events', 'MANUAL', '#6366f1', true, true) RETURNING id`
|
||||
);
|
||||
const sprints = await sql.unsafe(
|
||||
`INSERT INTO rcal.calendar_sources (name, source_type, color, is_active, is_visible)
|
||||
VALUES ('Development Sprints', 'MANUAL', '#f59e0b', true, true) RETURNING id`
|
||||
);
|
||||
const communityId = community[0].id;
|
||||
const sprintsId = sprints[0].id;
|
||||
|
||||
// Create location hierarchy
|
||||
const world = await sql.unsafe(
|
||||
`INSERT INTO rcal.locations (name, granularity) VALUES ('Earth', 1) RETURNING id`
|
||||
);
|
||||
const europe = await sql.unsafe(
|
||||
`INSERT INTO rcal.locations (name, granularity, parent_id, lat, lng) VALUES ('Europe', 2, $1, 48.8566, 2.3522) RETURNING id`,
|
||||
[world[0].id]
|
||||
);
|
||||
const berlin = await sql.unsafe(
|
||||
`INSERT INTO rcal.locations (name, granularity, parent_id, lat, lng) VALUES ('Berlin', 4, $1, 52.52, 13.405) RETURNING id`,
|
||||
[europe[0].id]
|
||||
);
|
||||
|
||||
// Seed events — past, current week, and future
|
||||
const now = new Date();
|
||||
const events = [
|
||||
{
|
||||
title: "rSpace Launch Party",
|
||||
desc: "Celebrating the launch of the unified rSpace platform with all 22 modules live.",
|
||||
start: daysFromNow(-21, 18, 0), end: daysFromNow(-21, 22, 0),
|
||||
sourceId: communityId, locationName: "Radiant Hall, Pittsburgh",
|
||||
},
|
||||
{
|
||||
title: "Provider Onboarding Workshop",
|
||||
desc: "Hands-on session for print providers joining the cosmolocal network.",
|
||||
start: daysFromNow(-12, 14, 0), end: daysFromNow(-12, 17, 0),
|
||||
sourceId: communityId, virtual: true, virtualUrl: "https://meet.jit.si/rspace-providers", virtualPlatform: "Jitsi",
|
||||
},
|
||||
{
|
||||
title: "Weekly Community Standup",
|
||||
desc: "Open standup — share what you're working on, ask for help, coordinate.",
|
||||
start: daysFromNow(0, 16, 0), end: daysFromNow(0, 16, 45),
|
||||
sourceId: communityId, virtual: true, virtualUrl: "https://meet.jit.si/rspace-standup", virtualPlatform: "Jitsi",
|
||||
},
|
||||
{
|
||||
title: "Sprint: Module Seeding & Polish",
|
||||
desc: "Focus sprint on populating demo data and improving UX across all modules.",
|
||||
start: daysFromNow(0, 9, 0), end: daysFromNow(5, 18, 0),
|
||||
sourceId: sprintsId, allDay: true,
|
||||
},
|
||||
{
|
||||
title: "rFunds Budget Review",
|
||||
desc: "Quarterly review of treasury flows, enoughness thresholds, and overflow routing.",
|
||||
start: daysFromNow(6, 15, 0), end: daysFromNow(6, 17, 0),
|
||||
sourceId: communityId, virtual: true, virtualUrl: "https://meet.jit.si/rfunds-review", virtualPlatform: "Jitsi",
|
||||
},
|
||||
{
|
||||
title: "Cosmolocal Design Sprint",
|
||||
desc: "Two-day design sprint on the next generation of cosmolocal tooling.",
|
||||
start: daysFromNow(11, 9, 0), end: daysFromNow(12, 18, 0),
|
||||
sourceId: sprintsId, locationId: berlin[0].id, locationName: "Druckwerkstatt Berlin",
|
||||
},
|
||||
{
|
||||
title: "Q1 Retrospective",
|
||||
desc: "Looking back at what we built, what worked, and what to improve.",
|
||||
start: daysFromNow(21, 16, 0), end: daysFromNow(21, 18, 0),
|
||||
sourceId: communityId, virtual: true, virtualUrl: "https://meet.jit.si/rspace-retro", virtualPlatform: "Jitsi",
|
||||
},
|
||||
];
|
||||
|
||||
for (const e of events) {
|
||||
await sql.unsafe(
|
||||
`INSERT INTO rcal.events (title, description, start_time, end_time, all_day, source_id,
|
||||
location_id, location_name, is_virtual, virtual_url, virtual_platform)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
|
||||
[e.title, e.desc, e.start.toISOString(), e.end.toISOString(), e.allDay || false,
|
||||
e.sourceId, e.locationId || null, e.locationName || null,
|
||||
e.virtual || false, e.virtualUrl || null, e.virtualPlatform || null]
|
||||
);
|
||||
}
|
||||
|
||||
console.log("[Cal] Demo data seeded: 2 sources, 3 locations, 7 events");
|
||||
} catch (e) {
|
||||
console.error("[Cal] Seed error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function daysFromNow(days: number, hours: number, minutes: number): Date {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() + days);
|
||||
d.setHours(hours, minutes, 0, 0);
|
||||
return d;
|
||||
}
|
||||
|
||||
initDB().then(seedDemoIfEmpty);
|
||||
|
||||
// ── API: Events ──
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,99 @@ async function initDB() {
|
|||
}
|
||||
}
|
||||
|
||||
initDB();
|
||||
async function seedDemoIfEmpty() {
|
||||
try {
|
||||
const count = await sql.unsafe("SELECT count(*)::int as cnt FROM rnotes.notebooks");
|
||||
if (parseInt(count[0].cnt) > 0) return;
|
||||
|
||||
// Notebook 1: Project Ideas
|
||||
const nb1 = await sql.unsafe(
|
||||
`INSERT INTO rnotes.notebooks (title, description, cover_color, is_public)
|
||||
VALUES ('Project Ideas', 'Brainstorms and design notes for the r* ecosystem', '#6366f1', true) RETURNING id`
|
||||
);
|
||||
// Notebook 2: Meeting Notes
|
||||
const nb2 = await sql.unsafe(
|
||||
`INSERT INTO rnotes.notebooks (title, description, cover_color, is_public)
|
||||
VALUES ('Meeting Notes', 'Weekly standups, design reviews, and retrospectives', '#f59e0b', true) RETURNING id`
|
||||
);
|
||||
// Notebook 3: How-To Guides
|
||||
const nb3 = await sql.unsafe(
|
||||
`INSERT INTO rnotes.notebooks (title, description, cover_color, is_public)
|
||||
VALUES ('How-To Guides', 'Tutorials and onboarding guides for contributors', '#10b981', true) RETURNING id`
|
||||
);
|
||||
|
||||
// Create tags
|
||||
const tagIds: Record<string, string> = {};
|
||||
for (const name of ["design", "architecture", "cosmolocal", "governance", "onboarding", "review", "standup"]) {
|
||||
const row = await sql.unsafe(
|
||||
`INSERT INTO rnotes.tags (name) VALUES ($1) ON CONFLICT (name) DO UPDATE SET name = $1 RETURNING id`,
|
||||
[name]
|
||||
);
|
||||
tagIds[name] = row[0].id;
|
||||
}
|
||||
|
||||
// Seed notes
|
||||
const notes = [
|
||||
{
|
||||
nbId: nb1[0].id, title: "Cosmolocal Manufacturing Network",
|
||||
content: "## Vision\n\nDesign global, manufacture local. Every creative work should be producible by the nearest capable provider.\n\n## Key Components\n\n- **Artifact Spec**: Standardized envelope describing what to produce\n- **Provider Registry**: Directory of local makers with capabilities + pricing\n- **rCart**: Marketplace connecting creators to providers\n- **Revenue Splits**: 50% provider, 35% creator, 15% community\n\n## Open Questions\n\n- How do we handle quality assurance across distributed providers?\n- Should providers be able to set custom margins?\n- What's the minimum viable set of capabilities for launch?",
|
||||
tags: ["cosmolocal", "architecture"], pinned: true,
|
||||
},
|
||||
{
|
||||
nbId: nb1[0].id, title: "Revenue Sharing Model",
|
||||
content: "## Current Split\n\n| Recipient | Share | Rationale |\n|-----------|-------|-----------|\n| Provider | 50% | Covers materials, labor, shipping |\n| Creator | 35% | Design and creative work |\n| Community | 15% | Platform maintenance, commons fund |\n\n## Enoughness Thresholds\n\nOnce a funnel reaches its sufficient threshold, surplus flows to the next highest-need funnel. This prevents accumulation and keeps resources flowing.\n\n## Implementation\n\nrFunds Flow Service handles deposits from rCart. Each order total is routed through the configured flow → funnel → overflow splits.",
|
||||
tags: ["cosmolocal", "governance"],
|
||||
},
|
||||
{
|
||||
nbId: nb1[0].id, title: "FUN Model: Forget, Update, New",
|
||||
content: "## Replacing CRUD\n\nNothing is permanently destroyed in rSpace.\n\n- **Forget** replaces Delete — soft-delete with `forgotten: true`. Shapes stay in document, hidden from canvas. Memory panel lets you browse + Remember.\n- **Update** stays the same — public `sync.updateShape()` for programmatic updates\n- **New** replaces Create — language shift: toolbar says \"New X\", events are `new-shape`\n\n## Why?\n\nData sovereignty means users should always be able to recover their work. The Memory panel makes forgotten shapes discoverable, like a digital archive.",
|
||||
tags: ["design", "architecture"],
|
||||
},
|
||||
{
|
||||
nbId: nb2[0].id, title: "Weekly Standup — Feb 15, 2026",
|
||||
content: "## Attendees\n\nAlice, Bob, Carol\n\n## Updates\n\n**Alice**: Finished EncryptID guardian recovery flow. 2-of-3 guardian approval working. Next: device linking via QR code.\n\n**Bob**: Provider registry now has 6 printers globally. Working on proximity search with earthdistance extension.\n\n**Carol**: rFunds river visualization deployed. Enoughness layer showing golden glow on sufficient funnels.\n\n## Action Items\n\n- [ ] Alice: Document guardian recovery API endpoints\n- [ ] Bob: Add turnaround time estimates to provider matching\n- [ ] Carol: Add demo mode to river view with mock data",
|
||||
tags: ["standup"],
|
||||
},
|
||||
{
|
||||
nbId: nb2[0].id, title: "Design Review — rBooks Flipbook Reader",
|
||||
content: "## What We Reviewed\n\nThe react-pageflip integration for PDF reading in rBooks.\n\n## Feedback\n\n1. **Page turn animation** — smooth, feels good on desktop. On mobile, swipe gesture needs larger hit area.\n2. **PDF rendering** — react-pdf handles most PDFs well. Large files (>50MB) cause browser memory issues.\n3. **Read Locally mode** — IndexedDB storage works. Need to show storage usage somewhere.\n\n## Decisions\n\n- Ship current version, iterate on mobile\n- Add a 50MB soft warning on upload\n- Explore PDF.js worker for background rendering",
|
||||
tags: ["review", "design"],
|
||||
},
|
||||
{
|
||||
nbId: nb3[0].id, title: "Getting Started with rSpace Development",
|
||||
content: "## Prerequisites\n\n- Bun runtime (v1.3+)\n- Docker + Docker Compose\n- Git access to Gitea\n\n## Local Setup\n\n```bash\ngit clone ssh://git@gitea.jeffemmett.com:223/jeffemmett/rspace-online.git\ncd rspace-online\nbun install\nbun run dev\n```\n\n## Module Structure\n\nEach module lives in `modules/{name}/` and exports an `RSpaceModule` interface:\n\n```typescript\nexport interface RSpaceModule {\n id: string;\n name: string;\n icon: string;\n description: string;\n routes: Hono;\n}\n```\n\n## Adding a New Module\n\n1. Create `modules/{name}/mod.ts`\n2. Create `modules/{name}/components/` for web components\n3. Add build step in `vite.config.ts`\n4. Register in `server/index.ts`",
|
||||
tags: ["onboarding"],
|
||||
},
|
||||
{
|
||||
nbId: nb3[0].id, title: "How to Add a Cosmolocal Provider",
|
||||
content: "## Overview\n\nProviders are local print shops, makerspaces, or studios that can fulfill rCart orders.\n\n## Steps\n\n1. Visit `providers.mycofi.earth`\n2. Sign in with your rStack passkey\n3. Click \"Register Provider\"\n4. Fill in:\n - Name, location (address + coordinates)\n - Capabilities (laser-print, risograph, screen-print, etc.)\n - Substrates (paper types, fabric, vinyl)\n - Turnaround time and pricing\n5. Submit for review\n\n## Matching Algorithm\n\nWhen an order comes in, rCart matches based on:\n- Required capabilities vs. provider capabilities\n- Geographic distance (earthdistance extension)\n- Turnaround time\n- Price",
|
||||
tags: ["cosmolocal", "onboarding"],
|
||||
},
|
||||
];
|
||||
|
||||
for (const n of notes) {
|
||||
const row = await sql.unsafe(
|
||||
`INSERT INTO rnotes.notes (notebook_id, title, content, content_plain, type, is_pinned)
|
||||
VALUES ($1, $2, $3, $4, 'NOTE', $5) RETURNING id`,
|
||||
[n.nbId, n.title, n.content, n.content.replace(/<[^>]*>/g, " ").replace(/[#*|`\-\[\]]/g, " ").replace(/\s+/g, " ").trim(), n.pinned || false]
|
||||
);
|
||||
for (const tagName of n.tags) {
|
||||
if (tagIds[tagName]) {
|
||||
await sql.unsafe(
|
||||
"INSERT INTO rnotes.note_tags (note_id, tag_id) VALUES ($1, $2) ON CONFLICT DO NOTHING",
|
||||
[row[0].id, tagIds[tagName]]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[Notes] Demo data seeded: 3 notebooks, 7 notes, 7 tags");
|
||||
} catch (e) {
|
||||
console.error("[Notes] Seed error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
initDB().then(seedDemoIfEmpty);
|
||||
|
||||
// ── Helper: get or create user ──
|
||||
async function getOrCreateUser(did: string, username?: string) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,65 @@ async function initDB() {
|
|||
}
|
||||
}
|
||||
|
||||
initDB();
|
||||
async function seedDemoIfEmpty() {
|
||||
try {
|
||||
const count = await sql.unsafe("SELECT count(*)::int as cnt FROM rvote.spaces");
|
||||
if (parseInt(count[0].cnt) > 0) return;
|
||||
|
||||
// Create demo user
|
||||
const user = await sql.unsafe(
|
||||
`INSERT INTO rvote.users (did, username) VALUES ('did:demo:seed', 'demo')
|
||||
ON CONFLICT (did) DO UPDATE SET username = 'demo' RETURNING id`
|
||||
);
|
||||
const userId = user[0].id;
|
||||
|
||||
// Create voting space
|
||||
await sql.unsafe(
|
||||
`INSERT INTO rvote.spaces (slug, name, description, owner_did, visibility, promotion_threshold)
|
||||
VALUES ('community', 'Community Governance', 'Proposals for the rSpace ecosystem', 'did:demo:seed', 'public', 100)`
|
||||
);
|
||||
|
||||
// Seed proposals in various states
|
||||
const proposals = [
|
||||
{ title: "Add dark mode across all r* modules", desc: "Implement a consistent dark theme with a toggle in shell.css. Use CSS custom properties for theming so each module inherits automatically.", status: "RANKING", score: 45 },
|
||||
{ title: "Implement real-time collaboration in rNotes", desc: "Use Automerge CRDTs (already in the stack) to enable simultaneous editing of notes, similar to how rSpace canvas works.", status: "RANKING", score: 72 },
|
||||
{ title: "Adopt cosmolocal print-on-demand for all merch", desc: "Route all merchandise orders through the provider registry to find the closest printer. Reduces shipping emissions and supports local economies.", status: "VOTING", score: 105 },
|
||||
{ title: "Use EncryptID passkeys for all authentication", desc: "Standardize on WebAuthn passkeys via EncryptID across the entire r* ecosystem. One passkey, all apps.", status: "PASSED", score: 150 },
|
||||
{ title: "Switch from PostgreSQL to SQLite for simpler deployment", desc: "Evaluate replacing PostgreSQL with SQLite for modules that don't need concurrent writes.", status: "FAILED", score: 30 },
|
||||
];
|
||||
|
||||
for (const p of proposals) {
|
||||
const row = await sql.unsafe(
|
||||
`INSERT INTO rvote.proposals (space_slug, author_id, title, description, status, score)
|
||||
VALUES ('community', $1, $2, $3, $4, $5) RETURNING id`,
|
||||
[userId, p.title, p.desc, p.status, p.score]
|
||||
);
|
||||
|
||||
if (p.status === "VOTING") {
|
||||
await sql.unsafe(
|
||||
`UPDATE rvote.proposals SET voting_ends_at = NOW() + INTERVAL '5 days', final_yes = 5, final_no = 2 WHERE id = $1`,
|
||||
[row[0].id]
|
||||
);
|
||||
} else if (p.status === "PASSED") {
|
||||
await sql.unsafe(
|
||||
`UPDATE rvote.proposals SET final_yes = 12, final_no = 3, final_abstain = 2 WHERE id = $1`,
|
||||
[row[0].id]
|
||||
);
|
||||
} else if (p.status === "FAILED") {
|
||||
await sql.unsafe(
|
||||
`UPDATE rvote.proposals SET final_yes = 2, final_no = 8, final_abstain = 1 WHERE id = $1`,
|
||||
[row[0].id]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[Vote] Demo data seeded: 1 space, 5 proposals");
|
||||
} catch (e) {
|
||||
console.error("[Vote] Seed error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
initDB().then(seedDemoIfEmpty);
|
||||
|
||||
// ── Helper: get or create user by DID ──
|
||||
async function getOrCreateUser(did: string, username?: string) {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,49 @@ async function initDB() {
|
|||
}
|
||||
}
|
||||
|
||||
initDB();
|
||||
async function seedDemoIfEmpty() {
|
||||
try {
|
||||
const count = await sql.unsafe("SELECT count(*)::int as cnt FROM rwork.spaces");
|
||||
if (parseInt(count[0].cnt) > 0) return;
|
||||
|
||||
// Create workspace
|
||||
const space = await sql.unsafe(
|
||||
`INSERT INTO rwork.spaces (name, slug, description, icon, owner_did)
|
||||
VALUES ('rSpace Development', 'rspace-dev', 'Building the cosmolocal r* ecosystem', '🚀', 'did:demo:seed')
|
||||
RETURNING id`
|
||||
);
|
||||
const spaceId = space[0].id;
|
||||
|
||||
// Seed tasks across all kanban columns
|
||||
const tasks = [
|
||||
{ title: "Add dark mode toggle to settings page", status: "TODO", priority: "MEDIUM", labels: ["feature"], sort: 0 },
|
||||
{ title: "Write API documentation for rPubs endpoints", status: "TODO", priority: "LOW", labels: ["docs"], sort: 1 },
|
||||
{ title: "Investigate slow PDF generation on large documents", status: "TODO", priority: "HIGH", labels: ["bug"], sort: 2 },
|
||||
{ title: "Implement file search and filtering in rFiles", status: "IN_PROGRESS", priority: "HIGH", labels: ["feature"], sort: 0 },
|
||||
{ title: "Set up SMTP relay for transactional notifications", status: "IN_PROGRESS", priority: "MEDIUM", labels: ["chore"], sort: 1 },
|
||||
{ title: "Add PDF export to rNotes notebooks", status: "REVIEW", priority: "MEDIUM", labels: ["feature"], sort: 0 },
|
||||
{ title: "Fix conviction score decay calculation in rVote", status: "REVIEW", priority: "HIGH", labels: ["bug"], sort: 1 },
|
||||
{ title: "Deploy EncryptID passkey authentication", status: "DONE", priority: "URGENT", labels: ["feature"], sort: 0 },
|
||||
{ title: "Set up Cloudflare tunnel for all r* domains", status: "DONE", priority: "HIGH", labels: ["chore"], sort: 1 },
|
||||
{ title: "Create cosmolocal provider directory with 6 printers", status: "DONE", priority: "MEDIUM", labels: ["feature"], sort: 2 },
|
||||
{ title: "Migrate email from Resend to self-hosted Mailcow", status: "DONE", priority: "MEDIUM", labels: ["chore"], sort: 3 },
|
||||
];
|
||||
|
||||
for (const t of tasks) {
|
||||
await sql.unsafe(
|
||||
`INSERT INTO rwork.tasks (space_id, title, status, priority, labels, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[spaceId, t.title, t.status, t.priority, t.labels, t.sort]
|
||||
);
|
||||
}
|
||||
|
||||
console.log("[Work] Demo data seeded: 1 workspace, 11 tasks");
|
||||
} catch (e) {
|
||||
console.error("[Work] Seed error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
initDB().then(seedDemoIfEmpty);
|
||||
|
||||
// ── API: Spaces ──
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue