fix(infra): consolidate external service URLs to rspace.online (TASK-51.1)

- Replace rdata.online/collect.js with /collect.js proxy route (7 files)
- Update MAPS_SYNC_URL to wss://maps-sync.rspace.online
- Fix legacy TWENTY_API_URL to crm.rspace.online
- Add Traefik host exclusions for maps-sync, crm, analytics, newsletter
- Close TASK-23 (feature parity audit) and TASK-51.2 (301 redirects)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-11 19:12:51 -07:00
parent 1460d2b579
commit 751a2c8e7b
13 changed files with 100 additions and 26 deletions

View File

@ -1,7 +1,7 @@
---
id: TASK-23
title: 'Feature parity audit: 13 overlapping shapes'
status: To Do
status: Done
assignee: []
created_date: '2026-02-18 19:49'
labels:
@ -37,7 +37,40 @@ For each pair: read both implementations, note feature gaps, classify as critica
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 All 13 shape pairs compared side-by-side
- [ ] #2 Feature gaps documented with severity (critical/nice-to-have)
- [ ] #3 Critical gaps identified for immediate fix
- [x] #1 All 13 shape pairs compared side-by-side
- [x] #2 Feature gaps documented with severity (critical/nice-to-have)
- [x] #3 Critical gaps identified for immediate fix
<!-- AC:END -->
## Implementation Notes
### Audit Findings Summary
Completed feature-parity audit across all 13 shape pairs. Identified critical gaps in 7 shapes requiring backend/architecture work:
**Critical Gaps by Shape:**
1. **folk-chat**: No real-time backend sync (local-only messages). Messages only persist in client-side Automerge store; no server broadcast or presence system.
2. **folk-video-chat**: No peer-to-peer video infrastructure. Missing Jitsi integration; only local camera preview available.
3. **folk-markdown**: Plain textarea implementation vs canvas-website's MDXEditor. No table support, syntax highlighting, or rich markdown features.
4. **folk-slide**: No slide navigation or presentation system. Currently a decorative container only; lacks deck traversal and full-screen mode.
5. **folk-prompt**: No streaming response support. No arrow-binding template substitution for dynamic prompt construction from shape references.
6. **folk-obs-note**: No vault sync mechanism (Quartz/GitHub integration). Plain textarea editor without multi-format obsidian compatibility.
7. **folk-map**: No collaborative presence indicators, pin annotations, routing/directions, or multi-style layer support (satellite, terrain).
**Pervasive Gap (All 13 Shapes)**:
StandardizedToolWrapper features missing across all implementations: pin/minimize/maximize/tags. These foundational window-management features need framework-level integration.
**Best Parity Shapes** (closest feature coverage):
- folk-google-item: Good feature alignment with canvas-website GoogleItem
- folk-embed: Functional iframe/external content support
- folk-transcription: Core audio-to-text parity achieved
**Recommendation**:
Prioritize real-time sync infrastructure (folk-chat, folk-video-chat) and StandardizedToolWrapper integration as foundational for improving parity across all shapes. Markdown and slide features require moderate lift; map and obs-note features are lower priority for MVP.

View File

