@quolu/lattice 0.50.0 → 0.50.1

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": "@quolu/lattice",
3
- "version": "0.50.0",
3
+ "version": "0.50.1",
4
4
  "description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
5
5
  "author": {
6
6
  "name": "Quo / クオ at kitepon.dev",
@@ -4,6 +4,9 @@ import { TODO_DASHBOARD_CODE_VERSION } from './todo-dashboard-registry.mjs';
4
4
 
5
5
  const LOOPBACK = '127.0.0.1';
6
6
  const POLL_MS = 500;
7
+ const SSE_HEARTBEAT_MS = 25_000;
8
+ const SSE_STALE_MS = 62_500;
9
+ const SSE_WATCHDOG_MS = 12_500;
7
10
  const HTTP_ERROR_SCHEMA = 'lattice.todo_gantt_http_error.v1';
8
11
  const PROJECT_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
9
12
 
@@ -58,7 +61,7 @@ function withExternalPane(html, pane) {
58
61
  }
59
62
 
60
63
  function liveHtml(html, headDigest, eventsPath, externalPane = null) {
61
- const controller = `<script>(()=>{const badge=document.createElement('div');badge.setAttribute('role','status');badge.style.cssText='position:fixed;right:12px;bottom:12px;z-index:99;padding:6px 10px;border:1px solid #d9d8d4;border-radius:4px;background:#fcfcfb;font:600 12px system-ui';badge.textContent='進捗: 接続中';document.body.append(badge);let head=${JSON.stringify(headDigest)};const stream=new EventSource(${JSON.stringify(eventsPath)});stream.addEventListener('state',event=>{const next=JSON.parse(event.data);badge.textContent='進捗: 最新';if(next.head_digest!==head){badge.textContent='進捗: 更新を反映中';location.reload();}});stream.addEventListener('lattice-error',event=>{const detail=JSON.parse(event.data);badge.textContent='進捗: エラー '+detail.code;badge.style.borderColor='#d03b3b';});stream.onerror=()=>{badge.textContent='進捗: 再接続中';};})();</script>`;
64
+ const controller = `<script>(()=>{const badge=document.createElement('div');badge.setAttribute('role','status');badge.style.cssText='position:fixed;right:12px;bottom:12px;z-index:99;padding:6px 10px;border:1px solid #d9d8d4;border-radius:4px;background:#fcfcfb;font:600 12px system-ui';badge.textContent='進捗: 接続中';document.body.append(badge);const head=${JSON.stringify(headDigest)};let lastReceipt=Date.now();let stream=null;const receive=event=>{const next=JSON.parse(event.data);if(typeof next.head_digest!=='string')return;lastReceipt=Date.now();badge.textContent='進捗: 最新';if(next.head_digest!==head){badge.textContent='進捗: 更新を反映中';location.reload();}};const connect=()=>{if(stream)stream.close();badge.textContent='進捗: 接続中';stream=new EventSource(${JSON.stringify(eventsPath)});stream.onopen=()=>{lastReceipt=Date.now();};stream.addEventListener('state',receive);stream.addEventListener('ping',receive);stream.addEventListener('lattice-error',event=>{lastReceipt=Date.now();const detail=JSON.parse(event.data);badge.textContent='進捗: エラー '+detail.code;badge.style.borderColor='#d03b3b';});stream.onerror=()=>{badge.textContent='進捗: 再接続中';};};connect();setInterval(()=>{if(Date.now()-lastReceipt>${SSE_STALE_MS})connect();},${SSE_WATCHDOG_MS});})();</script>`;
62
65
  const publicMetadata = '<meta name="description" content="Latticeで管理しているプロジェクトの依存工程と進捗を確認できます。"><meta name="robots" content="noindex, nofollow"><meta name="theme-color" content="#f7f3ea">';
63
66
  const publicStyle = '<style>body[data-gantt-root]{grid-template-rows:auto minmax(0,1fr)}.lattice-live-brand{z-index:10;display:flex;align-items:center;gap:8px;min-width:0;padding:10px 16px;border-bottom:1px solid #d8d0c5;background:#f7f3ea;color:#6c655d;font:600 12px/1.5 system-ui,-apple-system,"Hiragino Sans","Yu Gothic UI",sans-serif}.lattice-live-brand a{color:#201d19;text-decoration:none}.lattice-live-brand a:hover{color:#315cbe}.lattice-live-brand strong{color:#201d19}.lattice-live-brand-note{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lattice-live-back{margin-left:auto!important;color:#315cbe!important;font-weight:750}@media(max-width:560px){.lattice-live-brand{padding:9px 12px}.lattice-live-brand-note{display:none}}</style>';
64
67
  const publicHeader = '<header class="lattice-live-brand"><a href="https://kitepon.dev/">kitepon.dev</a><span aria-hidden="true">/</span><strong>Lattice</strong><span class="lattice-live-brand-note">公開工程表</span><a class="lattice-live-back" href="/projects/">一覧へ戻る</a></header>';
@@ -184,6 +187,7 @@ export async function startTodoGanttDashboardServer({ registry, port = 0, redire
184
187
  || typeof registry.list !== 'function') throw new TypeError('registry required');
185
188
  const clientsByProject = new Map();
186
189
  const lastHeads = new Map();
190
+ const lastHeartbeats = new Map();
187
191
  let eventId = 0;
188
192
  let checking = false;
189
193
  let closed = false;
@@ -194,6 +198,7 @@ export async function startTodoGanttDashboardServer({ registry, port = 0, redire
194
198
  for (const client of clients) client.end();
195
199
  clientsByProject.delete(projectId);
196
200
  lastHeads.delete(projectId);
201
+ lastHeartbeats.delete(projectId);
197
202
  }
198
203
  };
199
204
  const unsubscribe = typeof registry.subscribe === 'function'
@@ -255,6 +260,7 @@ export async function startTodoGanttDashboardServer({ registry, port = 0, redire
255
260
  try {
256
261
  const head = await project.readHead();
257
262
  lastHeads.set(project.projectId, head);
263
+ if (!lastHeartbeats.has(project.projectId)) lastHeartbeats.set(project.projectId, Date.now());
258
264
  sendEvent(response, 'state', ++eventId, { head_digest: head });
259
265
  } catch (error) {
260
266
  sendEvent(response, 'lattice-error', ++eventId, { code: error?.code ?? 'STORE_READ_FAILED' });
@@ -301,6 +307,11 @@ export async function startTodoGanttDashboardServer({ registry, port = 0, redire
301
307
  lastHeads.set(project.projectId, head);
302
308
  for (const client of clients) sendEvent(client, 'state', ++eventId, { head_digest: head });
303
309
  }
310
+ const now = Date.now();
311
+ if (now - (lastHeartbeats.get(project.projectId) ?? now) >= SSE_HEARTBEAT_MS) {
312
+ lastHeartbeats.set(project.projectId, now);
313
+ for (const client of clients) sendEvent(client, 'ping', ++eventId, { head_digest: head });
314
+ }
304
315
  } catch (error) {
305
316
  for (const client of clients) sendEvent(client, 'lattice-error', ++eventId,
306
317
  { code: error?.code ?? 'STORE_READ_FAILED' });