chore: slash-command refinements, server import fixes, misc cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-03 13:21:04 -08:00
parent f86c6234af
commit 06f7d67cd3
12 changed files with 252 additions and 26 deletions

View File

@ -0,0 +1,38 @@
---
id: TASK-82
title: Sankey-proportional edges + node satisfaction bars in rFunds diagram
status: Done
assignee: []
created_date: '2026-03-03 05:32'
labels:
- rfunds
- visualization
dependencies: []
references:
- modules/rfunds/components/folk-funds-app.ts
- modules/rfunds/components/funds.css
- modules/rfunds/lib/simulation.ts
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Edge widths now reflect actual dollar flow (source rates, overflow excess, spending drain) instead of just allocation percentages. Zero-flow paths render as ghost edges. Edge labels show dollar amounts alongside percentages. Funnel nodes display an inflow satisfaction bar. Outcome progress bars enhanced with dollar labels.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Edge widths proportional to actual dollar flow per edge
- [ ] #2 Ghost edges (dashed, low opacity) for zero-flow paths
- [ ] #3 Edge labels show dollar amounts: $2.5k (50%)
- [ ] #4 Funnel nodes show inflow satisfaction bar (green = received, grey = gap)
- [ ] #5 Outcome nodes have enhanced 8px progress bars with dollar labels
- [ ] #6 Clean tsc and vite build with no errors
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented in commit e644797 on dev branch. Modified `folk-funds-app.ts` (renderAllEdges two-pass with EdgeInfo, computeInflowSatisfaction, renderFunnelNodeSvg/renderOutcomeNodeSvg updated) and `funds.css` (ghost edge + satisfaction bar styles). Deployed to production.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -0,0 +1,29 @@
---
id: TASK-83
title: Fix tab-cache inline style extraction for canvas toolbar
status: Done
assignee: []
created_date: '2026-03-03 07:42'
labels:
- bugfix
- canvas
- tab-cache
dependencies: []
references:
- shared/tab-cache.ts
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
The tab-cache system (shared/tab-cache.ts) only extracted <link rel="stylesheet"> tags when switching tabs, missing inline <style> blocks. The canvas toolbar CSS is entirely in inline <style> blocks in canvas.html, causing unstyled toolbar when switching to rSpace via tab cache.
Fixed extractContent() to also collect inline <style> blocks from fetched page heads, and loadAssets() to inject them with data-tab-style attributes for deduplication.
<!-- SECTION:DESCRIPTION:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added inline <style> block extraction to tab-cache's extractContent() and injection in loadAssets(). Toolbar now renders correctly when switching to rSpace tab via client-side navigation. Committed as 4819852.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -0,0 +1,30 @@
---
id: TASK-84
title: Fix shape x/y/size preservation on canvas reload
status: Done
assignee: []
created_date: '2026-03-03 07:42'
labels:
- bugfix
- canvas
- shapes
dependencies: []
references:
- lib/folk-shape.ts
- website/canvas.html
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
All canvas shapes stacked at (0,0) on page reload. Root cause: FolkShape.createRenderRoot() unconditionally read x/y/width/height from HTML attributes, but canvas.html sets these as JS properties before DOM insertion. Since attributes don't exist, everything defaulted to 0/auto.
Fixed to only read from attributes when they actually exist (getAttribute !== null). Also fixed eraser: hardDeleteShape() was only in a click handler that never fired because pointerdown already removed the target element — moved Automerge deletion into pointerdown.
<!-- SECTION:DESCRIPTION:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed createRenderRoot() to conditionally read from HTML attributes only when present. Fixed eraser to persist deletions to Automerge in pointerdown handler instead of unreachable click handler. Committed as 4f9b036.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -0,0 +1,36 @@
---
id: TASK-85
title: 'Fix folk-wrapper crash, service worker API exclusion, fal.ai image-gen'
status: Done
assignee: []
created_date: '2026-03-03 07:43'
labels:
- bugfix
- canvas
- service-worker
- ai
dependencies: []
references:
- lib/folk-wrapper.ts
- website/sw.ts
- server/index.ts
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Three fixes:
1. folk-wrapper.ts createRenderRoot() crashed with "Cannot read properties of null (reading 'appendChild')" — innerHTML="" removed the slot from DOM, making parentElement null on the next line. Fixed by saving parent ref before clearing.
2. Service worker (sw.ts) only excluded /api/ at root path, not module API paths like /jeff/rcal/api/events. These got cached and when network failed, catch handler returned undefined instead of a Response. Fixed with includes("/api/") and proper fallback Response.
3. Image generation returned 502 "No image returned" — queue.fal.run is async (returns request_id), not the actual image. Changed to synchronous fal.run endpoint for all three fal.ai endpoints (image-gen, t2v, i2v).
<!-- SECTION:DESCRIPTION:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed folk-wrapper parentElement null crash, service worker module API path exclusion with proper offline fallback, and fal.ai endpoint from queue.fal.run to fal.run for synchronous image/video generation. Committed as fef419f.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -0,0 +1,43 @@
---
id: TASK-86
title: Encrypted server-side account vault for EncryptID
status: Done
assignee: []
created_date: '2026-03-03 19:15'
updated_date: '2026-03-03 19:15'
labels:
- encryptid
- security
- feature
dependencies: []
references:
- src/encryptid/vault.ts
- src/encryptid/server.ts
- shared/local-first/crypto.ts
- server/local-first/backup-routes.ts
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Zero-knowledge vault stores all EncryptID account data (profile, emails, devices, addresses, wallets, preferences) as a single AES-256-GCM encrypted JSON blob via the backup API. Key derived deterministically from WebAuthn PRF via HKDF — same passkey = same key on any device. Server never sees plaintext.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 VaultManager class with AccountVault interface, DocCrypto encryption, backup API storage, localStorage cache
- [x] #2 Vault auto-loads on passkey auth (handleLogin + conditionalUI), clears on logout
- [x] #3 Dashboard UI: checklist item, vault section with Save/Restore buttons, status display
- [x] #4 Save triggers passkey re-auth → AES-256-GCM encrypt → PUT /api/backup/__vault/account-vault
- [x] #5 Restore triggers passkey re-auth → GET → decrypt → populate DOM
- [x] #6 checkVaultStatus() on profile load updates checklist green check
- [x] #7 No new server routes or DB tables — uses existing backup API
- [x] #8 tsc --noEmit and vite build pass clean
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
## Files Created\n- `src/encryptid/vault.ts` — VaultManager class, AccountVault interface, singleton pattern\n\n## Files Modified\n- `src/encryptid/index.ts` — Export vault types and functions\n- `src/encryptid/ui/login-button.ts` — Load vault after auth, clear on logout\n- `src/encryptid/server.ts` — Dashboard vault section, checklist item, inline crypto functions (deriveVaultKey, saveVault, restoreVault, checkVaultStatus)\n\n## Key Design\n- Vault key: `Master PRF → HKDF("rspace:__vault") → HKDF("doc:account-vault") → AES-256-GCM`\n- Dashboard uses inline WebCrypto (not VaultManager import) since dashboard auth doesn't initialize DocCrypto\n- Save/restore require biometric re-auth for security\n\nCommit: e2e12af, deployed to production.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@ -0,0 +1,30 @@
---
id: TASK-HIGH.4
title: Fix Automerge proxy re-assignment error on canvas load
status: Done
assignee: []
created_date: '2026-03-03 19:17'
updated_date: '2026-03-03 19:18'
labels: []
dependencies: []
parent_task_id: TASK-HIGH
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix 'Cannot create a reference to an existing document object' RangeError that fires twice on canvas load (initFromCache + WS sync). Root cause: Automerge proxy objects from doc.shapes passed through DOM elements back into Automerge.change(). Fixed by deep-cloning in #shapeToData() and #updateShapeInDoc().
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 No RangeError in console on canvas hard-refresh
- [x] #2 Shapes persist correctly after move/resize
- [x] #3 Cross-tab sync works without errors
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Fixed in commit 023a883. Deep-cloned shape data in #shapeToData() and #updateShapeInDoc() to break Automerge proxy chain. Deployed to production.
<!-- SECTION:NOTES:END -->

