create-fluxstack 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/LLMD/INDEX.md +4 -3
  2. package/LLMD/resources/live-binary-delta.md +507 -0
  3. package/LLMD/resources/live-components.md +1 -0
  4. package/LLMD/resources/live-rooms.md +731 -333
  5. package/app/client/.live-stubs/LivePingPong.js +10 -0
  6. package/app/client/.live-stubs/LiveRoomChat.js +3 -2
  7. package/app/client/.live-stubs/LiveSharedCounter.js +10 -0
  8. package/app/client/src/App.tsx +15 -14
  9. package/app/client/src/components/AppLayout.tsx +4 -4
  10. package/app/client/src/live/PingPongDemo.tsx +199 -0
  11. package/app/client/src/live/RoomChatDemo.tsx +187 -22
  12. package/app/client/src/live/SharedCounterDemo.tsx +142 -0
  13. package/app/server/live/LivePingPong.ts +61 -0
  14. package/app/server/live/LiveRoomChat.ts +106 -38
  15. package/app/server/live/LiveSharedCounter.ts +73 -0
  16. package/app/server/live/rooms/ChatRoom.ts +68 -0
  17. package/app/server/live/rooms/CounterRoom.ts +51 -0
  18. package/app/server/live/rooms/DirectoryRoom.ts +42 -0
  19. package/app/server/live/rooms/PingRoom.ts +40 -0
  20. package/core/build/live-components-generator.ts +10 -1
  21. package/core/client/index.ts +0 -16
  22. package/core/server/live/auto-generated-components.ts +3 -9
  23. package/core/server/live/index.ts +0 -5
  24. package/core/server/live/websocket-plugin.ts +37 -2
  25. package/core/utils/version.ts +1 -1
  26. package/package.json +100 -99
  27. package/tsconfig.json +4 -1
  28. package/app/client/.live-stubs/LiveChat.js +0 -7
  29. package/app/client/.live-stubs/LiveTodoList.js +0 -9
  30. package/app/client/src/live/ChatDemo.tsx +0 -107
  31. package/app/client/src/live/LiveDebuggerPanel.tsx +0 -779
  32. package/app/client/src/live/TodoListDemo.tsx +0 -158
  33. package/app/server/live/LiveChat.ts +0 -78
  34. package/app/server/live/LiveTodoList.ts +0 -110
  35. package/core/client/components/LiveDebugger.tsx +0 -1324
