Merge branch 'dev'
CI/CD / deploy (push) Failing after 1m57s Details

This commit is contained in:
Jeff Emmett 2026-04-16 17:18:24 -04:00
commit f29cf02fb7
30 changed files with 77 additions and 75 deletions

View File

@ -265,7 +265,8 @@ redirects to the unified server with subdomain-based space routing.
| **rMaps** | rmaps.online | Geographic mapping & location hierarchy | | **rMaps** | rmaps.online | Geographic mapping & location hierarchy |
| **rTrips** | rtrips.online | Trip planning with itineraries | | **rTrips** | rtrips.online | Trip planning with itineraries |
| **rTasks** | rtasks.online | Task boards & project management | | **rTasks** | rtasks.online | Task boards & project management |
| **rSchedule** | rschedule.online | Persistent cron-based job scheduling with email, webhooks & briefings | | **rMinders** | rminders.online | Reminders, cron jobs, and automation workflows — email, webhooks, briefings |
| **rSchedule** | rschedule.online | Calendly-style booking from rCal availability (native port of schedule-jeffemmett) |
### Communication ### Communication

View File

@ -56,7 +56,8 @@ These are the building blocks that all rApps share and compose:
| **rMaps** | Real-time location sharing with OSM tiles, indoor routing (c3nav), privacy controls (precision fuzzing, ghost mode), and push notifications | | **rMaps** | Real-time location sharing with OSM tiles, indoor routing (c3nav), privacy controls (precision fuzzing, ghost mode), and push notifications |
| **rTrips** | Collaborative trip planner with itinerary, routing, expenses, and packing lists | | **rTrips** | Collaborative trip planner with itinerary, routing, expenses, and packing lists |
| **rTasks** | Kanban boards with ClickUp bi-directional sync | | **rTasks** | Kanban boards with ClickUp bi-directional sync |
| **rSchedule** | Cron-based job scheduling — emails, webhooks, calendar events, broadcasts, workflow nodes | | **rMinders** | Reminders, cron jobs, automation workflows — emails, webhooks, calendar events, broadcasts |
| **rSchedule** | Calendly-style public booking against rCal availability |
### Communication ### Communication

View File

@ -1,19 +1,19 @@
--- ---
id: TASK-104 id: TASK-104
title: n8n-style automation canvas for rSchedule title: n8n-style automation canvas for rMinders
status: Done status: Done
assignee: [] assignee: []
created_date: '2026-03-10 18:43' created_date: '2026-03-10 18:43'
labels: labels:
- rschedule - rminders
- feature - feature
- automation - automation
dependencies: [] dependencies: []
references: references:
- modules/rschedule/schemas.ts - modules/rminders/schemas.ts
- modules/rschedule/mod.ts - modules/rminders/mod.ts
- modules/rschedule/components/folk-automation-canvas.ts - modules/rminders/components/folk-automation-canvas.ts
- modules/rschedule/components/automation-canvas.css - modules/rminders/components/automation-canvas.css
- vite.config.ts - vite.config.ts
priority: medium priority: medium
--- ---
@ -21,14 +21,14 @@ priority: medium
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Visual workflow builder at /:space/rschedule/reminders that lets users wire together triggers, conditions, and actions from any rApp — enabling automations like "if my location approaches home, notify family" or "when document sign-off completes, schedule posts and notify comms director." Visual workflow builder at /:space/rminders/reminders that lets users wire together triggers, conditions, and actions from any rApp — enabling automations like "if my location approaches home, notify family" or "when document sign-off completes, schedule posts and notify comms director."
Built with SVG canvas (pan/zoom/Bezier wiring), 15 node types across 3 categories, REST-persisted CRUD, topological execution engine, cron tick loop integration, and webhook trigger endpoint. Built with SVG canvas (pan/zoom/Bezier wiring), 15 node types across 3 categories, REST-persisted CRUD, topological execution engine, cron tick loop integration, and webhook trigger endpoint.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria ## Acceptance Criteria
<!-- AC:BEGIN --> <!-- AC:BEGIN -->
- [ ] #1 Canvas loads at /:space/rschedule/reminders with node palette - [ ] #1 Canvas loads at /:space/rminders/reminders with node palette
- [ ] #2 Drag nodes from palette, wire ports, configure — auto-saves via REST - [ ] #2 Drag nodes from palette, wire ports, configure — auto-saves via REST
- [ ] #3 Run All on manual-trigger workflow — nodes animate, execution log shows results - [ ] #3 Run All on manual-trigger workflow — nodes animate, execution log shows results
- [ ] #4 Cron workflows execute on tick loop - [ ] #4 Cron workflows execute on tick loop
@ -39,9 +39,9 @@ Built with SVG canvas (pan/zoom/Bezier wiring), 15 node types across 3 categorie
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented n8n-style automation canvas for rSchedule with 5 files (2490 lines added): Implemented n8n-style automation canvas for rMinders with 5 files (2490 lines added):
**schemas.ts** — 15 automation node types (5 triggers, 4 conditions, 6 actions), NODE_CATALOG with typed ports and config schemas, Workflow/WorkflowNode/WorkflowEdge types, extended ScheduleDoc. **schemas.ts** — 15 automation node types (5 triggers, 4 conditions, 6 actions), NODE_CATALOG with typed ports and config schemas, Workflow/WorkflowNode/WorkflowEdge types, extended MindersDoc.
**folk-automation-canvas.ts** — SVG canvas with pan/zoom, left sidebar node palette (drag-to-add), Bezier edge wiring between typed ports, right sidebar config panel driven by NODE_CATALOG, execution visualization, REST persistence with 1.5s debounced auto-save. **folk-automation-canvas.ts** — SVG canvas with pan/zoom, left sidebar node palette (drag-to-add), Bezier edge wiring between typed ports, right sidebar config panel driven by NODE_CATALOG, execution visualization, REST persistence with 1.5s debounced auto-save.

