@quolu/lattice 0.12.16 → 0.12.18
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 +1 -1
- package/src/todo-gantt-layout.mjs +42 -9
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
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from './todo-markdown-renderer.mjs';
|
|
7
7
|
import { renderTodoGanttSvg, TODO_GANTT_STATUS_PRESENTATION } from './todo-gantt-svg.mjs';
|
|
8
8
|
|
|
9
|
-
export const TODO_GANTT_RENDERER_VERSION = 'lattice.todo_gantt_renderer.
|
|
9
|
+
export const TODO_GANTT_RENDERER_VERSION = 'lattice.todo_gantt_renderer.v14';
|
|
10
10
|
export const TODO_GANTT_PROSE_MAX_BYTES = 8 * 1024 * 1024;
|
|
11
11
|
export const TODO_GANTT_HTML_MAX_BYTES = 24 * 1024 * 1024;
|
|
12
12
|
|
|
@@ -4,6 +4,9 @@ const TASK_LIMIT = 2_000;
|
|
|
4
4
|
const EDGE_LIMIT = 8_000;
|
|
5
5
|
const SWEEP_ROUNDS = 4;
|
|
6
6
|
|
|
7
|
+
/** Columns for a stage made only of ToDos with no registered dependency. */
|
|
8
|
+
const LOOSE_COLUMNS_MINIMUM = 8;
|
|
9
|
+
|
|
7
10
|
const GEOMETRY = Object.freeze({
|
|
8
11
|
left: 16,
|
|
9
12
|
top: 16,
|
|
@@ -11,6 +14,7 @@ const GEOMETRY = Object.freeze({
|
|
|
11
14
|
lane_gap: 296,
|
|
12
15
|
node_width: 272,
|
|
13
16
|
node_height: 68,
|
|
17
|
+
stage_row_gap: 12,
|
|
14
18
|
route_inset: 8,
|
|
15
19
|
route_spacing: 12,
|
|
16
20
|
});
|
|
@@ -410,9 +414,27 @@ export function layoutTodoGantt(readModel, chainProjection, options = {}) {
|
|
|
410
414
|
const nodesByKey = new Map(nodes.map((node) => [node.key, node]));
|
|
411
415
|
const { incoming, outgoing, wave } = assignWaves(nodes, nodesByKey, edges);
|
|
412
416
|
const layers = orderLayers(nodes, wave, incoming, outgoing);
|
|
417
|
+
// A ToDo with no registered dependency has nothing to line up with. Laying
|
|
418
|
+
// every one of them out in a single row makes the canvas as wide as the plan
|
|
419
|
+
// is old — real stores have hundreds — so they wrap into a block underneath
|
|
420
|
+
// the wired ToDos of their stage. Nodes that carry edges keep the top row:
|
|
421
|
+
// edge routing assumes both endpoints of a stage share one baseline.
|
|
422
|
+
const wired = new Set();
|
|
423
|
+
for (const edge of edges) { wired.add(edge.from); wired.add(edge.to); }
|
|
413
424
|
const transversePosition = new Map();
|
|
425
|
+
const stageRow = new Map();
|
|
426
|
+
const stageRowCount = [];
|
|
414
427
|
for (const layer of layers) {
|
|
415
|
-
|
|
428
|
+
const connected = layer.filter((key) => wired.has(key));
|
|
429
|
+
const loose = layer.filter((key) => !wired.has(key));
|
|
430
|
+
connected.forEach((key, index) => { transversePosition.set(key, index); stageRow.set(key, 0); });
|
|
431
|
+
const columns = Math.max(LOOSE_COLUMNS_MINIMUM, connected.length);
|
|
432
|
+
const firstLooseRow = connected.length === 0 ? 0 : 1;
|
|
433
|
+
loose.forEach((key, index) => {
|
|
434
|
+
transversePosition.set(key, index % columns);
|
|
435
|
+
stageRow.set(key, firstLooseRow + Math.floor(index / columns));
|
|
436
|
+
});
|
|
437
|
+
stageRowCount.push(Math.max(0, ...layer.map((key) => stageRow.get(key))) + 1);
|
|
416
438
|
}
|
|
417
439
|
const visibleKeys = new Set(nodes.map(({ key }) => key));
|
|
418
440
|
const displayBranches = edges.flatMap((edge, semanticIndex) => {
|
|
@@ -458,15 +480,25 @@ export function layoutTodoGantt(readModel, chainProjection, options = {}) {
|
|
|
458
480
|
const nodeWidth = Math.max(GEOMETRY.node_width,
|
|
459
481
|
GEOMETRY.route_inset * 2 + (maximumPortTraffic + 1) * GEOMETRY.route_spacing);
|
|
460
482
|
const laneGap = nodeWidth + 24;
|
|
461
|
-
|
|
462
|
-
|
|
483
|
+
// Each routing band is sized for the edges that actually cross it. One
|
|
484
|
+
// crowded band used to set the spacing for every stage in the diagram, so a
|
|
485
|
+
// long plan paid the width of its busiest junction on all of its stages.
|
|
486
|
+
const gapBand = (gap) => Math.max(36, ((gapGroups.get(gap)?.length ?? 0)
|
|
487
|
+
+ (connectorGroupKeysByGap.get(gap)?.size ?? 0) + 1) * GEOMETRY.route_spacing);
|
|
488
|
+
const stageHeight = (index) => stageRowCount[index] * GEOMETRY.node_height
|
|
489
|
+
+ (stageRowCount[index] - 1) * GEOMETRY.stage_row_gap;
|
|
490
|
+
const stageTop = [GEOMETRY.top];
|
|
491
|
+
for (let index = 0; index + 1 < layers.length; index += 1) {
|
|
492
|
+
stageTop.push(stageTop[index] + stageHeight(index) + gapBand(index));
|
|
493
|
+
}
|
|
463
494
|
|
|
464
495
|
const coordinates = new Map();
|
|
465
496
|
const projectedNodes = nodes.map((node) => {
|
|
466
497
|
const visible = visibleKeys.has(node.key);
|
|
467
498
|
const geometry = visible ? {
|
|
468
499
|
x: GEOMETRY.left + transversePosition.get(node.key) * laneGap,
|
|
469
|
-
y:
|
|
500
|
+
y: stageTop[wave.get(node.key)]
|
|
501
|
+
+ stageRow.get(node.key) * (GEOMETRY.node_height + GEOMETRY.stage_row_gap),
|
|
470
502
|
width: nodeWidth,
|
|
471
503
|
height: GEOMETRY.node_height,
|
|
472
504
|
} : null;
|
|
@@ -486,10 +518,11 @@ export function layoutTodoGantt(readModel, chainProjection, options = {}) {
|
|
|
486
518
|
const gapPosition = (edgeKey, waveIndex) => {
|
|
487
519
|
const keys = gapGroups.get(waveIndex);
|
|
488
520
|
const index = keys.indexOf(edgeKey);
|
|
489
|
-
return
|
|
521
|
+
return stageTop[waveIndex] + stageHeight(waveIndex)
|
|
490
522
|
+ GEOMETRY.route_spacing * (index + 1);
|
|
491
523
|
};
|
|
492
|
-
const maximumLayerWidth = Math.max(
|
|
524
|
+
const maximumLayerWidth = Math.max(1,
|
|
525
|
+
...[...transversePosition.values()].map((position) => position + 1));
|
|
493
526
|
const routeRight = GEOMETRY.left + (maximumLayerWidth - 1) * laneGap + nodeWidth + 12;
|
|
494
527
|
const branchCounts = new Map();
|
|
495
528
|
for (const branch of displayBranches) {
|
|
@@ -551,7 +584,7 @@ export function layoutTodoGantt(readModel, chainProjection, options = {}) {
|
|
|
551
584
|
connectorGapRanks.set(targetGap, groupRank + 1);
|
|
552
585
|
const primaryTargetPortX = group[0].route.at(-1)[0];
|
|
553
586
|
const junction = [primaryTargetPortX,
|
|
554
|
-
|
|
587
|
+
stageTop[targetGap] + stageHeight(targetGap)
|
|
555
588
|
+ GEOMETRY.route_spacing * ((gapGroups.get(targetGap)?.length ?? 0) + groupRank + 1)];
|
|
556
589
|
const contacts = [];
|
|
557
590
|
for (const [index, edge] of group.entries()) {
|
|
@@ -602,8 +635,8 @@ export function layoutTodoGantt(readModel, chainProjection, options = {}) {
|
|
|
602
635
|
sweep: { method: 'stable_median', rounds: SWEEP_ROUNDS, tie_break: 'previous_position_then_task_ref' },
|
|
603
636
|
bounds: {
|
|
604
637
|
width: nodes.length === 0 ? 0 : routeMaximumX + GEOMETRY.left,
|
|
605
|
-
height: nodes.length === 0 ? 0
|
|
606
|
-
+ (layers.length - 1)
|
|
638
|
+
height: nodes.length === 0 ? 0
|
|
639
|
+
: GEOMETRY.top + stageTop.at(-1) + stageHeight(layers.length - 1),
|
|
607
640
|
wave_count: layers.length,
|
|
608
641
|
},
|
|
609
642
|
nodes: projectedNodes,
|