@quolu/lattice 0.12.17 → 0.12.19

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/README.md CHANGED
@@ -89,8 +89,16 @@ dashboardはmanifestのfile identityが変わらない間のstable store readを
89
89
  長時間の外部処理中にCLI呼出しが途切れても進行中projectを休眠扱いしません。
90
90
  LANや外部reverse proxyから閲覧するoptional bridgeは既定で無効です。明示したIPにだけbindする初回設定、
91
91
  再設定、停止方法は[bridge setup](docs/bridge-setup.md)を参照してください。
92
+ 工程図の既定表示は、後続に作業中・未着手が残っていない完了工程を図から除きます。まとめnodeも置かないため、
93
+ 完走したplanは図の場所を取りません。除いた工程は凡例の件数、右ペインの「全工程」一覧、各工程の詳細から
94
+ 辿れ、詳細の前提・後続は除外前の依存関係を示します。総数・進捗・最長依存鎖は除外前の全工程で数えます。
95
+ 凡例の件数バッジを押すと全工程を描いた図へ切り替わり、`lattice todo gantt --scope all`は最初から全件を
96
+ 描きます。表示規約は[ADR 0066](docs/adr/0066-gantt-live-scope-drops-finished-work.md)が正です。
97
+
92
98
  静的工程表は`lattice todo gantt status`で`current / stale / missing`を確認でき、HTMLまたは
93
99
  digest付きsidecarの欠落・改ざんはtyped failureになります。
100
+ dashboard daemonは起動時に読み込んだ版数をhealthで名乗り、installされた版と食い違えば`lattice status`の
101
+ たびに新版daemonへ置き換わります。publishしただけで配信面が古いまま残ることはありません。
94
102
  状態を書き込む`start / block / unblock / done / evidence promote / reopen / revise / revise-phase / revise-set`
95
103
  では、監査actorとして次の3環境変数をすべて設定してください。
96
104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.12.17",
3
+ "version": "0.12.19",
4
4
  "description": "Lattice — phase-aware TODO graph compiler and conflict-aware orchestration runtime",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/cli-help.mjs CHANGED
@@ -45,7 +45,7 @@ Read commands:
45
45
  status [--json]
46
46
  verify [--plan <key>] [--json]
47
47
  snapshot --rebuild --plan <key>
48
- gantt [--out <file>] [--scope live|all] # 既定live: 完走した枝を畳む
48
+ gantt [--out <file>] [--scope live|all] # 既定live: 完走した工程を図から除く(一覧には残る)
49
49
  gantt status [--out <file>]
50
50
  gantt serve --port <port> [--scope live|all] # /projects/<project_id>/ をforeground配信
51
51
  phase status --plan <key>
@@ -1,12 +1,9 @@
1
1
  import { createHash } from 'node:crypto';
2
2
 
3
- import {
4
- renderTodoMarkdownDocument,
5
- serializeJsonForScript,
6
- } from './todo-markdown-renderer.mjs';
3
+ import { serializeJsonForScript } from './todo-markdown-renderer.mjs';
7
4
  import { renderTodoGanttSvg, TODO_GANTT_STATUS_PRESENTATION } from './todo-gantt-svg.mjs';
8
5
 
9
- export const TODO_GANTT_RENDERER_VERSION = 'lattice.todo_gantt_renderer.v14';
6
+ export const TODO_GANTT_RENDERER_VERSION = 'lattice.todo_gantt_renderer.v15';
10
7
  export const TODO_GANTT_PROSE_MAX_BYTES = 8 * 1024 * 1024;
11
8
  export const TODO_GANTT_HTML_MAX_BYTES = 24 * 1024 * 1024;
12
9
 
@@ -50,18 +47,21 @@ function digest(value) {
50
47
  return createHash('sha256').update(value, 'utf8').digest('hex');
51
48
  }
52
49
 
