agentdev-webui 1.1.1 → 1.1.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentdev-webui",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Multi-agent workflow dashboard for auto-ticket processing",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/js/app.js CHANGED
@@ -32,7 +32,8 @@ let projectsList = [];
32
32
  let lastAgentsList = [];
33
33
  let lastHistoryList = [];
34
34
  let lastTodosList = [];
35
- const optimisticTickets = new Map(); // "repo:number" { ticket, created: Date.now() }
35
+ // Track optimistic tickets that were just created but may not be in SSE data yet
36
+ const pendingOptimisticTickets = new Map(); // "repo:number" → { ticket, created }
36
37
 
37
38
  let firstProjectMode = false;
38
39
 
@@ -1019,19 +1020,24 @@ async function createTicket() {
1019
1020
  formStatus.innerHTML = 'Ticket <a href="' + data.url + '" target="_blank">#' + data.number + '</a> created and added to project!';
1020
1021
  formStatus.className = 'form-status visible success';
1021
1022
 
1022
- // Optimistic update: track separately so SSE broadcasts don't wipe it
1023
- const ticketRepo = data.repo || repo;
1024
- optimisticTickets.set(ticketRepo + ':' + data.number, {
1025
- ticket: {
1026
- number: data.number,
1027
- repo: ticketRepo,
1028
- title: title,
1029
- status: 'Todo',
1030
- hasClaude: true,
1031
- project_id: currentProjectId
1032
- },
1033
- created: Date.now()
1034
- });
1023
+ // Optimistic injection: add ticket to board immediately so it doesn't
1024
+ // disappear when an SSE broadcast arrives before GitHub indexes it
1025
+ const optimisticTicket = {
1026
+ number: data.number,
1027
+ repo: data.repo || repo,
1028
+ title: title,
1029
+ body: body,
1030
+ state: 'OPEN',
1031
+ status: 'Todo',
1032
+ hasClaude: body.toLowerCase().includes('@claude'),
1033
+ project_id: currentProjectId || null,
1034
+ author: '',
1035
+ createdAt: new Date().toISOString(),
1036
+ comments: []
1037
+ };
1038
+ const optKey = (data.repo || repo) + ':' + data.number;
1039
+ pendingOptimisticTickets.set(optKey, { ticket: optimisticTicket, created: Date.now() });
1040
+ lastTodosList.push(optimisticTicket);
1035
1041
  updateTodoTickets(lastTodosList);
1036
1042
 
1037
1043
  ticketDescription.value = '';
@@ -1504,18 +1510,16 @@ const STATUS_COLORS = { 'Todo': '#4ade80', 'In Progress': '#fbbf24', 'test': '#6
1504
1510
  const STATUS_ORDER = ['Todo', 'In Progress', 'test', 'Done'];
1505
1511
 
1506
1512
  function updateTodoTickets(list) {
1507
- // Merge optimistic tickets: remove confirmed, expire old, add remaining
1508
- for (const [key] of optimisticTickets) {
1513
+ // Merge pending optimistic tickets into SSE data
1514
+ for (const [key, entry] of pendingOptimisticTickets) {
1509
1515
  const [repo, num] = key.split(':');
1510
- if (list.some(t => t.repo === repo && t.number === parseInt(num))) {
1511
- optimisticTickets.delete(key);
1512
- }
1513
- }
1514
- for (const [key, entry] of optimisticTickets) {
1515
- if (Date.now() - entry.created > 60000) {
1516
- optimisticTickets.delete(key);
1516
+ const numInt = parseInt(num);
1517
+ if (list.some(t => t.repo === repo && t.number === numInt)) {
1518
+ pendingOptimisticTickets.delete(key); // Confirmed by server
1519
+ } else if (Date.now() - entry.created > 60000) {
1520
+ pendingOptimisticTickets.delete(key); // Expired after 60s
1517
1521
  } else {
1518
- list.push(entry.ticket);
1522
+ list.push(entry.ticket); // Keep optimistic ticket visible
1519
1523
  }
1520
1524
  }
1521
1525
 
package/server.js CHANGED
@@ -273,6 +273,10 @@ async function resolveProjectToken(project) {
273
273
  return process.env.GH_TOKEN || process.env.GITHUB_DEFAULT_TOKEN || null;
274
274
  }
275
275
 
276
+ // Track recently created tickets server-side so SSE broadcasts include them
277
+ // even before GitHub indexes them (avoids optimistic update flicker)
278
+ const recentlyCreatedTickets = new Map(); // "repo:number" → { ticket, created }
279
+
276
280
  async function broadcastTodoTickets() {
277
281
  try {
278
282
  let allTickets = [];
@@ -305,6 +309,19 @@ async function broadcastTodoTickets() {
305
309
  allTickets = await github.fetchProjectTickets();
306
310
  }
307
311
 
312
+ // Merge recently created tickets that GitHub hasn't indexed yet
313
+ for (const [key, entry] of recentlyCreatedTickets) {
314
+ const [repo, num] = key.split(':');
315
+ const numInt = parseInt(num);
316
+ if (allTickets.some(t => t.repo === repo && t.number === numInt)) {
317
+ recentlyCreatedTickets.delete(key); // Confirmed by GitHub
318
+ } else if (Date.now() - entry.created > 60000) {
319
+ recentlyCreatedTickets.delete(key); // Expired after 60s
320
+ } else {
321
+ allTickets.push(entry.ticket); // Add to broadcast
322
+ }
323
+ }
324
+
308
325
  broadcast({ type: 'todos', list: allTickets });
309
326
  } catch (error) {
310
327
  console.error('Error broadcasting project tickets:', error.message);
@@ -1161,6 +1178,25 @@ http.createServer(async (req, res) => {
1161
1178
  console.log('Ticket #' + issue.number + ' added to database');
1162
1179
  }
1163
1180
 
1181
+ // Track the newly created ticket so SSE broadcasts include it
1182
+ // even before GitHub indexes it
1183
+ recentlyCreatedTickets.set(repo + ':' + issue.number, {
1184
+ ticket: {
1185
+ number: issue.number,
1186
+ repo: repo,
1187
+ title: title,
1188
+ body: ticketBody,
1189
+ state: 'OPEN',
1190
+ status: 'Todo',
1191
+ hasClaude: ticketBody.includes('@claude'),
1192
+ project_id: projectId,
1193
+ author: user?.username || 'unknown',
1194
+ createdAt: new Date().toISOString(),
1195
+ comments: []
1196
+ },
1197
+ created: Date.now()
1198
+ });
1199
+
1164
1200
  // Clear cache and broadcast updated board immediately
1165
1201
  github.clearTicketCache();
1166
1202
  broadcastTodoTickets();