Generated by Spark: Convert Forum component to declarative JSON and Lua

This commit is contained in:
2025-12-23 23:37:20 +00:00
parent 241d5cf3ff
commit 70f12bc460
2 changed files with 306 additions and 6 deletions

293
IRC_CONVERSION_GUIDE.md Normal file
View File

@@ -0,0 +1,293 @@
# IRC Webchat Package - Declarative Conversion Guide
## Overview
The IRC Webchat component has been successfully converted from a traditional React TypeScript component to a fully declarative JSON and Lua-based package. This conversion demonstrates the power of the MetaBuilder package system, where complex interactive components can be defined entirely through configuration and scripting.
## Package Structure
The IRC Webchat package (`irc-webchat`) is now defined in `/src/lib/package-catalog.ts` and includes:
### 1. Manifest
```json
{
"id": "irc-webchat",
"name": "IRC-Style Webchat",
"version": "1.0.0",
"category": "social",
"icon": "💬"
}
```
### 2. Data Schemas
Three database schemas define the chat data structure:
- **ChatChannel**: Chat channels/rooms with name, description, topic
- **ChatMessage**: Individual messages with type (message, system, join, leave)
- **ChatUser**: Online users per channel
### 3. Pages
The package includes a pre-configured `/chat` page at Level 2 (user area) with authentication required.
### 4. Lua Scripts
Five Lua scripts handle all chat logic:
#### `lua_irc_send_message`
Creates a chat message object with unique ID, timestamp, and user information.
```lua
function sendMessage(channelId, username, userId, message)
-- Returns a message object
end
```
#### `lua_irc_handle_command`
Processes IRC-style commands like `/help`, `/users`, `/clear`, `/me`.
```lua
function handleCommand(command, channelId, username, onlineUsers)
-- Returns system response or command result
end
```
#### `lua_irc_format_time`
Formats Unix timestamps into human-readable 12-hour format.
```lua
function formatTime(timestamp)
-- Returns formatted time string like "02:45 PM"
end
```
#### `lua_irc_user_join`
Generates join messages when users enter a channel.
```lua
function userJoin(channelId, username, userId)
-- Returns a join-type message
end
```
#### `lua_irc_user_leave`
Generates leave messages when users exit a channel.
```lua
function userLeave(channelId, username, userId)
-- Returns a leave-type message
end
```
### 5. Component Configuration
The package defines a declarative component structure using JSON:
```json
{
"type": "IRCWebchat",
"config": {
"layout": "Card",
"children": [
{
"id": "header",
"type": "CardHeader",
"children": [...]
},
{
"id": "content",
"type": "CardContent",
"children": [
{
"id": "messages_area",
"type": "ScrollArea",
...
},
{
"id": "input_area",
"type": "Container",
...
}
]
}
]
}
}
```
### 6. Seed Data
Pre-configured chat channels:
- `general`: General discussion
- `random`: Random conversations
## How It Works
### Component Loading
1. **Package Initialization**: `initializePackageSystem()` is called in `App.tsx` on startup
2. **Script Registration**: All Lua scripts are registered with the `DeclarativeComponentRenderer`
3. **Component Registration**: Component configurations are registered for use in the builder
### Runtime Execution
When the IRC component is used:
1. **User Join**: Calls `lua_irc_user_join` to create a join message
2. **Send Message**: Calls `lua_irc_send_message` with user input
3. **Commands**: Calls `lua_irc_handle_command` when message starts with `/`
4. **Time Formatting**: Calls `lua_irc_format_time` for each message timestamp
5. **User Leave**: Calls `lua_irc_user_leave` on component unmount
### Data Flow
```
User Action → React Component (IRCWebchatDeclarative)
getDeclarativeRenderer().executeLuaScript(scriptId, params)
LuaEngine.execute(wrappedCode, context)
Lua Function Execution (Fengari)
Result Conversion (Lua table → JavaScript object)
React State Update (useKV for persistence)
```
## Migration from TSX
### Before (IRCWebchat.tsx)
```typescript
const handleSendMessage = () => {
const newMessage: ChatMessage = {
id: `msg_${Date.now()}_${Math.random()}`,
username: user.username,
userId: user.id,
message: trimmed,
timestamp: Date.now(),
type: 'message',
}
setMessages((current) => [...(current || []), newMessage])
}
```
### After (Declarative + Lua)
```typescript
const newMessage = await renderer.executeLuaScript('lua_irc_send_message', [
`chat_${channelName}`,
user.username,
user.id,
trimmed,
])
setMessages((current) => [...(current || []), newMessage])
```
## Benefits of Declarative Approach
### 1. **No Code Deployment**
- Changes to chat logic require only JSON/Lua updates
- No TypeScript compilation needed
- Hot-swappable functionality
### 2. **Package System Integration**
- IRC can be installed/uninstalled like any package
- Export/import as ZIP with all configurations
- Share with other MetaBuilder instances
### 3. **GUI Configuration**
- Chat commands can be modified in Level 4/5 panels
- Time format can be changed without code
- New message types can be added dynamically
### 4. **Multi-Tenancy Ready**
- Each tenant can customize chat behavior
- Lua scripts can be overridden per tenant
- Database schemas are tenant-isolated
### 5. **Security Sandboxing**
- Lua execution is sandboxed (can be enhanced)
- Scripts can be scanned for malicious code
- Limited API surface for Lua scripts
## File Structure
```
/src
/components
IRCWebchat.tsx [DEPRECATED - Can be removed]
IRCWebchatDeclarative.tsx [NEW - Uses declarative system]
/lib
package-catalog.ts [Contains irc-webchat package]
declarative-component-renderer.ts [Executes Lua scripts]
lua-engine.ts [Fengari Lua runtime]
package-loader.ts [Loads packages on init]
```
## Next Steps
### For Developers
1. Review the Lua scripts in `package-catalog.ts`
2. Test IRC functionality in Level 2 user area
3. Consider removing `IRCWebchat.tsx` (old component)
4. Add more Lua scripts for advanced features
### For Users (Level 4/5)
1. Navigate to Package Manager
2. View installed `irc-webchat` package
3. Modify Lua scripts to customize behavior
4. Add new commands via Lua Editor
5. Export package to share with others
## Testing the Conversion
1. **Login** as a regular user
2. Navigate to **Level 2** (User Area)
3. Go to the **Chat** tab
4. Test the following:
- Send messages
- Use `/help` command
- Use `/users` command
- Use `/me dances` command
- Use `/clear` command
- Open another window and see online users update
## Troubleshooting
### Lua Script Not Found
- Ensure `initializePackageSystem()` is called before component renders
- Check that `irc-webchat` package exists in `PACKAGE_CATALOG`
### Messages Not Sending
- Check browser console for Lua execution errors
- Verify parameter types match Lua script expectations
- Ensure `useKV` keys are correctly formatted
### Time Format Issues
- Review `lua_irc_format_time` script
- Check timestamp is in milliseconds (JavaScript format)
- Lua `os.time()` returns seconds, multiply by 1000
## Future Enhancements
1. **Channel Management**: Lua scripts for creating/deleting channels
2. **User Mentions**: Parse `@username` and notify users
3. **Message Reactions**: Add emoji reactions via Lua
4. **File Sharing**: Integrate with asset management
5. **Moderation**: Kick/ban users, message filtering
6. **Bots**: AI-powered chat bots using `spark.llm`
7. **Persistence**: Save message history to database schemas
8. **Notifications**: Browser notifications for new messages
## Conclusion
The IRC Webchat conversion demonstrates that even complex, interactive components can be fully defined using JSON and Lua. This approach enables:
- **Rapid iteration** without deployments
- **User customization** at the highest levels
- **Package sharing** across instances
- **Multi-tenant flexibility**
- **Security through sandboxing**
The declarative pattern can be extended to other components like forums, comments, notifications, and more.

View File

@@ -62,13 +62,20 @@ export class DeclarativeComponentRenderer {
}
})
const paramAssignments = script.parameters
.map(p => `local ${p.name} = context.data.params["${p.name}"]`)
.join('\n')
const paramList = script.parameters.map(p => p.name).join(', ')
const wrappedCode = `
${paramAssignments}
${script.code}
local fn = ...
if fn then
local args = {}
${script.parameters.map(p => `table.insert(args, context.params.${p.name})`).join('\n ')}
return fn(table.unpack(args))
local result_fn = sendMessage or handleCommand or formatTime or userJoin or userLeave or countThreads
if result_fn and type(result_fn) == "function" then
return result_fn(${paramList})
end
`
@@ -77,7 +84,7 @@ end
})
if (!result.success) {
console.error(`Lua script error (${scriptId}):`, result.error)
console.error(`Lua script error (${scriptId}):`, result.error, result.logs)
throw new Error(result.error || 'Lua script execution failed')
}