View File

@ -24,5 +24,5 @@ Created shared ViewHistory<V> utility class providing stack-based back navigatio
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Commit 31b0885 on dev+main. New shared/view-history.ts with ViewHistory<V> class (push/back/canGoBack/peekBack/reset, max depth 20). Integrated into rtrips, rmaps, rtasks, rforum, rphotos, rvote, rnotes, rinbox, rschedule, rcart. Full rWork→rTasks rename: directory modules/rwork→modules/rtasks, component folk-work-board→folk-tasks-board, class FolkWorkBoard→FolkTasksBoard, all cross-module refs, docker-compose, vite config, encryptid CORS, landing pages. Removed rwork.online from cloudflared config and deleted its Cloudflare zone. Commit 31b0885 on dev+main. New shared/view-history.ts with ViewHistory<V> class (push/back/canGoBack/peekBack/reset, max depth 20). Integrated into rtrips, rmaps, rtasks, rforum, rphotos, rvote, rnotes, rinbox, rminders, rcart. Full rWork→rTasks rename: directory modules/rwork→modules/rtasks, component folk-work-board→folk-tasks-board, class FolkWorkBoard→FolkTasksBoard, all cross-module refs, docker-compose, vite config, encryptid CORS, landing pages. Removed rwork.online from cloudflared config and deleted its Cloudflare zone.
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -24,7 +24,7 @@ Ensure every rApp module has:
## Current State (27 modules) ## Current State (27 modules)
- **12 already have local-first/Automerge**: rbooks, rcal, rcart, rfiles, rflows, rinbox, rnotes, rsocials, rsplat, rtasks, rtrips, rvote - **12 already have local-first/Automerge**: rbooks, rcal, rcart, rfiles, rflows, rinbox, rnotes, rsocials, rsplat, rtasks, rtrips, rvote
- **2 use ephemeral WebSocket sync** (no Automerge): rmaps, rnetwork - **2 use ephemeral WebSocket sync** (no Automerge): rmaps, rnetwork
- **13 have NO real-time sync**: rchoices, rdata, rdesign, rdocs, rforum, rmeets, rphotos, rpubs, rswag, rtube, rwallet, rspace, rschedule - **13 have NO real-time sync**: rchoices, rdata, rdesign, rdocs, rforum, rmeets, rphotos, rpubs, rswag, rtube, rwallet, rspace, rminders
## "Pull rApplet to rSpace" Pattern ## "Pull rApplet to rSpace" Pattern
A standardized UI component (`folk-applet-pull.ts`) that: A standardized UI component (`folk-applet-pull.ts`) that:
@ -42,7 +42,7 @@ rbooks, rcal, rcart, rfiles, rflows, rinbox, rnotes, rsocials, rsplat, rtasks, r
- **rchoices**: Add schema + local-first-client for voting sessions, live vote tallies - **rchoices**: Add schema + local-first-client for voting sessions, live vote tallies
- **rswag**: Add schema for shared design state, collaborative editing - **rswag**: Add schema for shared design state, collaborative editing
- **rwallet**: Add schema for shared wallet watchlist, collaborative treasury view - **rwallet**: Add schema for shared wallet watchlist, collaborative treasury view
- **rschedule**: Already has schemas, needs local-first-client.ts + component sync - **rminders**: Already has schemas, needs local-first-client.ts + component sync
- **rnetwork**: Already has WebSocket, add Automerge doc for CRM data persistence - **rnetwork**: Already has WebSocket, add Automerge doc for CRM data persistence
### Tier 3 — UI-only wrappers, add lightweight sync (4 modules) ### Tier 3 — UI-only wrappers, add lightweight sync (4 modules)

View File

@ -1,6 +1,6 @@
--- ---
id: TASK-118.5 id: TASK-118.5
title: Add local-first-client to rschedule title: Add local-first-client to rminders
status: Done status: Done
assignee: [] assignee: []
created_date: '2026-03-16 00:06' created_date: '2026-03-16 00:06'
@ -17,10 +17,10 @@ priority: medium
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
rschedule already has Automerge schemas but lacks a local-first-client.ts for client-side sync. Add the client and wire it into the 3 components (automation-canvas, reminders-widget, schedule-app). rminders already has Automerge schemas but lacks a local-first-client.ts for client-side sync. Add the client and wire it into the 3 components (automation-canvas, reminders-widget, minders-app).
## New file: ## New file:
- `modules/rschedule/local-first-client.ts` — wraps existing schemas with sync methods - `modules/rminders/local-first-client.ts` — wraps existing schemas with sync methods
## Component updates: ## Component updates:
- All 3 components init the client, subscribe, and react to remote changes - All 3 components init the client, subscribe, and react to remote changes

View File