@ -1,7 +1,7 @@
---
id: TASK-51.1
title: 'Phase 2: Fix external service URLs (analytics, maps sync, Twenty CRM)'
status: To Do
status: Done
assignee: []
created_date: '2026-02-25 07:47'
labels:
@ -25,7 +25,27 @@ DECISION NEEDED: Is Twenty CRM (rnetwork.online) a separate container or proxied
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Analytics collect.js loads from relative path on rspace.online
- [ ] #2 Maps sync WebSocket connects via new URL
- [ ] #3 Network module reaches Twenty CRM without depending on rnetwork.online domain
- [x] #1 Analytics collect.js loads from relative path on rspace.online
- [x] #2 Maps sync WebSocket connects via new URL
- [x] #3 Network module reaches Twenty CRM without depending on rnetwork.online domain
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
### AC#1 — collect.js
- Added root-level `/collect.js` proxy route in `server/index.ts` (proxies from Umami at `analytics.rspace.online`)
- Replaced `https://rdata.online/collect.js``/collect.js` in: server/landing-proxy.ts, server/shell.ts (2x), server/landing.ts (2x), website/create-space.html, website/index.html, website/public/landing.html
### AC#2 — Maps sync
- Changed `MAPS_SYNC_URL` in docker-compose.yml: `wss://sync.rmaps.online``wss://maps-sync.rspace.online`
- Changed fallback in `modules/rmaps/mod.ts` to `wss://maps-sync.rspace.online`
- Added `Host(maps-sync.rspace.online)` to rmaps-sync Traefik labels on Netcup
- Added `maps-sync.rspace.online` CNAME in Cloudflare DNS
- Added `!Host(maps-sync.rspace.online)` exclusion to rspace-canvas wildcard Traefik rule
### AC#3 — Twenty CRM
- Already resolved: main docker-compose uses `TWENTY_API_URL=http://twenty-ch-server:3000` (internal Docker DNS)
- Fallback in rnetwork module already points to `crm.rspace.online`
- Fixed legacy docker-compose.standalone.yml: `https://rnetwork.online``https://crm.rspace.online`
<!-- SECTION:NOTES:END -->

View File

@ -1,7 +1,7 @@
---
id: TASK-51.2
title: 'Phase 1: Convert standalone domain rewrite to 301 redirects'
status: To Do
status: Done
assignee: []
created_date: '2026-02-25 07:47'
labels:
@ -24,8 +24,12 @@ Target: server/index.ts lines 482-521. Redirect HTML page loads, continue proxyi
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 rmaps.online/some-room returns 301 to rspace.online/demo/maps/some-room
- [ ] #2 rbooks.online/ returns 301 to rspace.online/demo/books
- [ ] #3 API and WebSocket requests still proxied without redirect
- [ ] #4 keepStandalone domains unaffected
- [x] #1 rmaps.online/some-room returns 301 to rspace.online/demo/maps/some-room
- [x] #2 rbooks.online/ returns 301 to rspace.online/demo/books
- [x] #3 API and WebSocket requests still proxied without redirect
- [x] #4 keepStandalone domains unaffected
<!-- AC:END -->
## Implementation Notes
301 redirect logic fully implemented in server/index.ts lines 2028-2098. domainToModule map built from mod.standaloneDomain, proper 301 Response.redirect issued, API/WS paths excluded. Traefik labels route all 20 standalone domains.

View File

@ -240,7 +240,7 @@ services:
command: ["bun", "run", "modules/rnetwork/standalone.ts"]
environment:
<<: *base-env
TWENTY_API_URL: https://rnetwork.online
TWENTY_API_URL: https://crm.rspace.online
TWENTY_API_TOKEN: ${TWENTY_API_TOKEN}
labels:
<<: *traefik-enabled

View File

@ -34,7 +34,7 @@ services:
- FUNNEL_ID=0ff6a9ac-1667-4fc7-9a01-b1620810509f
- UMAMI_URL=https://analytics.rspace.online
- UMAMI_WEBSITE_ID=292f6ac6-79f8-497b-ba6a-7a51e3b87b9f
- MAPS_SYNC_URL=wss://sync.rmaps.online
- MAPS_SYNC_URL=wss://maps-sync.rspace.online
- IMAP_HOST=mail.rmail.online
- IMAP_PORT=993
- IMAP_TLS_REJECT_UNAUTHORIZED=false
@ -61,7 +61,7 @@ services:
- "traefik.http.routers.rspace-main.entrypoints=web"
- "traefik.http.routers.rspace-main.priority=110"
# Subdomains — backward compat for *.rspace.online canvas
- "traefik.http.routers.rspace-canvas.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.rspace.online`) && !Host(`rspace.online`) && !Host(`www.rspace.online`) && !Host(`auth.rspace.online`)"
- "traefik.http.routers.rspace-canvas.rule=HostRegexp(`{subdomain:[a-z0-9-]+}.rspace.online`) && !Host(`rspace.online`) && !Host(`www.rspace.online`) && !Host(`auth.rspace.online`) && !Host(`maps-sync.rspace.online`) && !Host(`crm.rspace.online`) && !Host(`analytics.rspace.online`) && !Host(`newsletter.rspace.online`)"
- "traefik.http.routers.rspace-canvas.entrypoints=web"
- "traefik.http.routers.rspace-canvas.priority=100"
# ── Standalone domain routing (priority 120) — redirect to rspace.online ──

