@chatpanel/events 0.97.0 → 0.98.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.
- package/package.json +1 -1
- package/team-board.js +24 -2
- package/team-record.js +1 -1
- package/team-trail.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/events",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.98.0",
|
|
4
4
|
"description": "The canonical ChatPanel event-log and capability contracts — typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/team-board.js
CHANGED
|
@@ -104,6 +104,7 @@ export function foldBoard(state, ev) {
|
|
|
104
104
|
if (type === 'board.thread' && p.thread?.id) {
|
|
105
105
|
if (!s.threads.some((t) => t.id === p.thread.id)) s.threads.push({ ...p.thread });
|
|
106
106
|
} else if (type === 'board.post' && p.post?.id) {
|
|
107
|
+
if (Array.isArray(s.removed) && s.removed.includes(p.post.threadId)) return s;
|
|
107
108
|
if (!s.posts.some((x) => x.id === p.post.id)) s.posts.push({ ...p.post });
|
|
108
109
|
const t = s.threads.find((x) => x.id === p.post.threadId);
|
|
109
110
|
if (t) { t.lastAt = p.post.at; t.lastBy = p.post.by; t.posts = (t.posts || 0) + 1; }
|
|
@@ -113,6 +114,13 @@ export function foldBoard(state, ev) {
|
|
|
113
114
|
} else if (type === 'board.thread-status' && p.threadId) {
|
|
114
115
|
const t = s.threads.find((x) => x.id === p.threadId);
|
|
115
116
|
if (t) { t.status = p.status; if (p.status !== 'waiting') t.waitingOn = null; if (p.holder) t.holder = p.holder; }
|
|
117
|
+
} else if (type === 'board.thread-removed' && p.threadId) {
|
|
118
|
+
// A person took the thread off the board: it and its posts go, and its id is kept so a
|
|
119
|
+
// post a member still makes in it (its runner did not see the removal) lands nowhere.
|
|
120
|
+
s.threads = s.threads.filter((x) => x.id !== p.threadId);
|
|
121
|
+
s.posts = s.posts.filter((x) => x.threadId !== p.threadId);
|
|
122
|
+
if (!Array.isArray(s.removed)) s.removed = [];
|
|
123
|
+
if (!s.removed.includes(p.threadId)) s.removed.push(p.threadId);
|
|
116
124
|
}
|
|
117
125
|
return s;
|
|
118
126
|
}
|
|
@@ -123,7 +131,7 @@ export function foldBoard(state, ev) {
|
|
|
123
131
|
* stays: a finding is a post of kind `finding` in its task's thread.
|
|
124
132
|
*/
|
|
125
133
|
export function createBoard({ now = () => Date.now(), newId = null, state = null, onEvent = null } = {}) {
|
|
126
|
-
const st = state && Array.isArray(state.threads) ? { threads: state.threads.map((t) => ({ ...t })), posts: (state.posts || []).map((x) => ({ ...x })) } : emptyBoardState();
|
|
134
|
+
const st = state && Array.isArray(state.threads) ? { threads: state.threads.map((t) => ({ ...t })), posts: (state.posts || []).map((x) => ({ ...x })), ...(Array.isArray(state.removed) && state.removed.length ? { removed: [...state.removed] } : {}) } : emptyBoardState();
|
|
127
135
|
const listeners = new Set();
|
|
128
136
|
let seq = st.posts.length + st.threads.length;
|
|
129
137
|
const mk = (prefix) => (newId ? newId(prefix) : `${prefix}_${(++seq).toString(36)}${Math.random().toString(36).slice(2, 6)}`);
|
|
@@ -181,6 +189,20 @@ export function createBoard({ now = () => Date.now(), newId = null, state = null
|
|
|
181
189
|
say('board.thread-status', { threadId, status, ...extra });
|
|
182
190
|
return t;
|
|
183
191
|
},
|
|
192
|
+
/**
|
|
193
|
+
* A person takes a thread off the board (from either client — the store appends the
|
|
194
|
+
* event, the running client's tail applies it here). Its posts go with it; a member's
|
|
195
|
+
* later post in it lands nowhere. A waiting ask is not removed: answer it or stop the run.
|
|
196
|
+
*/
|
|
197
|
+
removeThread(threadId, { by = PERSON } = {}) {
|
|
198
|
+
const t = threadOf(threadId);
|
|
199
|
+
if (!t || (t.kind === 'ask' && t.status === 'waiting')) return null;
|
|
200
|
+
st.threads = st.threads.filter((x) => x.id !== threadId);
|
|
201
|
+
st.posts = st.posts.filter((x) => x.threadId !== threadId);
|
|
202
|
+
(st.removed ||= []).push(threadId);
|
|
203
|
+
say('board.thread-removed', { threadId, by });
|
|
204
|
+
return t;
|
|
205
|
+
},
|
|
184
206
|
/**
|
|
185
207
|
* A member is stuck: open an ask thread (status `waiting`) with the question as its
|
|
186
208
|
* first post. The runner waits on it; a person answers from either client.
|
|
@@ -208,7 +230,7 @@ export function createBoard({ now = () => Date.now(), newId = null, state = null
|
|
|
208
230
|
/** Every ask still waiting — what a client pins at the top. */
|
|
209
231
|
waiting: () => st.threads.filter((t) => t.kind === 'ask' && t.status === 'waiting').map((t) => ({ ...t })),
|
|
210
232
|
/** The whole board, for the run record and for a resume. */
|
|
211
|
-
state: () => ({ threads: st.threads.map((t) => ({ ...t })), posts: st.posts.map((x) => ({ ...x })) }),
|
|
233
|
+
state: () => ({ threads: st.threads.map((t) => ({ ...t })), posts: st.posts.map((x) => ({ ...x })), ...(st.removed?.length ? { removed: [...st.removed] } : {}) }),
|
|
212
234
|
|
|
213
235
|
// ── findings, as before: a finding is a post in its task's thread ──
|
|
214
236
|
add(list, { by = null } = {}) {
|
package/team-record.js
CHANGED
|
@@ -99,7 +99,7 @@ export function foldRun(run, ev) {
|
|
|
99
99
|
if (p.checkpoint) run.checkpoint = p.checkpoint;
|
|
100
100
|
break;
|
|
101
101
|
case 'run.stop-requested': run.stopRequested = at; break;
|
|
102
|
-
case 'board.thread': case 'board.post': case 'board.decision': case 'board.thread-status':
|
|
102
|
+
case 'board.thread': case 'board.post': case 'board.decision': case 'board.thread-status': case 'board.thread-removed':
|
|
103
103
|
run.threads = foldBoard(run.threads || emptyBoardState(), ev);
|
|
104
104
|
if (type === 'board.thread-status' && p.status !== 'waiting' && run.status === 'waiting' && !(run.threads.threads || []).some((t) => t.kind === 'ask' && t.status === 'waiting')) run.status = 'answered';
|
|
105
105
|
break;
|
package/team-trail.js
CHANGED
|
@@ -16,6 +16,7 @@ export function teamLine(ev) {
|
|
|
16
16
|
case 'run.resumed': return { type: 'status', text: `team ${ev.team} resumed${(ev.carried || []).length ? ` (${ev.carried.length} task${ev.carried.length === 1 ? '' : 's'} carried over)` : ''}` };
|
|
17
17
|
case 'board.post': return ev.post && ev.post.kind !== 'finding' ? { type: 'status', text: `${ev.post.by} ${ev.post.replyTo ? 'replied' : 'posted'} (${ev.post.kind}): ${String(ev.post.text || '').slice(0, 120)}` } : null;
|
|
18
18
|
case 'board.decision': return { type: 'status', text: `${ev.by || 'someone'} ${ev.status} a post` };
|
|
19
|
+
case 'board.thread-removed': return { type: 'status', text: `${ev.by || 'someone'} removed a thread from the board` };
|
|
19
20
|
case 'task.note': return { type: 'status', text: `${role}: ${ev.text}` };
|
|
20
21
|
case 'task.handoff': return { type: 'status', text: `${role} handed off ${ev.from ? `from ${ev.from} ` : ''}to ${ev.to} by ${ev.by || 'person'}${ev.reason ? ` — ${ev.reason}` : ''}` };
|
|
21
22
|
case 'task.step': return null;
|
|
@@ -63,6 +64,7 @@ export function teamLanes(prev, ev) {
|
|
|
63
64
|
case 'task.waiting': if (lanes.tasks[ev.taskId]) lanes.tasks[ev.taskId] = { ...lanes.tasks[ev.taskId], status: 'waiting', waitingOn: ev.threadId }; lanes.waiting = [...(lanes.waiting || []), ev.threadId]; break;
|
|
64
65
|
case 'task.done': case 'task.failed': if (lanes.tasks[ev.taskId]) lanes.tasks[ev.taskId] = { ...lanes.tasks[ev.taskId], status: ev.status || 'ok', ms: ev.ms }; break;
|
|
65
66
|
case 'board.thread-status': if (ev.status !== 'waiting' && lanes.waiting) lanes.waiting = lanes.waiting.filter((x) => x !== ev.threadId); break;
|
|
67
|
+
case 'board.thread-removed': if (lanes.waiting) lanes.waiting = lanes.waiting.filter((x) => x !== ev.threadId); break;
|
|
66
68
|
case 'run.waiting': lanes.waiting = [...(lanes.waiting || []), ev.threadId]; break;
|
|
67
69
|
case 'run.done': lanes.status = ev.status; lanes.usage = ev.usage; break;
|
|
68
70
|
default: break;
|