@ -6,7 +6,7 @@ assignee: []
created_date: '2026-03-17 01:01' created_date: '2026-03-17 01:01'
labels: labels:
- canvas - canvas
- rschedule - rminders
- UX - UX
dependencies: [] dependencies: []
references: references:
@ -37,5 +37,5 @@ Add multiple UX affordances for scheduling reminders on canvas shapes: floating
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented 4 reminder scheduling UX enhancements in `website/canvas.html` (156 insertions):\n\n1. **Right-click context menu** — \"📅 Schedule a reminder\" option in shape context menu opens reminder widget\n2. **Email notification** — Fetches user email from EncryptID `/auth/api/account/security`, caches it, passes `notifyEmail` to rSchedule API, shows confirmation in feedback\n3. **Floating calendar icon** — 28px circular 📅 button positioned at selected shape's top-right corner, repositions on scroll/zoom, toggles widget on click\n4. **Drag-to-calendar** — Compact calendar appears after 200ms of shape drag, day cells highlight on hover, releasing over a day creates the reminder Implemented 4 reminder scheduling UX enhancements in `website/canvas.html` (156 insertions):\n\n1. **Right-click context menu** — \"📅 Schedule a reminder\" option in shape context menu opens reminder widget\n2. **Email notification** — Fetches user email from EncryptID `/auth/api/account/security`, caches it, passes `notifyEmail` to rMinders API, shows confirmation in feedback\n3. **Floating calendar icon** — 28px circular 📅 button positioned at selected shape's top-right corner, repositions on scroll/zoom, toggles widget on click\n4. **Drag-to-calendar** — Compact calendar appears after 200ms of shape drag, day cells highlight on hover, releasing over a day creates the reminder
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -13,7 +13,7 @@ dependencies: []
references: references:
- modules/rchoices/mod.ts - modules/rchoices/mod.ts
- modules/rcal/mod.ts - modules/rcal/mod.ts
- modules/rschedule/mod.ts - modules/rminders/mod.ts
- server/index.ts (webhook pattern examples around line 2808) - server/index.ts (webhook pattern examples around line 2808)
priority: high priority: high
--- ---
@ -30,7 +30,7 @@ Enable lightweight poll/RSVP responses via text message. Users send a text or cl
## Key integration points: ## Key integration points:
- **rChoices** (`modules/rchoices/`) — simple polls (vote/rank/spider) - **rChoices** (`modules/rchoices/`) — simple polls (vote/rank/spider)
- **rCal** (`modules/rcal/`) — event RSVPs (attendee fields exist but stub) - **rCal** (`modules/rcal/`) — event RSVPs (attendee fields exist but stub)
- **rSchedule** (`modules/rschedule/`) — could add SMS as action type for scheduled sends - **rMinders** (`modules/rminders/`) — could add SMS as action type for scheduled sends
- **Existing webhook pattern** — follow payment webhook style (unauthenticated POST endpoints) - **Existing webhook pattern** — follow payment webhook style (unauthenticated POST endpoints)
## Design notes from initial discussion: ## Design notes from initial discussion:

View File

@ -33,6 +33,6 @@ export const MODULES: ModuleEntry[] = [
{ id: "rsplat", name: "rSplat", primarySelector: "folk-splat-viewer" }, { id: "rsplat", name: "rSplat", primarySelector: "folk-splat-viewer" },
{ id: "rphotos", name: "rPhotos", primarySelector: "folk-photo-gallery" }, { id: "rphotos", name: "rPhotos", primarySelector: "folk-photo-gallery" },
{ id: "rsocials", name: "rSocials", primarySelector: undefined }, // HTML hub page, no main element { id: "rsocials", name: "rSocials", primarySelector: undefined }, // HTML hub page, no main element
{ id: "rschedule", name: "rSchedule", primarySelector: "folk-schedule-app" }, { id: "rminders", name: "rMinders", primarySelector: "folk-minders-app" },
{ id: "rmeets", name: "rMeets", primarySelector: undefined }, // HTML hub page { id: "rmeets", name: "rMeets", primarySelector: undefined }, // HTML hub page
]; ];

View File