53
- // The Markdown renderer deliberately permits safe web links for its standalone API.
54
- // A self-contained gantt permits fragments only, so its known allow-list output is de-linked here.
55
- function removeNavigation(markup) {
56
- return markup.replace(/<a href="[^"]*"(?: title="[^"]*")?>/gu, '<span class="deactivated-link">')
57
- .replaceAll('</a>', '</span>');
58
- }
59
-
50
+ /**
51
+ * Sections carry one row per ToDo, plus the prose accounting for the narratives
52
+ * behind them.
53
+ *
54
+ * The right pane shows the store, not the Markdown: per the 2026-07-19 ruling
55
+ * the "全工程" list is the plan rendered from the ToDo store, and the original
56
+ * documents are reached through each ToDo's source reference. The narratives are
57
+ * still read, because anchor verification needs them, and their size is still
58
+ * bounded — but they are not rendered into the page.
59
+ */
60
60
  function normalizeSections(readModel, narratives, anchorOutcomes) {
61
61
  const supplied = new Map(narratives.map((entry) => [refKey(entry.ref), entry]));
62
62
  const outcomes = new Map(anchorOutcomes.map((entry) => [refKey(entry.ref), entry]));
63
63
  const result = [];
64
- const documents = new Map();
64
+ const counted = new Set();
65
65
  let proseBytes = 0;
66
66
  for (const member of readModel.members) {
67
67
  const states = new Map(member.tasks.map((state) => [state.task_id, state]));
@@ -70,57 +70,23 @@ function normalizeSections(readModel, narratives, anchorOutcomes) {
70
70
  const narrative = supplied.get(refKey(ref));
71
71
  const markdown = narrative?.markdown ?? '';
72
72
  const narrativeRef = narrative?.narrative_ref ?? task.narrative_ref;
73
+ // ToDos that share one narrative document count its bytes once.
73
74
  const documentKey = narrativeRef === null
74
75
  ? refKey(ref) : JSON.stringify([member.plan.plan_key, narrativeRef, digest(markdown)]);
75
- let document = documents.get(documentKey);
76
- if (document === undefined) {
76
+ if (!counted.has(documentKey)) {
77
+ counted.add(documentKey);
77
78
  proseBytes += Buffer.byteLength(markdown, 'utf8');
78
79
  if (proseBytes > TODO_GANTT_PROSE_MAX_BYTES) {
79
80
  throw new TodoGanttRenderError('TODO_SCALE_EXCEEDED', 'todo gantt embedded prose limit exceeded', {
80
81
  prose_bytes: proseBytes, prose_limit: TODO_GANTT_PROSE_MAX_BYTES,
81
82
  });
82
83
  }
83
- document = { markdown, narrativeRef, tasks: [], rendered: '' };
84
- documents.set(documentKey, document);
85
84
  }
86
85
  const anchorOutcome = outcomes.get(refKey(ref)) ?? {
87
86
  ref, narrative_ref: narrativeRef, anchored: false, reason: 'anchor_missing',
88
87
  origin_line: task.narrative_anchor?.origin_line ?? null,
89
88
  };
90
- const section = {
91
- ref, task, state: states.get(task.task_id), documentKey, narrativeRef, anchorOutcome, document,
92
- };
93
- document.tasks.push(section);
94
- result.push(section);
95
- }
96
- }
97
- for (const document of documents.values()) {
98
- const taskStatesByLine = new Map();
99
- for (const section of document.tasks) {
100
- if (!section.anchorOutcome.anchored) continue;
101
- const status = DOCUMENT_STATUS[section.state.status] ?? { mark: '?', label: '状態不明' };
102
- taskStatesByLine.set(section.anchorOutcome.origin_line, {
103
- status: section.state.status,
104
- mark: status.mark,
105
- label: status.label,
106
- narrativeKey: refKey(section.ref),
107
- blockedReason: section.state.blocked_reason,
108
- });
109
- }
110
- try {
111
- document.rendered = removeNavigation(renderTodoMarkdownDocument(
112
- document.markdown,
113
- { taskStatesByLine },
114
- ).html);
115
- } catch (error) {
116
- if (error?.code === 'TODO_MARKDOWN_SECTION_TOO_LARGE') {
117
- throw new TodoGanttRenderError('TODO_SCALE_EXCEEDED', 'todo gantt narrative section limit exceeded', {
118
- narrative_ref: document.narrativeRef,
119
- prose_section_bytes: error.detail.actual_bytes,
120
- prose_section_limit: error.detail.maximum_bytes,
121
- });
122
- }
123
- throw error;
89
+ result.push({ ref, task, state: states.get(task.task_id), narrativeRef, anchorOutcome });
124
90
  }
125
91
  }
126
92
  return { sections: result, proseBytes };
@@ -282,7 +248,7 @@ function renderRightPane(sections, layout, presentation, readModel) {
282
248
  return `<article class="task-detail" data-detail-key="${escapeHtmlAttribute(key)}" hidden><header><span class="detail-status status-${escapeHtmlAttribute(section.state.status)}">${escapeHtmlText(status.mark)} ${escapeHtmlText(status.label)}</span><span class="detail-reference">${escapeHtmlText(taskReference(section, lookup))}</span></header><h1>${escapeHtmlText(section.task.title)}</h1><p class="detail-category"><strong>カテゴリ:</strong> ${escapeHtmlText(category)}</p>${categoryDescription}<p><strong>正規ID:</strong> <code>${escapeHtmlText(`${section.ref.plan_key}/${section.task.task_id}`)}</code></p>${blockedReason}${readiness}${foldedNote}<section><h2>前提工程</h2>${renderRelationList(incoming.get(key), sectionByKey, lookup, '登録済みの前提工程はありません。', folds)}</section><section><h2>後続工程</h2>${renderRelationList(outgoing.get(key), sectionByKey, lookup, '登録済みの後続工程はありません。', folds)}</section><p class="anchor-status">${escapeHtmlText(anchorText)}</p><details class="task-diagnostics"><summary>開発者向け診断</summary><dl><dt>canonical ref</dt><dd><code>${escapeHtmlText(`${section.ref.project_id}/${section.ref.plan_key}/${section.task.task_id}`)}</code></dd><dt>anchor</dt><dd>${escapeHtmlText(section.anchorOutcome.anchored ? 'verified' : section.anchorOutcome.reason)}</dd></dl></details></article>`;
283
249
  }).join('');
284
250
  const taskIndex = renderTaskIndex(sections, lookup, folds);
285
- return `<div class="right-toolbar"><button type="button" data-show-overview>概要</button><button type="button" data-show-selected hidden>選択工程へ戻る</button><button type="button" data-show-task-index>元Markdown全文</button></div><div class="right-content">${overview}<div data-right-panel="details" hidden>${details}</div><section class="task-index" data-right-panel="task-index" hidden><h1>全工程</h1><p>Latticeに登録された全工程を、現在の状態とともに登録順で表示しています。</p>${taskIndex}</section></div>`;
251
+ return `<div class="right-toolbar"><button type="button" data-show-overview>概要</button><button type="button" data-show-selected hidden>選択工程へ戻る</button><button type="button" data-show-task-index>全工程一覧</button></div><div class="right-content">${overview}<div data-right-panel="details" hidden>${details}</div><section class="task-index" data-right-panel="task-index" hidden><h1>全工程</h1><p>Latticeに登録された全工程を、現在の状態とともに登録順で表示しています。</p>${taskIndex}</section></div>`;
286
252
  }
287
253
 
288
254
  function renderDiagramLegend(presentation, layout = null, expandable = false) {