View File

@ -9,6 +9,21 @@ import { Plugin, PluginKey } from '@tiptap/pm/state';
import type { EditorView } from '@tiptap/pm/view'; import type { EditorView } from '@tiptap/pm/view';
import type { Editor } from '@tiptap/core'; import type { Editor } from '@tiptap/core';
/** Inline SVG icons for slash menu items (16×16, stroke-based, currentColor) */
const SLASH_ICONS: Record<string, string> = {
text: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="4" y1="3" x2="12" y2="3"/><line x1="8" y1="3" x2="8" y2="13"/><line x1="6" y1="13" x2="10" y2="13"/></svg>',
heading1: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3v10M8 3v10M2 8h6"/><text x="10.5" y="13" font-size="7" fill="currentColor" stroke="none" font-family="system-ui" font-weight="700">1</text></svg>',
heading2: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3v10M8 3v10M2 8h6"/><text x="10.5" y="13" font-size="7" fill="currentColor" stroke="none" font-family="system-ui" font-weight="700">2</text></svg>',
heading3: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M2 3v10M8 3v10M2 8h6"/><text x="10.5" y="13" font-size="7" fill="currentColor" stroke="none" font-family="system-ui" font-weight="700">3</text></svg>',
bulletList: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="6" y1="4" x2="14" y2="4"/><line x1="6" y1="8" x2="14" y2="8"/><line x1="6" y1="12" x2="14" y2="12"/><circle cx="3" cy="4" r="1" fill="currentColor" stroke="none"/><circle cx="3" cy="8" r="1" fill="currentColor" stroke="none"/><circle cx="3" cy="12" r="1" fill="currentColor" stroke="none"/></svg>',
orderedList: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="7" y1="4" x2="14" y2="4"/><line x1="7" y1="8" x2="14" y2="8"/><line x1="7" y1="12" x2="14" y2="12"/><text x="1.5" y="5.5" font-size="5" fill="currentColor" stroke="none" font-family="system-ui" font-weight="600">1</text><text x="1.5" y="9.5" font-size="5" fill="currentColor" stroke="none" font-family="system-ui" font-weight="600">2</text><text x="1.5" y="13.5" font-size="5" fill="currentColor" stroke="none" font-family="system-ui" font-weight="600">3</text></svg>',
taskList: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="5" height="5" rx="1"/><polyline points="3.5 4.5 4.5 5.5 6 3.5"/><line x1="9" y1="4.5" x2="14" y2="4.5"/><rect x="2" y="9" width="5" height="5" rx="1"/><line x1="9" y1="11.5" x2="14" y2="11.5"/></svg>',
codeBlock: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1.5" y="1.5" width="13" height="13" rx="2"/><polyline points="5 6 3.5 8 5 10"/><polyline points="11 6 12.5 8 11 10"/><line x1="9" y1="5" x2="7" y2="11"/></svg>',
blockquote: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="2" x2="3" y2="14"/><line x1="7" y1="4" x2="14" y2="4"/><line x1="7" y1="8" x2="14" y2="8"/><line x1="7" y1="12" x2="12" y2="12"/></svg>',
horizontalRule: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="2" y1="8" x2="14" y2="8"/><circle cx="4" cy="8" r="0.5" fill="currentColor"/><circle cx="8" cy="8" r="0.5" fill="currentColor"/><circle cx="12" cy="8" r="0.5" fill="currentColor"/></svg>',
image: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="1.5" y="2.5" width="13" height="11" rx="2"/><circle cx="5.5" cy="6" r="1.5"/><path d="M14.5 10.5l-3.5-3.5-5 5"/></svg>',
};
export interface SlashMenuItem { export interface SlashMenuItem {
title: string; title: string;
icon: string; icon: string;
@ -19,71 +34,72 @@ export interface SlashMenuItem {
export const SLASH_ITEMS: SlashMenuItem[] = [ export const SLASH_ITEMS: SlashMenuItem[] = [
{ {
title: 'Text', title: 'Text',
icon: 'Aa', icon: 'text',
description: 'Plain paragraph text', description: 'Plain paragraph text',
command: (e) => e.chain().focus().setParagraph().run(), command: (e) => e.chain().focus().setParagraph().run(),
}, },
{ {
title: 'Heading 1', title: 'Heading 1',
icon: 'H1', icon: 'heading1',
description: 'Large section heading', description: 'Large section heading',
command: (e) => e.chain().focus().setHeading({ level: 1 }).run(), command: (e) => e.chain().focus().setHeading({ level: 1 }).run(),
}, },
{ {
title: 'Heading 2', title: 'Heading 2',
icon: 'H2', icon: 'heading2',
description: 'Medium section heading', description: 'Medium section heading',
command: (e) => e.chain().focus().setHeading({ level: 2 }).run(), command: (e) => e.chain().focus().setHeading({ level: 2 }).run(),
}, },
{ {
title: 'Heading 3', title: 'Heading 3',
icon: 'H3', icon: 'heading3',
description: 'Small section heading', description: 'Small section heading',
command: (e) => e.chain().focus().setHeading({ level: 3 }).run(), command: (e) => e.chain().focus().setHeading({ level: 3 }).run(),
}, },
{ {
title: 'Bullet List', title: 'Bullet List',
icon: '&bull;', icon: 'bulletList',
description: 'Unordered bullet list', description: 'Unordered bullet list',
command: (e) => e.chain().focus().toggleBulletList().run(), command: (e) => e.chain().focus().toggleBulletList().run(),
}, },
{ {
title: 'Numbered List', title: 'Numbered List',
icon: '1.', icon: 'orderedList',
description: 'Ordered numbered list', description: 'Ordered numbered list',
command: (e) => e.chain().focus().toggleOrderedList().run(), command: (e) => e.chain().focus().toggleOrderedList().run(),
}, },
{ {
title: 'Task List', title: 'Task List',
icon: '&#9745;', icon: 'taskList',
description: 'Checklist with checkboxes', description: 'Checklist with checkboxes',
command: (e) => e.chain().focus().toggleTaskList().run(), command: (e) => e.chain().focus().toggleTaskList().run(),
}, },
{ {
title: 'Code Block', title: 'Code Block',
icon: '&lt;/&gt;', icon: 'codeBlock',
description: 'Syntax-highlighted code block', description: 'Syntax-highlighted code block',
command: (e) => e.chain().focus().toggleCodeBlock().run(), command: (e) => e.chain().focus().toggleCodeBlock().run(),
}, },
{ {
title: 'Blockquote', title: 'Blockquote',
icon: '&#8220;', icon: 'blockquote',
description: 'Indented quote block', description: 'Indented quote block',
command: (e) => e.chain().focus().toggleBlockquote().run(), command: (e) => e.chain().focus().toggleBlockquote().run(),
}, },
{ {
title: 'Horizontal Rule', title: 'Horizontal Rule',
icon: '&#8212;', icon: 'horizontalRule',
description: 'Visual divider line', description: 'Visual divider line',
command: (e) => e.chain().focus().setHorizontalRule().run(), command: (e) => e.chain().focus().setHorizontalRule().run(),
}, },
{ {
title: 'Image', title: 'Image',
icon: '&#128247;', icon: 'image',
description: 'Insert an image from URL', description: 'Insert an image from URL',
command: (e) => { command: (e) => {
const url = prompt('Image URL:'); // Dispatch custom event for parent to show URL popover
if (url) e.chain().focus().setImage({ src: url }).run(); const event = new CustomEvent('slash-insert-image', { bubbles: true, composed: true });
(e.view.dom as HTMLElement).dispatchEvent(event);
}, },
}, },
]; ];
@ -122,15 +138,17 @@ export function createSlashCommandPlugin(editor: Editor, shadowRoot: ShadowRoot)
function updateMenuContent() { function updateMenuContent() {
if (!menuEl) return; if (!menuEl) return;
menuEl.innerHTML = filteredItems menuEl.innerHTML = `<div class="slash-menu__header">Insert block</div>` +
filteredItems
.map( .map(
(item, i) => (item, i) =>
`<div class="slash-menu-item${i === selectedIndex ? ' selected' : ''}" data-index="${i}"> `<div class="slash-menu-item${i === selectedIndex ? ' selected' : ''}" data-index="${i}">
<span class="slash-menu-icon">${item.icon}</span> <span class="slash-menu-icon">${SLASH_ICONS[item.icon] || item.icon}</span>
<div class="slash-menu-text"> <div class="slash-menu-text">
<div class="slash-menu-title">${item.title}</div> <div class="slash-menu-title">${item.title}</div>
<div class="slash-menu-desc">${item.description}</div> <div class="slash-menu-desc">${item.description}</div>
</div> </div>
${i === selectedIndex ? '<span class="slash-menu-hint">Enter</span>' : ''}
</div>`, </div>`,
) )
.join(''); .join('');

View File

@ -329,7 +329,7 @@ async function getSpaceConfig(slug: string): Promise<SpaceAuthConfig | null> {
if (!doc) return null; if (!doc) return null;
return { return {
spaceSlug: slug, spaceSlug: slug,
visibility: (doc.meta.visibility || "public_read") as SpaceVisibility, visibility: (doc.meta.visibility || "public") as SpaceVisibility,
ownerDID: doc.meta.ownerDID || undefined, ownerDID: doc.meta.ownerDID || undefined,
app: "rspace", app: "rspace",
}; };
@ -352,9 +352,9 @@ app.post("/api/communities", async (c) => {
} }
const body = await c.req.json<{ name?: string; slug?: string; visibility?: SpaceVisibility }>(); const body = await c.req.json<{ name?: string; slug?: string; visibility?: SpaceVisibility }>();
const { name, slug, visibility = "public_read" } = body; const { name, slug, visibility = "public" } = body;
const validVisibilities: SpaceVisibility[] = ["public", "public_read", "authenticated", "members_only"]; const validVisibilities: SpaceVisibility[] = ["public", "permissioned", "private"];
if (visibility && !validVisibilities.includes(visibility)) return c.json({ error: "Invalid visibility" }, 400); if (visibility && !validVisibilities.includes(visibility)) return c.json({ error: "Invalid visibility" }, 400);
const result = await createSpace({ const result = await createSpace({
@ -381,7 +381,7 @@ app.post("/api/internal/provision", async (c) => {
name: space.charAt(0).toUpperCase() + space.slice(1), name: space.charAt(0).toUpperCase() + space.slice(1),
slug: space, slug: space,
ownerDID: `did:system:${space}`, ownerDID: `did:system:${space}`,
visibility: body.public ? "public" : "public_read", visibility: "public",
source: 'internal', source: 'internal',
}); });
if (!result.ok) return c.json({ error: result.error }, result.status); if (!result.ok) return c.json({ error: result.error }, result.status);
@ -1147,7 +1147,7 @@ app.post("/api/spaces/auto-provision", async (c) => {
name: `${claims.username}'s Space`, name: `${claims.username}'s Space`,
slug: username, slug: username,
ownerDID: claims.sub, ownerDID: claims.sub,
visibility: "members_only", visibility: "private",
source: 'auto-provision', source: 'auto-provision',
}); });
if (!result.ok) return c.json({ error: result.error }, result.status); if (!result.ok) return c.json({ error: result.error }, result.status);
@ -1156,9 +1156,9 @@ app.post("/api/spaces/auto-provision", async (c) => {
}); });
// ── Inject space visibility into HTML responses ── // ── Inject space visibility into HTML responses ──
// Replaces the default data-space-visibility="public_read" rendered by renderShell // Replaces the default data-space-visibility="public" rendered by renderShell
// with the actual visibility from the space config, so the client-side access gate // with the actual visibility from the space config, so the client-side access gate
// can block content for members_only spaces when no session exists. // can block content for private spaces when no session exists.
app.use("/:space/*", async (c, next) => { app.use("/:space/*", async (c, next) => {
await next(); await next();
const ct = c.res.headers.get("content-type"); const ct = c.res.headers.get("content-type");

View File

@ -58,7 +58,7 @@ export function renderShell(opts: ShellOptions): string {
modules, modules,
theme = "dark", theme = "dark",
head = "", head = "",
spaceVisibility = "public_read", spaceVisibility = "public",
} = opts; } = opts;
// Auto-populate from space data when not explicitly provided // Auto-populate from space data when not explicitly provided
@ -201,10 +201,10 @@ export function renderShell(opts: ShellOptions): string {
}; };
// ── Private space access gate ── // ── Private space access gate ──
// If the space is members_only and no session exists, show a sign-in gate // If the space is private and no session exists, show a sign-in gate
(function() { (function() {
var vis = document.body.getAttribute('data-space-visibility'); var vis = document.body.getAttribute('data-space-visibility');
if (vis !== 'members_only') return; if (vis !== 'private') return;
try { try {
var raw = localStorage.getItem('encryptid_session'); var raw = localStorage.getItem('encryptid_session');
if (raw) { if (raw) {

View File

@ -623,6 +623,7 @@ spaces.patch("/:slug", async (c) => {
if (body.visibility) { if (body.visibility) {
const valid: SpaceVisibility[] = ["public", "permissioned", "private"]; const valid: SpaceVisibility[] = ["public", "permissioned", "private"];
// Note: the create endpoint (line ~276) already validates correctly
if (!valid.includes(body.visibility)) { if (!valid.includes(body.visibility)) {
return c.json({ error: `Invalid visibility. Must be one of: ${valid.join(", ")}` }, 400); return c.json({ error: `Invalid visibility. Must be one of: ${valid.join(", ")}` }, 400);
} }

View File

@ -423,7 +423,7 @@ app.post('/api/register/complete', async (c) => {
const spaceSlug = username.toLowerCase().replace(/[^a-z0-9-]/g, '-'); const spaceSlug = username.toLowerCase().replace(/[^a-z0-9-]/g, '-');
if (!await communityExists(spaceSlug)) { if (!await communityExists(spaceSlug)) {
await createCommunity(username, spaceSlug, did, 'members_only', { await createCommunity(username, spaceSlug, did, 'private', {
enabledModules: DEFAULT_USER_MODULES, enabledModules: DEFAULT_USER_MODULES,
nestPolicy: DEFAULT_USER_NEST_POLICY, nestPolicy: DEFAULT_USER_NEST_POLICY,
}); });

View File

@ -1640,6 +1640,7 @@
<button id="new-choice-vote" title="New Poll">☑ Poll</button> <button id="new-choice-vote" title="New Poll">☑ Poll</button>
<button id="new-choice-rank" title="New Ranking">📊 Ranking</button> <button id="new-choice-rank" title="New Ranking">📊 Ranking</button>
<button id="new-choice-spider" title="New Scoring">🕸 Scoring</button> <button id="new-choice-spider" title="New Scoring">🕸 Scoring</button>
<button id="new-conviction" title="Conviction Ranking">⏳ Conviction</button>
<button id="new-token" title="New Token">🪙 Token</button> <button id="new-token" title="New Token">🪙 Token</button>
<button id="embed-funds" title="Embed rFunds">🌊 rFunds</button> <button id="embed-funds" title="Embed rFunds">🌊 rFunds</button>
<button id="embed-wallet" title="Embed rWallet">💰 rWallet</button> <button id="embed-wallet" title="Embed rWallet">💰 rWallet</button>