View File

@ -18,7 +18,7 @@ const SYNC_SERVER = process.env.MAPS_SYNC_URL || "http://localhost:3001";
// ── Sync URL for client-side WebSocket connection ──
routes.get("/api/sync-url", (c) => {
const wsUrl = process.env.MAPS_SYNC_URL || "wss://sync.rmaps.online";
const wsUrl = process.env.MAPS_SYNC_URL || "wss://maps-sync.rspace.online";
return c.json({ syncUrl: wsUrl });
});

View File

@ -153,6 +153,23 @@ app.get("/.well-known/webauthn", (c) => {
);
});
// ── Analytics tracker (proxied from Umami — replaces rdata.online/collect.js) ──
const UMAMI_URL = process.env.UMAMI_URL || "https://analytics.rspace.online";
app.get("/collect.js", async (c) => {
try {
const res = await fetch(`${UMAMI_URL}/script.js`, { signal: AbortSignal.timeout(5000) });
if (res.ok) {
const script = await res.text();
return new Response(script, {
headers: { "Content-Type": "application/javascript", "Cache-Control": "public, max-age=3600" },
});
}
} catch {}
return new Response("/* umami unavailable */", {
headers: { "Content-Type": "application/javascript" },
});
});
// ── Serve generated files from /data/files/generated/ ──
app.get("/data/files/generated/:filename", async (c) => {
const filename = c.req.param("filename");

View File

@ -163,7 +163,7 @@ async function transformWithRewriter(
element(el) {
el.append(
`<link rel="stylesheet" href="/shell.css">
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>`,
<script defer src="/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>`,
{ html: true },
);
},

View File

@ -57,7 +57,7 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
<style>${MODULE_LANDING_CSS}</style>
<style>${RICH_LANDING_CSS}</style>
<style>${MAIN_LANDING_CSS}</style>
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
<script defer src="/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
<body>
<header class="rstack-header">
@ -297,7 +297,7 @@ export function renderSpaceDashboard(space: string, modules: ModuleInfo[]): stri
<link rel="stylesheet" href="/shell.css">
<style>${MODULE_LANDING_CSS}</style>
<style>${SPACE_DASHBOARD_CSS}</style>
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
<script defer src="/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
<body>
<header class="rstack-header">

View File

@ -1195,7 +1195,7 @@ export function renderModuleLanding(opts: ModuleLandingOptions): string {
<link rel="stylesheet" href="/theme.css">
<link rel="stylesheet" href="/shell.css">
${cssBlock}
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
<script defer src="/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
<body>
<header class="rstack-header">
@ -1539,7 +1539,7 @@ export function renderSubPageInfo(opts: SubPageInfoOptions): string {
<link rel="stylesheet" href="/shell.css">
<style>${MODULE_LANDING_CSS}</style>
<style>${RICH_LANDING_CSS}</style>
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
<script defer src="/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
<body>
<header class="rstack-header">

View File

@ -219,7 +219,7 @@
color: var(--rs-text-muted);
}
</style>
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
<script defer src="/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
<body>
<header class="rstack-header">

View File

@ -262,7 +262,7 @@
}
</style>
<link rel="stylesheet" href="/shell.css">
<script defer src="https://rdata.online/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
<script defer src="/collect.js" data-website-id="6ee7917b-0ed7-44cb-a4c8-91037638526b"></script>
</head>
<body data-theme="dark">
<header class="rstack-header" data-theme="dark">

File diff suppressed because one or more lines are too long