@ -922,7 +922,7 @@ export class CommentPinManager {
}; };
if (email) body.notifyEmail = email; if (email) body.notifyEmail = email;
const res = await fetch(`${getModuleApiBase("rschedule")}/api/reminders`, { const res = await fetch(`${getModuleApiBase("rminders")}/api/reminders`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(body), body: JSON.stringify(body),

View File

@ -538,7 +538,7 @@ const WIDGET_API: Record<string, { path: string; transform: (data: any) => Widge
}; };
}, },
}, },
rschedule: { rminders: {
path: "/api/jobs", path: "/api/jobs",
transform: (data) => { transform: (data) => {
const jobs = data?.results || []; const jobs = data?.results || [];

View File

@ -34,7 +34,7 @@ export const MODULE_META: Record<string, ModuleDisplayMeta> = {
rtrips: { badge: "rT", color: "#6ee7b7", name: "rTrips", icon: "✈️" }, rtrips: { badge: "rT", color: "#6ee7b7", name: "rTrips", icon: "✈️" },
rmaps: { badge: "rM", color: "#86efac", name: "rMaps", icon: "🗺️" }, rmaps: { badge: "rM", color: "#86efac", name: "rMaps", icon: "🗺️" },
rmeets: { badge: "rMe", color: "#6ee7b7", name: "rMeets", icon: "📹" }, rmeets: { badge: "rMe", color: "#6ee7b7", name: "rMeets", icon: "📹" },
rschedule: { badge: "rSc", color: "#93c5fd", name: "rSchedule", icon: "⏰" }, rminders: { badge: "rMi", color: "#93c5fd", name: "rMinders", icon: "⏰" },
rsocials: { badge: "rSo", color: "#f9a8d4", name: "rSocials", icon: "📱" }, rsocials: { badge: "rSo", color: "#f9a8d4", name: "rSocials", icon: "📱" },
rdesign: { badge: "rDe", color: "#7c3aed", name: "rDesign", icon: "🎨" }, rdesign: { badge: "rDe", color: "#7c3aed", name: "rDesign", icon: "🎨" },
rtime: { badge: "rTi", color: "#a78bfa", name: "rTime", icon: "⏳" }, rtime: { badge: "rTi", color: "#a78bfa", name: "rTime", icon: "⏳" },

View File

@ -1011,7 +1011,7 @@ class FolkCalendarView extends HTMLElement {
const virtualHtml = e.is_virtual ? `<span class="ev-virtual" title="Virtual">\u{1F4BB}</span>` : ""; const virtualHtml = e.is_virtual ? `<span class="ev-virtual" title="Virtual">\u{1F4BB}</span>` : "";
const likelihoodHtml = es.isTentative ? `<span class="ev-likelihood">${es.likelihoodLabel}</span>` : ""; const likelihoodHtml = es.isTentative ? `<span class="ev-likelihood">${es.likelihoodLabel}</span>` : "";
const lockHtml = e.accessVisibility && e.accessVisibility !== 'viewer' ? `<span class="membrane-lock" title="Restricted: ${e.accessVisibility} only">&#128274;</span> ` : ""; const lockHtml = e.accessVisibility && e.accessVisibility !== 'viewer' ? `<span class="membrane-lock" title="Restricted: ${e.accessVisibility} only">&#128274;</span> ` : "";
return `<div class="ev-label" style="border-left:2px ${es.borderStyle} ${evColor};background:${es.bgColor};opacity:${es.opacity}" data-event-id="${e.id}">${e.rToolSource === "rSchedule" ? '<span class="ev-bell">&#128276;</span>' : ""}${lockHtml}${virtualHtml}<span class="ev-time">${this.formatTime(e.start_time)}</span>${this.esc(e.title)}${likelihoodHtml}${cityHtml}</div>`; return `<div class="ev-label" style="border-left:2px ${es.borderStyle} ${evColor};background:${es.bgColor};opacity:${es.opacity}" data-event-id="${e.id}">${e.rToolSource === "rMinders" ? '<span class="ev-bell">&#128276;</span>' : ""}${lockHtml}${virtualHtml}<span class="ev-time">${this.formatTime(e.start_time)}</span>${this.esc(e.title)}${likelihoodHtml}${cityHtml}</div>`;
}).join(""); }).join("");
} }
@ -2284,7 +2284,7 @@ class FolkCalendarView extends HTMLElement {
private getScheduleApiBase(): string { private getScheduleApiBase(): string {
const path = window.location.pathname; const path = window.location.pathname;
const match = path.match(/^(\/[^/]+)/); const match = path.match(/^(\/[^/]+)/);
return match ? `${match[1]}/rschedule` : "/rschedule"; return match ? `${match[1]}/rminders` : "/rminders";
} }
private async handleReminderDrop(e: DragEvent, dropDate: string) { private async handleReminderDrop(e: DragEvent, dropDate: string) {

View File

@ -447,7 +447,7 @@ routes.post("/api/events", async (c) => {
isVirtual: is_virtual || false, isVirtual: is_virtual || false,
virtualUrl: virtual_url || null, virtualUrl: virtual_url || null,
virtualPlatform: virtual_platform || null, virtualPlatform: virtual_platform || null,
rToolSource: r_tool_source || 'rSchedule', rToolSource: r_tool_source || 'rMinders',
rToolEntityId: r_tool_entity_id || null, rToolEntityId: r_tool_entity_id || null,
attendees: [], attendees: [],
attendeeCount: 0, attendeeCount: 0,

View File

@ -729,10 +729,10 @@ class NotesCommentPanel extends HTMLElement {
// Set reminder on thread // Set reminder on thread
let reminderId: string | undefined; let reminderId: string | undefined;
// Try creating a reminder via rSchedule API (non-demo only) // Try creating a reminder via rMinders API (non-demo only)
if (!this.isDemo && this._space) { if (!this.isDemo && this._space) {
try { try {
const res = await fetch(`${getModuleApiBase("rschedule")}/api/reminders`, { const res = await fetch(`${getModuleApiBase("rminders")}/api/reminders`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json', ...this.authHeaders() }, headers: { 'Content-Type': 'application/json', ...this.authHeaders() },
body: JSON.stringify({ body: JSON.stringify({
@ -783,10 +783,10 @@ class NotesCommentPanel extends HTMLElement {
const thread = threads.find(t => t.id === threadId); const thread = threads.find(t => t.id === threadId);
const reminderId = thread?.reminderId; const reminderId = thread?.reminderId;
// Delete from rSchedule if exists // Delete from rMinders if exists
if (reminderId && !this.isDemo && this._space) { if (reminderId && !this.isDemo && this._space) {
try { try {
await fetch(`${getModuleApiBase("rschedule")}/api/reminders/${reminderId}`, { await fetch(`${getModuleApiBase("rminders")}/api/reminders/${reminderId}`, {
method: 'DELETE', method: 'DELETE',
headers: this.authHeaders(), headers: this.authHeaders(),
}); });

View File

@ -88,7 +88,7 @@ export function renderLanding(): string {
<div class="rl-card rl-card--center"> <div class="rl-card rl-card--center">
<div class="rl-icon-box">&#128197;</div> <div class="rl-icon-box">&#128197;</div>
<h3>Calendar Integration</h3> <h3>Calendar Integration</h3>
<p>Schedule meetings through rCal and rSchedule. Room links auto-generated, invites sent through your own mail server.</p> <p>Schedule meetings through rCal and rMinders. Room links auto-generated, invites sent through your own mail server.</p>
</div> </div>
<div class="rl-card rl-card--center"> <div class="rl-card rl-card--center">
<div class="rl-icon-box">&#128172;</div> <div class="rl-icon-box">&#128172;</div>

View File

@ -100,7 +100,7 @@ export function renderLanding(): string {
<div class="rl-card"> <div class="rl-card">
<div style="font-size:1.75rem;margin-bottom:1rem">&#128197;</div> <div style="font-size:1.75rem;margin-bottom:1rem">&#128197;</div>
<h3>Shape Scheduling</h3> <h3>Shape Scheduling</h3>
<p>Tag any shape to a calendar date. Drag shapes onto the mini-calendar or click the schedule icon &mdash; reminders sync to rSchedule with email notifications.</p> <p>Tag any shape to a calendar date. Drag shapes onto the mini-calendar or click the schedule icon &mdash; reminders sync to rMinders with email notifications.</p>
</div> </div>
<div class="rl-card"> <div class="rl-card">
<div style="font-size:1.75rem;margin-bottom:1rem">&#128274;</div> <div style="font-size:1.75rem;margin-bottom:1rem">&#128274;</div>

View File

@ -11,7 +11,7 @@ import { escapeHtml, escapeAttr, brandedAppName, MODULE_LANDING_CSS, RICH_LANDIN
/** Category → module IDs mapping for the tabbed showcase. */ /** Category → module IDs mapping for the tabbed showcase. */
const CATEGORY_GROUPS: Record<string, { label: string; icon: string; ids: string[] }> = { const CATEGORY_GROUPS: Record<string, { label: string; icon: string; ids: string[] }> = {
plan: { label: "Plan", icon: "📋", ids: ["rcal", "rtasks", "rschedule", "rtime"] }, plan: { label: "Plan", icon: "📋", ids: ["rcal", "rtasks", "rminders", "rtime"] },
create: { label: "Create", icon: "✏️", ids: ["rspace", "rdocs", "rnotes", "rdesign", "rsplat", "rpubs", "rsheets", "rbooks"] }, create: { label: "Create", icon: "✏️", ids: ["rspace", "rdocs", "rnotes", "rdesign", "rsplat", "rpubs", "rsheets", "rbooks"] },
communicate: { label: "Communicate", icon: "💬", ids: ["rchats", "rforum", "rinbox", "rmeets", "rsocials"] }, communicate: { label: "Communicate", icon: "💬", ids: ["rchats", "rforum", "rinbox", "rmeets", "rsocials"] },
govern: { label: "Govern", icon: "⚖️", ids: ["rchoices", "rvote", "rgov", "crowdsurf"] }, govern: { label: "Govern", icon: "⚖️", ids: ["rchoices", "rvote", "rgov", "crowdsurf"] },

View File

@ -7,7 +7,7 @@
* *
* 108 tools across 36 groups: * 108 tools across 36 groups:
* spaces (2), rcal (4), rnotes (5), rtasks (5), rwallet (4), * spaces (2), rcal (4), rnotes (5), rtasks (5), rwallet (4),
* rsocials (4), rnetwork (3), rinbox (4), rtime (4), rfiles (3), rschedule (4), * rsocials (4), rnetwork (3), rinbox (4), rtime (4), rfiles (3), rminders (4),
* rvote (3), rchoices (3), rtrips (4), rcart (4), rexchange (4), rbnb (4), * rvote (3), rchoices (3), rtrips (4), rcart (4), rexchange (4), rbnb (4),
* rvnb (3), crowdsurf (2), rbooks (2), rpubs (2), rmeets (2), rtube (2), * rvnb (3), crowdsurf (2), rbooks (2), rpubs (2), rmeets (2), rtube (2),
* rswag (2), rdesign (2), rsplat (2), rphotos (2), rflows (2), rdocs (5), * rswag (2), rdesign (2), rsplat (2), rphotos (2), rflows (2), rdocs (5),
@ -31,7 +31,7 @@ import { registerNetworkTools } from "./mcp-tools/rnetwork";
import { registerInboxTools } from "./mcp-tools/rinbox"; import { registerInboxTools } from "./mcp-tools/rinbox";
import { registerTimeTools } from "./mcp-tools/rtime"; import { registerTimeTools } from "./mcp-tools/rtime";
import { registerFilesTools } from "./mcp-tools/rfiles"; import { registerFilesTools } from "./mcp-tools/rfiles";
import { registerScheduleTools } from "./mcp-tools/rschedule"; import { registerMindersTools } from "./mcp-tools/rminders";
import { registerVoteTools } from "./mcp-tools/rvote"; import { registerVoteTools } from "./mcp-tools/rvote";
import { registerChoicesTools } from "./mcp-tools/rchoices"; import { registerChoicesTools } from "./mcp-tools/rchoices";
import { registerTripsTools } from "./mcp-tools/rtrips"; import { registerTripsTools } from "./mcp-tools/rtrips";
@ -75,7 +75,7 @@ function createMcpServerInstance(syncServer: SyncServer): McpServer {
registerInboxTools(server, syncServer); registerInboxTools(server, syncServer);
registerTimeTools(server, syncServer); registerTimeTools(server, syncServer);
registerFilesTools(server, syncServer); registerFilesTools(server, syncServer);
registerScheduleTools(server, syncServer); registerMindersTools(server, syncServer);
registerVoteTools(server, syncServer); registerVoteTools(server, syncServer);
registerChoicesTools(server, syncServer); registerChoicesTools(server, syncServer);
registerTripsTools(server, syncServer); registerTripsTools(server, syncServer);

View File

@ -14,7 +14,7 @@ import { getRecentContactsForMI } from "../modules/rnetwork/mod";
import { getRecentThreadsForMI } from "../modules/rinbox/mod"; import { getRecentThreadsForMI } from "../modules/rinbox/mod";
import { getRecentCommitmentsForMI } from "../modules/rtime/mod"; import { getRecentCommitmentsForMI } from "../modules/rtime/mod";
import { getRecentFilesForMI } from "../modules/rfiles/mod"; import { getRecentFilesForMI } from "../modules/rfiles/mod";
import { getUpcomingRemindersForMI } from "../modules/rschedule/mod"; import { getUpcomingRemindersForMI } from "../modules/rminders/mod";
import { getMapPinsForMI } from "../modules/rmaps/mod"; import { getMapPinsForMI } from "../modules/rmaps/mod";
import { getRecentMeetingsForMI } from "../modules/rmeets/mod"; import { getRecentMeetingsForMI } from "../modules/rmeets/mod";
import { getRecentVideosForMI } from "../modules/rtube/mod"; import { getRecentVideosForMI } from "../modules/rtube/mod";
@ -139,7 +139,7 @@ export function queryModuleContent(
return { ok: true, module, queryType, data: files, summary: lines.length ? `Recent files:\n${lines.join("\n")}` : "No files found." }; return { ok: true, module, queryType, data: files, summary: lines.length ? `Recent files:\n${lines.join("\n")}` : "No files found." };
} }
case "rschedule": { case "rminders": {
const reminders = getUpcomingRemindersForMI(space, 14, limit); const reminders = getUpcomingRemindersForMI(space, 14, limit);
if (queryType === "count") { if (queryType === "count") {
return { ok: true, module, queryType, data: { count: reminders.length }, summary: `${reminders.length} upcoming reminders.` }; return { ok: true, module, queryType, data: { count: reminders.length }, summary: `${reminders.length} upcoming reminders.` };

View File

@ -272,7 +272,7 @@ When you need to look up the user's actual data (notes, tasks, events):
[MI_ACTION:{"type":"query-content","module":"rinbox","queryType":"recent","limit":5}] [MI_ACTION:{"type":"query-content","module":"rinbox","queryType":"recent","limit":5}]
[MI_ACTION:{"type":"query-content","module":"rtime","queryType":"recent","limit":5}] [MI_ACTION:{"type":"query-content","module":"rtime","queryType":"recent","limit":5}]
[MI_ACTION:{"type":"query-content","module":"rfiles","queryType":"recent","limit":5}] [MI_ACTION:{"type":"query-content","module":"rfiles","queryType":"recent","limit":5}]
[MI_ACTION:{"type":"query-content","module":"rschedule","queryType":"recent","limit":5}] [MI_ACTION:{"type":"query-content","module":"rminders","queryType":"recent","limit":5}]
[MI_ACTION:{"type":"query-content","module":"rmaps","queryType":"recent","limit":5}] [MI_ACTION:{"type":"query-content","module":"rmaps","queryType":"recent","limit":5}]
[MI_ACTION:{"type":"query-content","module":"rmeets","queryType":"recent","limit":5}] [MI_ACTION:{"type":"query-content","module":"rmeets","queryType":"recent","limit":5}]
[MI_ACTION:{"type":"query-content","module":"rtube","queryType":"recent","limit":5}] [MI_ACTION:{"type":"query-content","module":"rtube","queryType":"recent","limit":5}]

View File

@ -139,11 +139,11 @@ Shared calendar with external sync.
- **Event creation** shared community events`, - **Event creation** shared community events`,
}, },
{ {
id: "demo-tool-rschedule", id: "demo-tool-rminders",
type: "folk-markdown", type: "folk-markdown",
...pos(2, 1), width: CARD_W, height: CARD_H, rotation: 0, ...pos(2, 1), width: CARD_W, height: CARD_H, rotation: 0,
content: `### rSchedule content: `### rMinders
Automations and scheduled jobs. Automations, reminders, and scheduled jobs.
- **Cron jobs** recurring tasks on a schedule - **Cron jobs** recurring tasks on a schedule
- **Reminders** email notifications - **Reminders** email notifications

View File

@ -31,7 +31,7 @@ const FAVICON_BADGE_MAP: Record<string, { badge: string; color: string }> = {
rmeets: { badge: "r📹", color: "#67e8f9" }, rmeets: { badge: "r📹", color: "#67e8f9" },
rcal: { badge: "r📅", color: "#7dd3fc" }, rcal: { badge: "r📅", color: "#7dd3fc" },
rchoices: { badge: "r☑", color: "#f0abfc" }, rchoices: { badge: "r☑", color: "#f0abfc" },
rschedule: { badge: "r⏱", color: "#a5b4fc" }, rminders: { badge: "r⏱", color: "#a5b4fc" },
rtasks: { badge: "r📋", color: "#cbd5e1" }, rtasks: { badge: "r📋", color: "#cbd5e1" },
rtime: { badge: "r⏳", color: "#a78bfa" }, rtime: { badge: "r⏳", color: "#a78bfa" },
rvote: { badge: "r🗳", color: "#c4b5fd" }, rvote: { badge: "r🗳", color: "#c4b5fd" },

View File

@ -18,7 +18,7 @@ import { getRecentContactsForMI } from "../modules/rnetwork/mod";
import { getRecentThreadsForMI } from "../modules/rinbox/mod"; import { getRecentThreadsForMI } from "../modules/rinbox/mod";
import { getRecentCommitmentsForMI } from "../modules/rtime/mod"; import { getRecentCommitmentsForMI } from "../modules/rtime/mod";
import { getRecentFilesForMI } from "../modules/rfiles/mod"; import { getRecentFilesForMI } from "../modules/rfiles/mod";
import { getUpcomingRemindersForMI } from "../modules/rschedule/mod"; import { getUpcomingRemindersForMI } from "../modules/rminders/mod";
import { getMapPinsForMI } from "../modules/rmaps/mod"; import { getMapPinsForMI } from "../modules/rmaps/mod";
import { getRecentMeetingsForMI } from "../modules/rmeets/mod"; import { getRecentMeetingsForMI } from "../modules/rmeets/mod";
import { getRecentVideosForMI } from "../modules/rtube/mod"; import { getRecentVideosForMI } from "../modules/rtube/mod";
@ -244,7 +244,7 @@ class SpaceKnowledgeIndex {
const safeTitle = sanitizeForPrompt(r.title, MAX_TITLE_LENGTH); const safeTitle = sanitizeForPrompt(r.title, MAX_TITLE_LENGTH);
const line = `${date}: ${safeTitle}${r.sourceModule ? ` (from ${r.sourceModule})` : ""}`; const line = `${date}: ${safeTitle}${r.sourceModule ? ` (from ${r.sourceModule})` : ""}`;
entries.push({ entries.push({
id: `rschedule:${r.id}`, moduleId: "rschedule", category: "time", id: `rminders:${r.id}`, moduleId: "rminders", category: "time",
title: safeTitle, detail: r.sourceModule || "", title: safeTitle, detail: r.sourceModule || "",
tags: [r.sourceModule].filter(Boolean) as string[], tags: [r.sourceModule].filter(Boolean) as string[],
timestamp: r.remindAt || now, formatted: line, timestamp: r.remindAt || now, formatted: line,

View File

@ -38,7 +38,7 @@ const MODULE_BADGES: Record<string, { badge: string; color: string }> = {
// Coordinate // Coordinate
rcal: { badge: "r📅", color: "#7dd3fc" }, // sky-300 rcal: { badge: "r📅", color: "#7dd3fc" }, // sky-300
rchoices: { badge: "r☑", color: "#f0abfc" }, // fuchsia-300 rchoices: { badge: "r☑", color: "#f0abfc" }, // fuchsia-300
rschedule: { badge: "r⏱", color: "#a5b4fc" }, // indigo-200 rminders: { badge: "r⏱", color: "#a5b4fc" }, // indigo-200
rtasks: { badge: "r📋", color: "#cbd5e1" }, // slate-300 rtasks: { badge: "r📋", color: "#cbd5e1" }, // slate-300
rtime: { badge: "r⏳", color: "#a78bfa" }, // violet-400 rtime: { badge: "r⏳", color: "#a78bfa" }, // violet-400
rvote: { badge: "r🗳", color: "#c4b5fd" }, // violet-300 rvote: { badge: "r🗳", color: "#c4b5fd" }, // violet-300
@ -94,7 +94,7 @@ const MODULE_CATEGORIES: Record<string, string> = {
crowdsurf: "Coordinate", crowdsurf: "Coordinate",
rcal: "Coordinate", rcal: "Coordinate",
rchoices: "Coordinate", rchoices: "Coordinate",
rschedule: "Coordinate", rminders: "Coordinate",
rtasks: "Coordinate", rtasks: "Coordinate",
rtime: "Coordinate", rtime: "Coordinate",
rvote: "Coordinate", rvote: "Coordinate",

View File

@ -53,7 +53,7 @@ const MODULE_BADGES: Record<string, { badge: string; color: string }> = {
rbooks: { badge: "r📚", color: "#fda4af" }, rbooks: { badge: "r📚", color: "#fda4af" },
rdata: { badge: "r📊", color: "#d8b4fe" }, rdata: { badge: "r📊", color: "#d8b4fe" },
rtasks: { badge: "r📋", color: "#cbd5e1" }, rtasks: { badge: "r📋", color: "#cbd5e1" },
rschedule: { badge: "r⏱", color: "#a5b4fc" }, rminders: { badge: "r⏱", color: "#a5b4fc" },
rids: { badge: "r🪪", color: "#6ee7b7" }, rids: { badge: "r🪪", color: "#6ee7b7" },
rcred: { badge: "r⭐", color: "#d97706" }, rcred: { badge: "r⭐", color: "#d97706" },
rstack: { badge: "r✨", color: "" }, rstack: { badge: "r✨", color: "" },

View File

@ -3,7 +3,7 @@
* *
* Two complementary projections: * Two complementary projections:
* - **Scheduled** (MarkwhenSourceFactory): "what's happening / will happen" * - **Scheduled** (MarkwhenSourceFactory): "what's happening / will happen"
* events with explicit start/end (rCal, rSchedule). * events with explicit start/end (rCal, rMinders).
* - **Creations** (CreationEnumerator / CreationSpec): "what has been made" * - **Creations** (CreationEnumerator / CreationSpec): "what has been made"
* every CRDT record's birth moment across every rApp. This is what rPast * every CRDT record's birth moment across every rApp. This is what rPast
* renders. Declared statically in `creation-specs.ts`. * renders. Declared statically in `creation-specs.ts`.

View File

@ -13,7 +13,7 @@ const TAG_COLOR_DEFAULTS: Record<string, string> = {
rnotes: 'green', rnotes: 'green',
rtasks: 'orange', rtasks: 'orange',
rvote: 'purple', rvote: 'purple',
rschedule: 'teal', rminders: 'teal',
rtrips: 'amber', rtrips: 'amber',
rinbox: 'gray', rinbox: 'gray',
}; };

View File

@ -1384,42 +1384,42 @@ export default defineConfig({
resolve(__dirname, "dist/modules/rphotos/photos.css"), resolve(__dirname, "dist/modules/rphotos/photos.css"),
); );
// Build schedule module component // Build minders module component
await wasmBuild({ await wasmBuild({
configFile: false, configFile: false,
root: resolve(__dirname, "modules/rschedule/components"), root: resolve(__dirname, "modules/rminders/components"),
build: { build: {
emptyOutDir: false, emptyOutDir: false,
outDir: resolve(__dirname, "dist/modules/rschedule"), outDir: resolve(__dirname, "dist/modules/rminders"),
lib: { lib: {
entry: resolve(__dirname, "modules/rschedule/components/folk-schedule-app.ts"), entry: resolve(__dirname, "modules/rminders/components/folk-minders-app.ts"),
formats: ["es"], formats: ["es"],
fileName: () => "folk-schedule-app.js", fileName: () => "folk-minders-app.js",
}, },
rollupOptions: { rollupOptions: {
output: { output: {
entryFileNames: "folk-schedule-app.js", entryFileNames: "folk-minders-app.js",
}, },
}, },
}, },
}); });
// Copy schedule CSS // Copy minders CSS
mkdirSync(resolve(__dirname, "dist/modules/rschedule"), { recursive: true }); mkdirSync(resolve(__dirname, "dist/modules/rminders"), { recursive: true });
copyFileSync( copyFileSync(
resolve(__dirname, "modules/rschedule/components/schedule.css"), resolve(__dirname, "modules/rminders/components/minders.css"),
resolve(__dirname, "dist/modules/rschedule/schedule.css"), resolve(__dirname, "dist/modules/rminders/minders.css"),
); );
// Build schedule reminders widget component // Build minders reminders widget component
await wasmBuild({ await wasmBuild({
configFile: false, configFile: false,
root: resolve(__dirname, "modules/rschedule/components"), root: resolve(__dirname, "modules/rminders/components"),
build: { build: {
emptyOutDir: false, emptyOutDir: false,
outDir: resolve(__dirname, "dist/modules/rschedule"), outDir: resolve(__dirname, "dist/modules/rminders"),
lib: { lib: {
entry: resolve(__dirname, "modules/rschedule/components/folk-reminders-widget.ts"), entry: resolve(__dirname, "modules/rminders/components/folk-reminders-widget.ts"),
formats: ["es"], formats: ["es"],
fileName: () => "folk-reminders-widget.js", fileName: () => "folk-reminders-widget.js",
}, },
@ -1431,20 +1431,20 @@ export default defineConfig({
}, },
}); });
// Build schedule automation canvas component // Build minders automation canvas component
await wasmBuild({ await wasmBuild({
configFile: false, configFile: false,
root: resolve(__dirname, "modules/rschedule/components"), root: resolve(__dirname, "modules/rminders/components"),
resolve: { resolve: {
alias: { alias: {
"../schemas": resolve(__dirname, "modules/rschedule/schemas.ts"), "../schemas": resolve(__dirname, "modules/rminders/schemas.ts"),
}, },
}, },
build: { build: {
emptyOutDir: false, emptyOutDir: false,
outDir: resolve(__dirname, "dist/modules/rschedule"), outDir: resolve(__dirname, "dist/modules/rminders"),
lib: { lib: {
entry: resolve(__dirname, "modules/rschedule/components/folk-automation-canvas.ts"), entry: resolve(__dirname, "modules/rminders/components/folk-automation-canvas.ts"),
formats: ["es"], formats: ["es"],
fileName: () => "folk-automation-canvas.js", fileName: () => "folk-automation-canvas.js",
}, },
@ -1458,8 +1458,8 @@ export default defineConfig({
// Copy automation canvas CSS // Copy automation canvas CSS
copyFileSync( copyFileSync(
resolve(__dirname, "modules/rschedule/components/automation-canvas.css"), resolve(__dirname, "modules/rminders/components/automation-canvas.css"),
resolve(__dirname, "dist/modules/rschedule/automation-canvas.css"), resolve(__dirname, "dist/modules/rminders/automation-canvas.css"),
); );
// ── Demo infrastructure ── // ── Demo infrastructure ──

View File

@ -8290,7 +8290,7 @@ Use real coordinates, YYYY-MM-DD dates, ISO currency codes. Ask clarifying quest
if (!rwSelectedShape) return; if (!rwSelectedShape) return;
const info = getShapeInfo(rwSelectedShape); const info = getShapeInfo(rwSelectedShape);
const remindAt = new Date(dateStr + "T09:00:00").getTime(); const remindAt = new Date(dateStr + "T09:00:00").getTime();
const schedBase = `/${communitySlug}/rschedule`; const schedBase = `/${communitySlug}/rminders`;
// Fetch user email for notification (cached after first call) // Fetch user email for notification (cached after first call)
if (cachedUserEmail === null) { if (cachedUserEmail === null) {