@@ -1,158 +0,0 @@
1
- // TodoListDemo - Lista de tarefas colaborativa em tempo real
2
-
3
- import { useState } from 'react'
4
- import { Live } from '@/core/client'
5
- import { LiveTodoList } from '@server/live/LiveTodoList'
6
-
7
- export function TodoListDemo() {
8
- const [text, setText] = useState('')
9
-
10
- const todoList = Live.use(LiveTodoList, {
11
- room: 'global-todos'
12
- })
13
-
14
- const handleAdd = async () => {
15
- if (!text.trim()) return
16
- await todoList.addTodo({ text })
17
- setText('')
18
- }
19
-
20
- const todos = todoList.$state.todos ?? []
21
- const doneCount = todos.filter((t: any) => t.done).length
22
- const pendingCount = todos.length - doneCount
23
-
24
- return (
25
- <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-5 sm:p-8 max-w-lg w-full mx-auto">
26
- <h2 className="text-xl sm:text-2xl font-bold text-white mb-2 text-center">
27
- Todo List Colaborativo
28
- </h2>
29
-
30
- <p className="text-gray-400 text-xs sm:text-sm text-center mb-4">
31
- Abra em várias abas - todos compartilham a mesma lista!
32
- </p>
33
-
34
- {/* Status bar */}
35
- <div className="flex flex-wrap justify-center gap-2 mb-6">
36
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full text-xs ${
37
- todoList.$connected
38
- ? 'bg-emerald-500/20 text-emerald-300'
39
- : 'bg-red-500/20 text-red-300'
40
- }`}>
41
- <div className={`w-2 h-2 rounded-full ${
42
- todoList.$connected ? 'bg-emerald-400' : 'bg-red-400'
43
- }`} />
44
- {todoList.$connected ? 'Conectado' : 'Desconectado'}
45
- </div>
46
-
47
- <div className="flex items-center gap-2 px-3 py-1 rounded-full text-xs bg-blue-500/20 text-blue-300">
48
- {todoList.$state.connectedUsers} online
49
- </div>
50
-
51
- <div className="flex items-center gap-2 px-3 py-1 rounded-full text-xs bg-purple-500/20 text-purple-300">
52
- {todoList.$state.totalCreated} criadas
53
- </div>
54
- </div>
55
-
56
- {/* Input */}
57
- <div className="flex gap-2 mb-6">
58
- <input
59
- type="text"
60
- value={text}
61
- onChange={(e) => setText(e.target.value)}
62
- onKeyDown={(e) => e.key === 'Enter' && handleAdd()}
63
- placeholder="Nova tarefa..."
64
- className="flex-1 bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-purple-500/50 transition-colors"
65
- />
66
- <button
67
- onClick={handleAdd}
68
- disabled={!todoList.$connected || !text.trim()}
69
- className="px-5 py-3 bg-purple-500/20 hover:bg-purple-500/30 border border-purple-500/30 text-purple-300 rounded-xl transition-all disabled:opacity-50 font-medium"
70
- >
71
- +
72
- </button>
73
- </div>
74
-
75
- {/* Stats */}
76
- {todos.length > 0 && (
77
- <div className="flex justify-between items-center mb-4 text-xs text-gray-500">
78
- <span>{pendingCount} pendente(s) / {doneCount} feita(s)</span>
79
- {doneCount > 0 && (
80
- <button
81
- onClick={() => todoList.clearCompleted()}
82
- className="text-red-400 hover:text-red-300 transition-colors"
83
- >
84
- Limpar feitas
85
- </button>
86
- )}
87
- </div>
88
- )}
89
-
90
- {/* Todo list */}
91
- <div className="space-y-2 max-h-80 overflow-y-auto">
92
- {todos.length === 0 ? (
93
- <p className="text-gray-500 text-sm text-center py-8">
94
- Nenhuma tarefa ainda. Adicione uma acima!
95
- </p>
96
- ) : (
97
- todos.map((todo: any) => (
98
- <div
99
- key={todo.id}
100
- className={`flex items-center gap-3 p-3 rounded-xl border transition-all ${
101
- todo.done
102
- ? 'bg-emerald-500/5 border-emerald-500/20'
103
- : 'bg-white/5 border-white/10'
104
- }`}
105
- >
106
- <button
107
- onClick={() => todoList.toggleTodo({ id: todo.id })}
108
- className={`w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all flex-shrink-0 ${
109
- todo.done
110
- ? 'bg-emerald-500 border-emerald-500 text-white'
111
- : 'border-gray-500 hover:border-purple-400'
112
- }`}
113
- >
114
- {todo.done && (
115
- <svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
116
- <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
117
- </svg>
118
- )}
119
- </button>
120
-
121
- <span className={`flex-1 text-sm ${
122
- todo.done ? 'text-gray-500 line-through' : 'text-white'
123
- }`}>
124
- {todo.text}
125
- </span>
126
-
127
- <span className="text-[10px] text-gray-600 flex-shrink-0">
128
- {todo.createdBy}
129
- </span>
130
-
131
- <button
132
- onClick={() => todoList.removeTodo({ id: todo.id })}
133
- className="text-gray-600 hover:text-red-400 transition-colors flex-shrink-0"
134
- >
135
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
136
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
137
- </svg>
138
- </button>
139
- </div>
140
- ))
141
- )}
142
- </div>
143
-
144
- {/* Loading indicator */}
145
- {todoList.$loading && (
146
- <div className="flex justify-center mt-4">
147
- <div className="w-5 h-5 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
148
- </div>
149
- )}
150
-
151
- <div className="mt-6 pt-4 border-t border-white/10">
152
- <p className="text-gray-500 text-xs text-center">
153
- Usando <code className="text-purple-400">Live.use()</code> + <code className="text-purple-400">Room Events</code>
154
- </p>
155
- </div>
156
- </div>
157
- )
158
- }
@@ -1,78 +0,0 @@
1
- // LiveChat - Chat compartilhado por sala
2
-
3
- import { LiveComponent, type FluxStackWebSocket } from '@core/types/types'
4
-
5
- // Componente Cliente (Ctrl+Click para navegar)
6
- import type { ChatDemo as _Client } from '@client/src/live/ChatDemo'
7
-
8
- export type ChatMessage = {
9
- id: string
10
- user: string
11
- text: string
12
- timestamp: number
13
- }
14
-
15
- export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
16
- static componentName = 'LiveChat'
17
- static publicActions = ['sendMessage'] as const
18
- static defaultState = {
19
- messages: [] as ChatMessage[]
20
- }
21
- protected roomType = 'chat'
22
- private maxMessages = 50
23
- private static roomHistory = new Map<string, ChatMessage[]>()
24
-
25
- constructor(initialState: Partial<typeof LiveChat.defaultState> = {}, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) {
26
- super(initialState, ws, options)
27
-
28
- this.onRoomEvent<ChatMessage>('NEW_MESSAGE', (message) => {
29
- this.addMessage(message)
30
- })
31
-
32
- if (this.room) {
33
- const history = LiveChat.roomHistory.get(this.room) || []
34
- if (history.length > 0) {
35
- this.setState({ messages: history })
36
- }
37
- }
38
- }
39
-
40
- private addMessage(message: ChatMessage) {
41
- const next = [...this.state.messages, message].slice(-this.maxMessages)
42
- if (this.room) {
43
- LiveChat.roomHistory.set(this.room, next)
44
- }
45
- this.setState({ messages: next })
46
- }
47
-
48
- async sendMessage(payload: { user: string; text: string }) {
49
- const text = payload.text?.trim()
50
- const user = payload.user?.trim() || 'anonymous'
51
-
52
- if (!text) {
53
- throw new Error('Message cannot be empty')
54
- }
55
-
56
- if (text.length > 500) {
57
- throw new Error('Message too long')
58
- }
59
-
60
- const message: ChatMessage = {
61
- id: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
62
- user,
63
- text,
64
- timestamp: Date.now()
65
- }
66
-
67
- const next = [...this.state.messages, message].slice(-this.maxMessages)
68
- if (this.room) {
69
- LiveChat.roomHistory.set(this.room, next)
70
- }
71
-
72
- this.emitRoomEventWithState('NEW_MESSAGE', message, {
73
- messages: next
74
- })
75
-
76
- return { success: true }
77
- }
78
- }
@@ -1,110 +0,0 @@
1
- // LiveTodoList - Lista de tarefas colaborativa em tempo real
2
- // Testa: state mutations, room events, multiple actions, arrays no state
3
-
4
- import { LiveComponent, type FluxStackWebSocket } from '@core/types/types'
5
-
6
- // Componente Cliente (Ctrl+Click para navegar)
7
- import type { TodoListDemo as _Client } from '@client/src/live/TodoListDemo'
8
-
9
- interface TodoItem {
10
- id: string
11
- text: string
12
- done: boolean
13
- createdBy: string
14
- createdAt: number
15
- }
16
-
17
- export class LiveTodoList extends LiveComponent<typeof LiveTodoList.defaultState> {
18
- static componentName = 'LiveTodoList'
19
- static publicActions = ['addTodo', 'toggleTodo', 'removeTodo', 'clearCompleted'] as const
20
- static defaultState = {
21
- todos: [] as TodoItem[],
22
- totalCreated: 0,
23
- connectedUsers: 0
24
- }
25
- protected roomType = 'todo'
26
-
27
- constructor(initialState: Partial<typeof LiveTodoList.defaultState> = {}, ws: FluxStackWebSocket, options?: { room?: string; userId?: string }) {
28
- super(initialState, ws, options)
29
-
30
- this.onRoomEvent<{ todos: TodoItem[]; totalCreated: number }>('TODOS_CHANGED', (data) => {
31
- this.setState({ todos: data.todos, totalCreated: data.totalCreated })
32
- })
33
-
34
- this.onRoomEvent<{ connectedUsers: number }>('USER_COUNT_CHANGED', (data) => {
35
- this.setState({ connectedUsers: data.connectedUsers })
36
- })
37
-
38
- const newCount = this.state.connectedUsers + 1
39
- this.emitRoomEventWithState('USER_COUNT_CHANGED', { connectedUsers: newCount }, { connectedUsers: newCount })
40
- }
41
-
42
- async addTodo(payload: { text: string }) {
43
- if (!payload.text?.trim()) {
44
- return { success: false, error: 'Text is required' }
45
- }
46
-
47
- const todo: TodoItem = {
48
- id: `todo-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
49
- text: payload.text.trim(),
50
- done: false,
51
- createdBy: this.userId || 'anonymous',
52
- createdAt: Date.now()
53
- }
54
-
55
- const nextTodos = [...this.state.todos, todo]
56
- const nextTotal = this.state.totalCreated + 1
57
-
58
- this.emitRoomEventWithState(
59
- 'TODOS_CHANGED',
60
- { todos: nextTodos, totalCreated: nextTotal },
61
- { todos: nextTodos, totalCreated: nextTotal }
62
- )
63
-
64
- return { success: true, todo }
65
- }
66
-
67
- async toggleTodo(payload: { id: string }) {
68
- const nextTodos = this.state.todos.map(t =>
69
- t.id === payload.id ? { ...t, done: !t.done } : t
70
- )
71
-
72
- this.emitRoomEventWithState(
73
- 'TODOS_CHANGED',
74
- { todos: nextTodos, totalCreated: this.state.totalCreated },
75
- { todos: nextTodos }
76
- )
77
-
78
- return { success: true }
79
- }
80
-
81
- async removeTodo(payload: { id: string }) {
82
- const nextTodos = this.state.todos.filter(t => t.id !== payload.id)
83
-
84
- this.emitRoomEventWithState(
85
- 'TODOS_CHANGED',
86
- { todos: nextTodos, totalCreated: this.state.totalCreated },
87
- { todos: nextTodos }
88
- )
89
-
90
- return { success: true }
91
- }
92
-
93
- async clearCompleted() {
94
- const nextTodos = this.state.todos.filter(t => !t.done)
95
-
96
- this.emitRoomEventWithState(
97
- 'TODOS_CHANGED',
98
- { todos: nextTodos, totalCreated: this.state.totalCreated },
99
- { todos: nextTodos }
100
- )
101
-
102
- return { success: true, removed: this.state.todos.length - nextTodos.length }
103
- }
104
-
105
- destroy() {
106
- const newCount = Math.max(0, this.state.connectedUsers - 1)
107
- this.emitRoomEvent('USER_COUNT_CHANGED', { connectedUsers: newCount })
108
- super.destroy()
109
- }
110
- }