@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 +8 -0
- package/package.json +1 -1
- package/src/cli-help.mjs +1 -1
- package/src/todo-gantt-html.mjs +18 -52
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
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>
|
package/src/todo-gantt-html.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
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) {
|