@quolu/lattice 0.1.0 → 0.12.2
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 +87 -4
- package/bin/lattice-bridge.mjs +87 -0
- package/bin/lattice-dashboard.mjs +70 -0
- package/bin/lattice-mcp.mjs +9 -9
- package/bin/lattice.mjs +61 -8
- package/docs/bridge-setup.md +87 -0
- package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -0
- package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -0
- package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -0
- package/package.json +19 -7
- package/sensor/dist/bin/command-supervision.d.ts +1 -1
- package/sensor/dist/bin/command-supervision.js +4 -4
- package/sensor/dist/bin/command-supervision.js.map +1 -1
- package/sensor/dist/bin/fatal-handler.js +2 -2
- package/sensor/dist/bin/fatal-handler.js.map +1 -1
- package/sensor/dist/bin/{codegraph.js → lattice-sensor.js} +207 -328
- package/sensor/dist/bin/node-version-check.d.ts +7 -5
- package/sensor/dist/bin/node-version-check.d.ts.map +1 -1
- package/sensor/dist/bin/node-version-check.js +19 -14
- package/sensor/dist/bin/node-version-check.js.map +1 -1
- package/sensor/dist/context/index.d.ts +1 -1
- package/sensor/dist/context/index.js +6 -6
- package/sensor/dist/context/index.js.map +1 -1
- package/sensor/dist/db/index.d.ts +5 -5
- package/sensor/dist/db/index.d.ts.map +1 -1
- package/sensor/dist/db/index.js +7 -7
- package/sensor/dist/db/index.js.map +1 -1
- package/sensor/dist/db/migrations.js +1 -1
- package/sensor/dist/db/migrations.js.map +1 -1
- package/sensor/dist/db/queries.d.ts +1 -1
- package/sensor/dist/db/queries.js +5 -5
- package/sensor/dist/db/queries.js.map +1 -1
- package/sensor/dist/db/schema.sql +2 -2
- package/sensor/dist/db/sqlite-adapter.d.ts +2 -2
- package/sensor/dist/db/sqlite-adapter.js +3 -3
- package/sensor/dist/db/sqlite-adapter.js.map +1 -1
- package/sensor/dist/db/wal-valve.d.ts +1 -1
- package/sensor/dist/db/wal-valve.d.ts.map +1 -1
- package/sensor/dist/db/wal-valve.js +2 -2
- package/sensor/dist/db/wal-valve.js.map +1 -1
- package/sensor/dist/directory.d.ts +27 -63
- package/sensor/dist/directory.d.ts.map +1 -1
- package/sensor/dist/directory.js +78 -134
- package/sensor/dist/directory.js.map +1 -1
- package/sensor/dist/errors.d.ts +15 -15
- package/sensor/dist/errors.d.ts.map +1 -1
- package/sensor/dist/errors.js +21 -21
- package/sensor/dist/errors.js.map +1 -1
- package/sensor/dist/extraction/dynamic-import.d.ts +1 -1
- package/sensor/dist/extraction/dynamic-import.js +1 -1
- package/sensor/dist/extraction/extraction-version.d.ts +1 -1
- package/sensor/dist/extraction/extraction-version.js +1 -1
- package/sensor/dist/extraction/generated-detection.d.ts +1 -1
- package/sensor/dist/extraction/generated-detection.js +1 -1
- package/sensor/dist/extraction/grammars.d.ts +3 -3
- package/sensor/dist/extraction/grammars.js +5 -5
- package/sensor/dist/extraction/grammars.js.map +1 -1
- package/sensor/dist/extraction/index.d.ts +6 -6
- package/sensor/dist/extraction/index.js +50 -50
- package/sensor/dist/extraction/index.js.map +1 -1
- package/sensor/dist/extraction/kernel/decode.d.ts +1 -1
- package/sensor/dist/extraction/kernel/decode.js +1 -1
- package/sensor/dist/extraction/kernel/index.d.ts +3 -3
- package/sensor/dist/extraction/kernel/index.js +6 -6
- package/sensor/dist/extraction/kernel/index.js.map +1 -1
- package/sensor/dist/extraction/kernel/layout.d.ts +1 -1
- package/sensor/dist/extraction/kernel/layout.js +1 -1
- package/sensor/dist/extraction/kernel/loader.d.ts +6 -6
- package/sensor/dist/extraction/kernel/loader.js +13 -13
- package/sensor/dist/extraction/kernel/loader.js.map +1 -1
- package/sensor/dist/extraction/languages/cfquery.d.ts +1 -1
- package/sensor/dist/extraction/languages/cfquery.js +1 -1
- package/sensor/dist/extraction/languages/lua.d.ts +11 -0
- package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/lua.js +1 -0
- package/sensor/dist/extraction/languages/lua.js.map +1 -1
- package/sensor/dist/extraction/parse-pool.d.ts +5 -5
- package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
- package/sensor/dist/extraction/parse-pool.js +4 -4
- package/sensor/dist/extraction/parse-worker.js +1 -1
- package/sensor/dist/extraction/parse-worker.js.map +1 -1
- package/sensor/dist/extraction/store-writer.d.ts +1 -1
- package/sensor/dist/extraction/store-writer.js +1 -1
- package/sensor/dist/extraction/svelte-extractor.d.ts +1 -1
- package/sensor/dist/extraction/svelte-extractor.js +1 -1
- package/sensor/dist/extraction/tree-sitter-helpers.js +1 -1
- package/sensor/dist/extraction/tree-sitter.d.ts +2 -2
- package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/sensor/dist/extraction/tree-sitter.js +71 -4
- package/sensor/dist/extraction/tree-sitter.js.map +1 -1
- package/sensor/dist/extraction/wasm-runtime-flags.d.ts +2 -2
- package/sensor/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
- package/sensor/dist/extraction/wasm-runtime-flags.js +5 -5
- package/sensor/dist/extraction/wasm-runtime-flags.js.map +1 -1
- package/sensor/dist/index.d.ts +36 -36
- package/sensor/dist/index.d.ts.map +1 -1
- package/sensor/dist/index.js +71 -71
- package/sensor/dist/index.js.map +1 -1
- package/sensor/dist/mcp/daemon-manager.js +1 -1
- package/sensor/dist/mcp/daemon-paths.d.ts +3 -3
- package/sensor/dist/mcp/daemon-paths.js +7 -7
- package/sensor/dist/mcp/daemon-paths.js.map +1 -1
- package/sensor/dist/mcp/daemon-registry.d.ts +2 -2
- package/sensor/dist/mcp/daemon-registry.js +5 -5
- package/sensor/dist/mcp/daemon.d.ts +7 -7
- package/sensor/dist/mcp/daemon.d.ts.map +1 -1
- package/sensor/dist/mcp/daemon.js +21 -21
- package/sensor/dist/mcp/daemon.js.map +1 -1
- package/sensor/dist/mcp/dynamic-boundaries.js +1 -1
- package/sensor/dist/mcp/engine.d.ts +9 -9
- package/sensor/dist/mcp/engine.d.ts.map +1 -1
- package/sensor/dist/mcp/engine.js +44 -44
- package/sensor/dist/mcp/engine.js.map +1 -1
- package/sensor/dist/mcp/index.d.ts +14 -14
- package/sensor/dist/mcp/index.d.ts.map +1 -1
- package/sensor/dist/mcp/index.js +41 -41
- package/sensor/dist/mcp/index.js.map +1 -1
- package/sensor/dist/mcp/liveness-watchdog.js +7 -7
- package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
- package/sensor/dist/mcp/ppid-watchdog.d.ts +3 -3
- package/sensor/dist/mcp/ppid-watchdog.js +2 -2
- package/sensor/dist/mcp/proxy.d.ts.map +1 -1
- package/sensor/dist/mcp/proxy.js +37 -37
- package/sensor/dist/mcp/proxy.js.map +1 -1
- package/sensor/dist/mcp/query-pool.d.ts +3 -3
- package/sensor/dist/mcp/query-pool.js +7 -7
- package/sensor/dist/mcp/query-pool.js.map +1 -1
- package/sensor/dist/mcp/query-worker.d.ts +2 -2
- package/sensor/dist/mcp/query-worker.js +7 -7
- package/sensor/dist/mcp/query-worker.js.map +1 -1
- package/sensor/dist/mcp/server-instructions.d.ts +10 -12
- package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
- package/sensor/dist/mcp/server-instructions.js +39 -37
- package/sensor/dist/mcp/server-instructions.js.map +1 -1
- package/sensor/dist/mcp/session.d.ts +2 -2
- package/sensor/dist/mcp/session.js +18 -18
- package/sensor/dist/mcp/session.js.map +1 -1
- package/sensor/dist/mcp/startup-handshake.d.ts +3 -3
- package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
- package/sensor/dist/mcp/startup-handshake.js +3 -3
- package/sensor/dist/mcp/startup-handshake.js.map +1 -1
- package/sensor/dist/mcp/tools.d.ts +53 -53
- package/sensor/dist/mcp/tools.d.ts.map +1 -1
- package/sensor/dist/mcp/tools.js +180 -216
- package/sensor/dist/mcp/tools.js.map +1 -1
- package/sensor/dist/mcp/transport.d.ts +1 -1
- package/sensor/dist/mcp/transport.js +6 -6
- package/sensor/dist/mcp/transport.js.map +1 -1
- package/sensor/dist/mcp/version.d.ts +2 -2
- package/sensor/dist/mcp/version.d.ts.map +1 -1
- package/sensor/dist/mcp/version.js +3 -3
- package/sensor/dist/mcp/version.js.map +1 -1
- package/sensor/dist/project-config.d.ts +6 -6
- package/sensor/dist/project-config.d.ts.map +1 -1
- package/sensor/dist/project-config.js +10 -10
- package/sensor/dist/project-config.js.map +1 -1
- package/sensor/dist/resolution/callback-synthesizer.js +4 -4
- package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
- package/sensor/dist/resolution/frameworks/drupal.d.ts +3 -3
- package/sensor/dist/resolution/frameworks/drupal.js +3 -3
- package/sensor/dist/resolution/frameworks/fabric.js +1 -1
- package/sensor/dist/resolution/frameworks/fabric.js.map +1 -1
- package/sensor/dist/resolution/frameworks/java.js +1 -1
- package/sensor/dist/resolution/frameworks/java.js.map +1 -1
- package/sensor/dist/resolution/frameworks/svelte.js +1 -1
- package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
- package/sensor/dist/resolution/goframe-synthesizer.d.ts +1 -1
- package/sensor/dist/resolution/goframe-synthesizer.js +1 -1
- package/sensor/dist/resolution/index.d.ts +1 -1
- package/sensor/dist/resolution/index.js +9 -9
- package/sensor/dist/resolution/index.js.map +1 -1
- package/sensor/dist/resolution/name-matcher.js +2 -2
- package/sensor/dist/resolution/name-matcher.js.map +1 -1
- package/sensor/dist/resolution/resolver-pool.d.ts +2 -2
- package/sensor/dist/resolution/resolver-pool.js +4 -4
- package/sensor/dist/resolution/resolver-pool.js.map +1 -1
- package/sensor/dist/resolution/resolver-worker.js +2 -2
- package/sensor/dist/resolution/resolver-worker.js.map +1 -1
- package/sensor/dist/resolution/workspace-packages.js +1 -1
- package/sensor/dist/resolution/workspace-packages.js.map +1 -1
- package/sensor/dist/search/identifier-segments.d.ts +1 -1
- package/sensor/dist/search/identifier-segments.js +1 -1
- package/sensor/dist/search/query-utils.d.ts +1 -0
- package/sensor/dist/search/query-utils.d.ts.map +1 -1
- package/sensor/dist/search/query-utils.js +10 -3
- package/sensor/dist/search/query-utils.js.map +1 -1
- package/sensor/dist/sync/git-hooks.d.ts +9 -9
- package/sensor/dist/sync/git-hooks.d.ts.map +1 -1
- package/sensor/dist/sync/git-hooks.js +15 -15
- package/sensor/dist/sync/git-hooks.js.map +1 -1
- package/sensor/dist/sync/watch-policy.d.ts +2 -2
- package/sensor/dist/sync/watch-policy.js +5 -5
- package/sensor/dist/sync/watch-policy.js.map +1 -1
- package/sensor/dist/sync/watcher.d.ts +2 -2
- package/sensor/dist/sync/watcher.d.ts.map +1 -1
- package/sensor/dist/sync/watcher.js +16 -16
- package/sensor/dist/sync/watcher.js.map +1 -1
- package/sensor/dist/sync/worktree.d.ts +5 -5
- package/sensor/dist/sync/worktree.d.ts.map +1 -1
- package/sensor/dist/sync/worktree.js +9 -9
- package/sensor/dist/sync/worktree.js.map +1 -1
- package/sensor/dist/telemetry/index.d.ts +8 -8
- package/sensor/dist/telemetry/index.d.ts.map +1 -1
- package/sensor/dist/telemetry/index.js +16 -16
- package/sensor/dist/telemetry/index.js.map +1 -1
- package/sensor/dist/types.d.ts +2 -2
- package/sensor/dist/types.js +1 -1
- package/sensor/dist/ui/color.d.ts +2 -2
- package/sensor/dist/ui/color.js +2 -2
- package/sensor/dist/ui/glyphs.d.ts +3 -3
- package/sensor/dist/ui/glyphs.js +7 -7
- package/sensor/dist/ui/glyphs.js.map +1 -1
- package/sensor/dist/utils.d.ts +4 -4
- package/sensor/dist/utils.js +8 -8
- package/sensor/dist/utils.js.map +1 -1
- package/sensor/package.json +8 -13
- package/src/artifact-contracts.mjs +2 -2
- package/src/boundary-compiler.mjs +28 -28
- package/src/boundary-observation-compiler-v2.mjs +10 -10
- package/src/bounded-seam.mjs +144 -0
- package/src/bridge-cli.mjs +227 -0
- package/src/bridge-config.mjs +346 -0
- package/src/bridge-daemon.mjs +328 -0
- package/src/bridge-launch-agent.mjs +311 -0
- package/src/bridge-server.mjs +309 -0
- package/src/cli-help.mjs +163 -0
- package/src/control-compiler.mjs +30 -30
- package/src/dag-chain.mjs +261 -0
- package/src/factory-diagnostics.mjs +8 -7
- package/src/hash-chain.mjs +76 -0
- package/src/node-version-guard.mjs +5 -4
- package/src/project-cli.mjs +392 -0
- package/src/project-identity.mjs +71 -0
- package/src/rc1-evidence-bundle.mjs +16 -16
- package/src/rc1-v4-campaign.mjs +15 -14
- package/src/rc1-v5-artifact-set.mjs +2 -2
- package/src/rc1-v5-campaign.mjs +15 -14
- package/src/rc1-v6-artifact-set.mjs +18 -18
- package/src/rc1-v6-campaign.mjs +16 -16
- package/src/rc1-v6-causal-binding.mjs +16 -14
- package/src/rc1-v6-measurement.mjs +20 -38
- package/src/rc2-artifact-set.mjs +45 -40
- package/src/rc2-campaign.mjs +48 -66
- package/src/rc2-delivery-policy-front-end.mjs +30 -30
- package/src/rc2-rc1-transfer-front-end.mjs +5 -5
- package/src/rc3-actual-dogfood.mjs +6 -6
- package/src/rc3-dogfood-scaffold.mjs +12 -7
- package/src/rc3-scripted-campaign.mjs +75 -21
- package/src/rc4-stage1-dogfood.mjs +24 -7
- package/src/runtime-cli.mjs +2424 -45
- package/src/runtime-contracts.mjs +7 -7
- package/src/runtime-control-store.mjs +547 -0
- package/src/runtime-controller-protocol.mjs +517 -0
- package/src/runtime-direct-os-observer.mjs +284 -0
- package/src/runtime-engine.mjs +2 -2
- package/src/runtime-errors.mjs +1 -1
- package/src/runtime-event-store.mjs +15 -43
- package/src/runtime-front-end.mjs +26 -26
- package/src/runtime-gate-store.mjs +477 -0
- package/src/runtime-hold-recompile.mjs +214 -23
- package/src/runtime-lifecycle-lock.mjs +290 -0
- package/src/runtime-managed-supervisor.mjs +1276 -0
- package/src/runtime-multi-epoch-store.mjs +761 -0
- package/src/runtime-projection.mjs +62 -0
- package/src/seam-transform.mjs +38 -38
- package/src/{codegraph-adapter.mjs → sensor-adapter.mjs} +7 -7
- package/src/sensor-cli.mjs +107 -0
- package/src/sensor-runtime.mjs +48 -0
- package/src/timestamp-contract.mjs +8 -0
- package/src/todo-chain.mjs +178 -0
- package/src/todo-cli.mjs +1032 -0
- package/src/todo-contracts.mjs +501 -0
- package/src/todo-dashboard-registry.mjs +318 -0
- package/src/todo-gantt-html.mjs +416 -0
- package/src/todo-gantt-layout.mjs +603 -0
- package/src/todo-gantt-live.mjs +269 -0
- package/src/todo-gantt-presentation.mjs +217 -0
- package/src/todo-gantt-svg.mjs +249 -0
- package/src/todo-markdown-renderer.mjs +260 -0
- package/src/todo-migration.mjs +260 -0
- package/src/todo-narrative-anchor.mjs +130 -0
- package/src/todo-revision.mjs +425 -0
- package/src/todo-status.mjs +340 -0
- package/src/todo-store.mjs +3451 -0
- package/src/treatment-compiler.mjs +28 -28
- package/src/treatment-runner.mjs +42 -44
- package/sensor/dist/bin/codegraph.d.ts +0 -26
- package/sensor/dist/bin/codegraph.d.ts.map +0 -1
- package/sensor/dist/bin/codegraph.js.map +0 -1
- package/sensor/dist/bin/uninstall.d.ts +0 -14
- package/sensor/dist/bin/uninstall.d.ts.map +0 -1
- package/sensor/dist/bin/uninstall.js +0 -36
- package/sensor/dist/bin/uninstall.js.map +0 -1
- package/sensor/dist/installer/beta-signup.d.ts +0 -54
- package/sensor/dist/installer/beta-signup.d.ts.map +0 -1
- package/sensor/dist/installer/beta-signup.js +0 -178
- package/sensor/dist/installer/beta-signup.js.map +0 -1
- package/sensor/dist/installer/config-writer.d.ts +0 -28
- package/sensor/dist/installer/config-writer.d.ts.map +0 -1
- package/sensor/dist/installer/config-writer.js +0 -91
- package/sensor/dist/installer/config-writer.js.map +0 -1
- package/sensor/dist/installer/index.d.ts +0 -142
- package/sensor/dist/installer/index.d.ts.map +0 -1
- package/sensor/dist/installer/index.js +0 -622
- package/sensor/dist/installer/index.js.map +0 -1
- package/sensor/dist/installer/instructions-template.d.ts +0 -41
- package/sensor/dist/installer/instructions-template.d.ts.map +0 -1
- package/sensor/dist/installer/instructions-template.js +0 -53
- package/sensor/dist/installer/instructions-template.js.map +0 -1
- package/sensor/dist/installer/targets/antigravity.d.ts +0 -57
- package/sensor/dist/installer/targets/antigravity.d.ts.map +0 -1
- package/sensor/dist/installer/targets/antigravity.js +0 -308
- package/sensor/dist/installer/targets/antigravity.js.map +0 -1
- package/sensor/dist/installer/targets/claude.d.ts +0 -62
- package/sensor/dist/installer/targets/claude.d.ts.map +0 -1
- package/sensor/dist/installer/targets/claude.js +0 -454
- package/sensor/dist/installer/targets/claude.js.map +0 -1
- package/sensor/dist/installer/targets/codex.d.ts +0 -18
- package/sensor/dist/installer/targets/codex.d.ts.map +0 -1
- package/sensor/dist/installer/targets/codex.js +0 -185
- package/sensor/dist/installer/targets/codex.js.map +0 -1
- package/sensor/dist/installer/targets/cursor.d.ts +0 -35
- package/sensor/dist/installer/targets/cursor.d.ts.map +0 -1
- package/sensor/dist/installer/targets/cursor.js +0 -254
- package/sensor/dist/installer/targets/cursor.js.map +0 -1
- package/sensor/dist/installer/targets/gemini.d.ts +0 -26
- package/sensor/dist/installer/targets/gemini.d.ts.map +0 -1
- package/sensor/dist/installer/targets/gemini.js +0 -165
- package/sensor/dist/installer/targets/gemini.js.map +0 -1
- package/sensor/dist/installer/targets/hermes.d.ts +0 -18
- package/sensor/dist/installer/targets/hermes.d.ts.map +0 -1
- package/sensor/dist/installer/targets/hermes.js +0 -359
- package/sensor/dist/installer/targets/hermes.js.map +0 -1
- package/sensor/dist/installer/targets/kiro.d.ts +0 -27
- package/sensor/dist/installer/targets/kiro.d.ts.map +0 -1
- package/sensor/dist/installer/targets/kiro.js +0 -178
- package/sensor/dist/installer/targets/kiro.js.map +0 -1
- package/sensor/dist/installer/targets/opencode.d.ts +0 -38
- package/sensor/dist/installer/targets/opencode.d.ts.map +0 -1
- package/sensor/dist/installer/targets/opencode.js +0 -288
- package/sensor/dist/installer/targets/opencode.js.map +0 -1
- package/sensor/dist/installer/targets/registry.d.ts +0 -35
- package/sensor/dist/installer/targets/registry.d.ts.map +0 -1
- package/sensor/dist/installer/targets/registry.js +0 -91
- package/sensor/dist/installer/targets/registry.js.map +0 -1
- package/sensor/dist/installer/targets/shared.d.ts +0 -101
- package/sensor/dist/installer/targets/shared.d.ts.map +0 -1
- package/sensor/dist/installer/targets/shared.js +0 -264
- package/sensor/dist/installer/targets/shared.js.map +0 -1
- package/sensor/dist/installer/targets/toml.d.ts +0 -52
- package/sensor/dist/installer/targets/toml.d.ts.map +0 -1
- package/sensor/dist/installer/targets/toml.js +0 -147
- package/sensor/dist/installer/targets/toml.js.map +0 -1
- package/sensor/dist/installer/targets/types.d.ts +0 -108
- package/sensor/dist/installer/targets/types.d.ts.map +0 -1
- package/sensor/dist/installer/targets/types.js +0 -16
- package/sensor/dist/installer/targets/types.js.map +0 -1
- package/sensor/dist/upgrade/index.d.ts +0 -171
- package/sensor/dist/upgrade/index.d.ts.map +0 -1
- package/sensor/dist/upgrade/index.js +0 -659
- package/sensor/dist/upgrade/index.js.map +0 -1
- package/sensor/dist/upgrade/remove-binary.d.ts +0 -87
- package/sensor/dist/upgrade/remove-binary.d.ts.map +0 -1
- package/sensor/dist/upgrade/remove-binary.js +0 -289
- package/sensor/dist/upgrade/remove-binary.js.map +0 -1
- package/sensor/dist/upgrade/update-check.d.ts +0 -92
- package/sensor/dist/upgrade/update-check.d.ts.map +0 -1
- package/sensor/dist/upgrade/update-check.js +0 -258
- package/sensor/dist/upgrade/update-check.js.map +0 -1
- package/src/bootstrap.mjs +0 -88
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
renderTodoMarkdownDocument,
|
|
5
|
+
serializeJsonForScript,
|
|
6
|
+
} from './todo-markdown-renderer.mjs';
|
|
7
|
+
import { renderTodoGanttSvg, TODO_GANTT_STATUS_PRESENTATION } from './todo-gantt-svg.mjs';
|
|
8
|
+
|
|
9
|
+
export const TODO_GANTT_RENDERER_VERSION = 'lattice.todo_gantt_renderer.v8';
|
|
10
|
+
export const TODO_GANTT_PROSE_MAX_BYTES = 8 * 1024 * 1024;
|
|
11
|
+
export const TODO_GANTT_HTML_MAX_BYTES = 24 * 1024 * 1024;
|
|
12
|
+
|
|
13
|
+
const DOCUMENT_STATUS = TODO_GANTT_STATUS_PRESENTATION;
|
|
14
|
+
|
|
15
|
+
function statusMarkup(status, suffix = '') {
|
|
16
|
+
const value = DOCUMENT_STATUS[status] ?? { mark: '?', label: '状態不明' };
|
|
17
|
+
return `<span class="status-symbol status-${escapeHtmlAttribute(status)}" role="img" aria-label="${escapeHtmlAttribute(value.label)}">${escapeHtmlText(value.mark)}</span>${suffix}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function projectDisplayName(readModel, metadata) {
|
|
21
|
+
for (const candidate of [metadata?.project_display_name, metadata?.project_name]) {
|
|
22
|
+
if (typeof candidate === 'string' && candidate.trim() !== '') return candidate.trim();
|
|
23
|
+
}
|
|
24
|
+
return readModel.project_id;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class TodoGanttRenderError extends Error {
|
|
28
|
+
constructor(code, message, detail = {}) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'TodoGanttRenderError';
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.detail = detail;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function escapeHtmlAttribute(value) {
|
|
37
|
+
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')
|
|
38
|
+
.replaceAll('"', '"').replaceAll("'", ''').replace(/[\u0000-\u001f\u007f]/gu, '');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function escapeHtmlText(value) {
|
|
42
|
+
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function refKey(ref) {
|
|
46
|
+
return JSON.stringify([ref.project_id, ref.plan_key, ref.task_id]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function digest(value) {
|
|
50
|
+
return createHash('sha256').update(value, 'utf8').digest('hex');
|
|
51
|
+
}
|
|
52
|
+
|
|
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
|
+
|
|
60
|
+
function normalizeSections(readModel, narratives, anchorOutcomes) {
|
|
61
|
+
const supplied = new Map(narratives.map((entry) => [refKey(entry.ref), entry]));
|
|
62
|
+
const outcomes = new Map(anchorOutcomes.map((entry) => [refKey(entry.ref), entry]));
|
|
63
|
+
const result = [];
|
|
64
|
+
const documents = new Map();
|
|
65
|
+
let proseBytes = 0;
|
|
66
|
+
for (const member of readModel.members) {
|
|
67
|
+
const states = new Map(member.tasks.map((state) => [state.task_id, state]));
|
|
68
|
+
for (const task of member.plan.tasks) {
|
|
69
|
+
const ref = { project_id: member.plan.project_id, plan_key: member.plan.plan_key, task_id: task.task_id };
|
|
70
|
+
const narrative = supplied.get(refKey(ref));
|
|
71
|
+
const markdown = narrative?.markdown ?? '';
|
|
72
|
+
const narrativeRef = narrative?.narrative_ref ?? task.narrative_ref;
|
|
73
|
+
const documentKey = narrativeRef === null
|
|
74
|
+
? refKey(ref) : JSON.stringify([member.plan.plan_key, narrativeRef, digest(markdown)]);
|
|
75
|
+
let document = documents.get(documentKey);
|
|
76
|
+
if (document === undefined) {
|
|
77
|
+
proseBytes += Buffer.byteLength(markdown, 'utf8');
|
|
78
|
+
if (proseBytes > TODO_GANTT_PROSE_MAX_BYTES) {
|
|
79
|
+
throw new TodoGanttRenderError('TODO_SCALE_EXCEEDED', 'todo gantt embedded prose limit exceeded', {
|
|
80
|
+
prose_bytes: proseBytes, prose_limit: TODO_GANTT_PROSE_MAX_BYTES,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
document = { markdown, narrativeRef, tasks: [], rendered: '' };
|
|
84
|
+
documents.set(documentKey, document);
|
|
85
|
+
}
|
|
86
|
+
const anchorOutcome = outcomes.get(refKey(ref)) ?? {
|
|
87
|
+
ref, narrative_ref: narrativeRef, anchored: false, reason: 'anchor_missing',
|
|
88
|
+
origin_line: task.narrative_anchor?.origin_line ?? null,
|
|
89
|
+
};
|
|
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;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return { sections: result, proseBytes };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function renderTaskIndex(sections, lookup) {
|
|
130
|
+
const plans = [];
|
|
131
|
+
for (const section of sections) {
|
|
132
|
+
let plan = plans.at(-1);
|
|
133
|
+
if (plan === undefined || plan.planKey !== section.ref.plan_key) {
|
|
134
|
+
plan = { planKey: section.ref.plan_key, tasks: [] };
|
|
135
|
+
plans.push(plan);
|
|
136
|
+
}
|
|
137
|
+
plan.tasks.push(section);
|
|
138
|
+
}
|
|
139
|
+
return plans.map((plan) => `<section class="task-index-plan"><h2><code>${escapeHtmlText(plan.planKey)}</code></h2><ol class="task-index-list">${plan.tasks.map((section) => {
|
|
140
|
+
const key = refKey(section.ref);
|
|
141
|
+
const status = DOCUMENT_STATUS[section.state.status] ?? { mark: '?', label: '状態不明' };
|
|
142
|
+
const blockedReason = section.state.status === 'blocked'
|
|
143
|
+
? `<span class="task-index-blocked-reason">— ${escapeHtmlText(section.state.blocked_reason ?? '理由未記録')}</span>` : '';
|
|
144
|
+
return `<li><button type="button" data-select-node-key="${escapeHtmlAttribute(key)}"><span class="task-index-status status-${escapeHtmlAttribute(section.state.status)}" role="img" aria-label="${escapeHtmlAttribute(status.label)}">${escapeHtmlText(status.mark)}</span><span class="task-index-reference">${escapeHtmlText(taskReference(section, lookup))}</span><strong>${escapeHtmlText(section.task.title)}</strong>${blockedReason}</button></li>`;
|
|
145
|
+
}).join('')}</ol></section>`).join('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function presentationLookup(presentation) {
|
|
149
|
+
return {
|
|
150
|
+
lanes: new Map((presentation?.lanes ?? []).map((lane) => [JSON.stringify([lane.plan_key, lane.lane]), lane])),
|
|
151
|
+
taskNumbers: new Map((presentation?.task_numbers ?? []).map((entry) => [refKey(entry), entry])),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function taskReference(section, lookup) {
|
|
156
|
+
const number = lookup.taskNumbers.get(refKey(section.ref));
|
|
157
|
+
return number === undefined ? `ID ${section.task.task_id}` : `工程 ${number.display_number}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function renderRelationList(relations, sectionByKey, lookup, emptyText) {
|
|
161
|
+
if (relations.length === 0) return `<p class="relation-empty">${escapeHtmlText(emptyText)}</p>`;
|
|
162
|
+
return `<ul class="relation-list">${relations.map((relation) => {
|
|
163
|
+
const target = sectionByKey.get(refKey(relation.ref));
|
|
164
|
+
if (target === undefined) return '';
|
|
165
|
+
const join = relation.joinIds.length === 0 ? ''
|
|
166
|
+
: `<span class="relation-kind">合流条件: ${escapeHtmlText(relation.joinIds.join(', '))}</span>`;
|
|
167
|
+
return `<li><button type="button" data-select-node-key="${escapeHtmlAttribute(refKey(target.ref))}"><strong>${escapeHtmlText(taskReference(target, lookup))}</strong><span>${escapeHtmlText(target.task.title)}</span></button>${join}</li>`;
|
|
168
|
+
}).join('')}</ul>`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function renderPhaseProgress(readModel) {
|
|
172
|
+
const rows = [];
|
|
173
|
+
for (const member of readModel.members) {
|
|
174
|
+
if (!['lattice.todo_plan.v4', 'lattice.todo_plan.v5'].includes(member.plan.schema)) continue;
|
|
175
|
+
const phases = new Map(member.snapshot.phases.map((phase) => [phase.phase_id, phase]));
|
|
176
|
+
for (const phase of member.plan.phases) {
|
|
177
|
+
const tasks = member.plan.tasks.filter((task) => task.phase_id === phase.phase_id);
|
|
178
|
+
const states = new Map(member.tasks.map((task) => [task.task_id, task.status]));
|
|
179
|
+
const done = tasks.filter((task) => states.get(task.task_id) === 'done').length;
|
|
180
|
+
const state = phases.get(phase.phase_id);
|
|
181
|
+
rows.push(`<li class="phase-progress status-${escapeHtmlAttribute(state.status)}"><header><strong>${escapeHtmlText(phase.title ?? phase.phase_id)}</strong><span>${escapeHtmlText(state.status)}</span></header><p><code>${escapeHtmlText(`${member.plan.plan_key}/${phase.phase_id}`)}</code> — policy <code>${escapeHtmlText(phase.gate_policy)}</code> — ToDo ${done}/${tasks.length}</p><progress max="${tasks.length}" value="${done}">${done}/${tasks.length}</progress></li>`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const decoupled = readModel.members.some(({ plan }) => plan.schema === 'lattice.todo_plan.v5');
|
|
185
|
+
const guidance = decoupled
|
|
186
|
+
? 'ToDo完了とPhase受理は別です。Phaseは重監査の順序を表し、通常ToDoの開始順はToDo依存だけで決まります。'
|
|
187
|
+
: 'ToDo完了とPhase受理は別です。<code>gate_ready</code>では後続Phaseはまだ解放されません。';
|
|
188
|
+
return rows.length === 0 ? ''
|
|
189
|
+
: `<section class="phase-overview"><h2>Phase進捗</h2><p>${guidance}</p><ol>${rows.join('')}</ol></section>`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function renderRightPane(sections, layout, presentation, readModel) {
|
|
193
|
+
const lookup = presentationLookup(presentation);
|
|
194
|
+
const sectionByKey = new Map(sections.map((section) => [refKey(section.ref), section]));
|
|
195
|
+
const nodeByKey = new Map(layout.nodes.map((node) => [refKey(node.ref), node]));
|
|
196
|
+
const incoming = new Map(sections.map((section) => [refKey(section.ref), []]));
|
|
197
|
+
const outgoing = new Map(sections.map((section) => [refKey(section.ref), []]));
|
|
198
|
+
const addRelation = (relations, ownerKey, ref, joinIds) => {
|
|
199
|
+
const entries = relations.get(ownerKey);
|
|
200
|
+
if (entries === undefined) return;
|
|
201
|
+
let entry = entries.find((candidate) => refKey(candidate.ref) === refKey(ref));
|
|
202
|
+
if (entry === undefined) {
|
|
203
|
+
entry = { ref, joinIds: [] };
|
|
204
|
+
entries.push(entry);
|
|
205
|
+
}
|
|
206
|
+
entry.joinIds = [...new Set([...entry.joinIds, ...joinIds])].sort();
|
|
207
|
+
};
|
|
208
|
+
for (const edge of layout.edges) {
|
|
209
|
+
addRelation(incoming, refKey(edge.to), edge.from, edge.join_ids);
|
|
210
|
+
addRelation(outgoing, refKey(edge.from), edge.to, edge.join_ids);
|
|
211
|
+
}
|
|
212
|
+
const counts = { pending: 0, 'in-progress': 0, blocked: 0, done: 0 };
|
|
213
|
+
for (const section of sections) counts[section.state.status] += 1;
|
|
214
|
+
const active = sections.filter((section) => section.state.status === 'in-progress');
|
|
215
|
+
const ready = layout.nodes.filter((node) => node.visibility.next_ready);
|
|
216
|
+
const dispatchSummary = ready.length > 1
|
|
217
|
+
? `<p class="readiness-note"><strong>同時dispatch推奨:</strong> ${ready.length}工程。ready frontier全件が既定です。一部だけを直列着手する場合は理由が必要です。</p>`
|
|
218
|
+
: ready.length === 1
|
|
219
|
+
? '<p class="readiness-note"><strong>着手候補:</strong> 1工程です。</p>'
|
|
220
|
+
: '<p class="readiness-note">現在のready frontierは空です。</p>';
|
|
221
|
+
const activeLinks = active.length === 0 ? '<p>作業中の工程はありません。</p>'
|
|
222
|
+
: `<ul class="active-list">${active.map((section) => `<li><button type="button" data-select-node-key="${escapeHtmlAttribute(refKey(section.ref))}">${escapeHtmlText(taskReference(section, lookup))} — ${escapeHtmlText(section.task.title)}</button></li>`).join('')}</ul>`;
|
|
223
|
+
const overview = `<section class="right-overview" data-right-panel="overview"><h1>工程を選択してください</h1><p>左の依存工程図から工程を選ぶと、題名・状態・前提・後続を表示します。</p><div class="status-summary"><span>☐ 未着手 ${counts.pending}</span><span>▶ 作業中 ${counts['in-progress']}</span><span>✅ 完了 ${counts.done}</span><span>⛔ ブロック中 ${counts.blocked}</span></div>${dispatchSummary}${renderPhaseProgress(readModel)}<h2>作業中</h2>${activeLinks}</section>`;
|
|
224
|
+
const details = sections.map((section) => {
|
|
225
|
+
const key = refKey(section.ref);
|
|
226
|
+
const node = nodeByKey.get(key);
|
|
227
|
+
const status = DOCUMENT_STATUS[section.state.status] ?? { mark: '?', label: '状態不明' };
|
|
228
|
+
const lane = lookup.lanes.get(JSON.stringify([section.ref.plan_key, section.task.lane]));
|
|
229
|
+
const category = lane === undefined ? section.task.lane : `${section.task.lane} — ${lane.name}`;
|
|
230
|
+
const categoryDescription = lane === undefined ? '' : `<p class="category-description">${escapeHtmlText(lane.description)}</p>`;
|
|
231
|
+
const blockedReason = section.state.status === 'blocked'
|
|
232
|
+
? `<p><strong>ブロック理由:</strong> ${escapeHtmlText(section.state.blocked_reason ?? '理由未記録')}</p>` : '';
|
|
233
|
+
const sourceLine = section.anchorOutcome.origin_line === null ? '' : `:${section.anchorOutcome.origin_line}`;
|
|
234
|
+
const sourceRef = section.narrativeRef ?? '参照なし';
|
|
235
|
+
const anchorText = section.anchorOutcome.anchored
|
|
236
|
+
? `元plan: ${sourceRef}${sourceLine} — 行対応を確認済み`
|
|
237
|
+
: `元plan: ${sourceRef}${sourceLine} — 行対応を確認できないため、本文位置との対応は表示していません`;
|
|
238
|
+
const readiness = node?.visibility.next_ready
|
|
239
|
+
? `<p class="readiness-note">ready frontierの一員です。${ready.length > 1 ? '他のready工程と同時dispatchするのが既定です。subsetだけを選ぶ場合は理由を記録してください。' : '現在の唯一の着手候補です。'}</p>`
|
|
240
|
+
: incoming.get(key).length === 0 ? '<p class="readiness-note">登録済みの前提工程はありません。図だけではdispatch可否を判定しません。</p>' : '';
|
|
241
|
+
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}<section><h2>前提工程</h2>${renderRelationList(incoming.get(key), sectionByKey, lookup, '登録済みの前提工程はありません。')}</section><section><h2>後続工程</h2>${renderRelationList(outgoing.get(key), sectionByKey, lookup, '登録済みの後続工程はありません。')}</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>`;
|
|
242
|
+
}).join('');
|
|
243
|
+
const taskIndex = renderTaskIndex(sections, lookup);
|
|
244
|
+
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>`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function renderDiagramLegend(presentation) {
|
|
248
|
+
const categories = (presentation?.lanes ?? []).map((lane) => `<div class="category-entry"><dt><code>${escapeHtmlText(lane.lane)}</code> — ${escapeHtmlText(lane.name)}</dt><dd>${escapeHtmlText(lane.description)}</dd></div>`).join('');
|
|
249
|
+
const categoryDetails = categories === '' ? '' : `<details class="category-legend"><summary>カテゴリ説明</summary><dl>${categories}</dl></details>`;
|
|
250
|
+
return `<div class="diagram-legend" aria-label="工程図の凡例"><span>${statusMarkup('pending', ' 未着手')}</span><span>${statusMarkup('in-progress', ' 作業中')}</span><span>${statusMarkup('done', ' 完了')}</span><span>${statusMarkup('blocked', ' ブロック中')}</span><span>破線枠: ready frontier(同時dispatch推奨)</span><span>太線: 構造上の最長依存鎖</span><span>半円: 非接触の線交差</span><span>黒丸: 論理上の合流</span>${categoryDetails}<p>縦方向は時間ではなく、登録済み依存関係による工程段階です。ready frontierは全件同時dispatchが既定です。未登録の資源・host制約によりsubsetだけを選ぶ場合は理由を記録します。構造上の最長依存鎖は各工程を同じ重みとして数え、実時間・工数・納期を表しません。</p></div>`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const CSS = `
|
|
254
|
+
:root{
|
|
255
|
+
color-scheme:light;
|
|
256
|
+
--surface-1:#fcfcfb;
|
|
257
|
+
--surface-2:#f4f4f2;
|
|
258
|
+
--text-primary:#0b0b0b;
|
|
259
|
+
--text-secondary:#52514e;
|
|
260
|
+
--border:#d9d8d4;
|
|
261
|
+
--accent:#2a78d6;
|
|
262
|
+
--good:#0ca30c;
|
|
263
|
+
--critical:#d03b3b;
|
|
264
|
+
font-family:system-ui,-apple-system,"Hiragino Sans","Yu Gothic UI",sans-serif;
|
|
265
|
+
font-size:13.5px;
|
|
266
|
+
font-weight:400;
|
|
267
|
+
line-height:1.6;
|
|
268
|
+
}
|
|
269
|
+
*{box-sizing:border-box}
|
|
270
|
+
body{display:grid;grid-template-rows:minmax(0,1fr);height:100vh;margin:0;background:var(--surface-1);color:var(--text-primary)}
|
|
271
|
+
.shell{display:grid;grid-template-columns:minmax(0,var(--split,58%)) auto minmax(24rem,1fr);min-width:0;min-height:0}
|
|
272
|
+
.gantt-pane{display:grid;grid-template-rows:auto auto minmax(0,1fr);min-width:0;min-height:0;overflow:hidden;background:var(--surface-1)}
|
|
273
|
+
.pane-divider{width:8px;cursor:col-resize;background:rgba(217,216,212,.5);touch-action:none}
|
|
274
|
+
.diagram-toolbar{z-index:3;display:flex;align-items:center;gap:8px;padding:8px 16px;border-bottom:1px solid var(--border);background:var(--surface-2);color:var(--text-secondary)}
|
|
275
|
+
.diagram-toolbar button{min-height:32px;padding:0 8px;border:1px solid var(--border);border-radius:4px;background:var(--surface-2);color:var(--text-primary);font:500 12px/1.6 system-ui,-apple-system,"Hiragino Sans","Yu Gothic UI",sans-serif}
|
|
276
|
+
.diagram-toolbar button:focus-visible{outline:2px solid var(--text-primary);outline-offset:2px}
|
|
277
|
+
.zoom-readout{min-width:48px;text-align:center;font-size:12px;font-weight:500;font-variant-numeric:tabular-nums}
|
|
278
|
+
.diagram-note{margin-left:auto;color:var(--text-secondary);font-size:12px;font-weight:500}
|
|
279
|
+
.project-heading{margin-right:8px;color:var(--text-primary);font-size:13px;font-weight:650;white-space:nowrap}.status-symbol.status-in-progress{color:var(--accent)}.status-symbol.status-done{color:var(--good)}.status-symbol.status-blocked{color:var(--critical)}
|
|
280
|
+
.diagram-legend{display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px;padding:8px 16px;border-bottom:1px solid var(--border);background:var(--surface-1);color:var(--text-secondary);font-size:12px;font-weight:500}
|
|
281
|
+
.diagram-legend>span{white-space:nowrap}.diagram-legend>p{flex:1 0 100%;margin:0;font-weight:400}
|
|
282
|
+
.category-legend{margin-left:auto}.category-legend summary{cursor:pointer;color:var(--text-primary)}
|
|
283
|
+
.category-legend dl{position:absolute;z-index:4;right:16px;max-width:38rem;margin:8px 0 0;padding:12px 16px;border:1px solid var(--border);background:var(--surface-1);box-shadow:0 4px 16px rgba(11,11,11,.12)}
|
|
284
|
+
.category-entry+ .category-entry{margin-top:8px}.category-entry dt{color:var(--text-primary)}.category-entry dd{margin:0;color:var(--text-secondary);font-weight:400}
|
|
285
|
+
.diagram-scroll{min-width:0;min-height:0;max-width:calc(100% - 16px);margin:8px;overflow:auto;overscroll-behavior:contain;border:1px solid rgba(217,216,212,.5)}
|
|
286
|
+
.todo-gantt{display:block;max-width:none}
|
|
287
|
+
.narrative-pane{min-width:0;overflow:auto;background:var(--surface-1)}
|
|
288
|
+
[hidden]{display:none!important}
|
|
289
|
+
.right-toolbar{position:sticky;z-index:3;top:0;display:flex;gap:8px;padding:8px 16px;border-bottom:1px solid var(--border);background:var(--surface-1)}
|
|
290
|
+
.right-toolbar button,.relation-list button,.active-list button,.task-index-list button{padding:6px 8px;border:1px solid var(--border);border-radius:4px;background:var(--surface-2);color:var(--text-primary);font:500 12px/1.6 system-ui,-apple-system,"Hiragino Sans","Yu Gothic UI",sans-serif;text-align:left;cursor:pointer}
|
|
291
|
+
.right-toolbar button:focus-visible,.relation-list button:focus-visible,.active-list button:focus-visible,.task-index-list button:focus-visible{outline:2px solid var(--text-primary);outline-offset:2px}
|
|
292
|
+
.right-content{max-width:72ch;margin:0 auto;padding:16px 24px 48px}
|
|
293
|
+
.right-overview h1,.task-detail h1{margin:0 0 16px;font-size:19px;font-weight:650;line-height:1.45}
|
|
294
|
+
.right-overview h2,.task-detail h2{margin:24px 0 8px;font-size:16px;font-weight:600}
|
|
295
|
+
.status-summary{display:flex;flex-wrap:wrap;gap:8px 16px;margin:16px 0;padding:12px;background:var(--surface-2)}
|
|
296
|
+
.phase-overview>p{color:var(--text-secondary)}.phase-overview>ol{display:grid;gap:8px;margin:0;padding:0;list-style:none}.phase-progress{padding:10px 12px;border:1px solid var(--border);border-left-width:4px;background:var(--surface-2)}.phase-progress>header{display:flex;justify-content:space-between;gap:12px}.phase-progress>p{margin:4px 0;color:var(--text-secondary);font-size:12px}.phase-progress progress{display:block;width:100%}.phase-progress.status-accepted{border-left-color:var(--good)}.phase-progress.status-reviewing,.phase-progress.status-gate_ready{border-left-color:var(--accent)}.phase-progress.status-rejected{border-left-color:var(--critical)}
|
|
297
|
+
.active-list,.relation-list{margin:0;padding:0;list-style:none}.active-list li+li,.relation-list li+li{margin-top:8px}
|
|
298
|
+
.active-list button{width:100%}.anchor-status,.readiness-note,.category-description,.relation-empty{color:var(--text-secondary)}
|
|
299
|
+
.task-detail>header{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px}.detail-status,.detail-reference{font-size:12px;font-weight:600}.detail-reference{color:var(--text-secondary)}
|
|
300
|
+
.relation-list button{display:flex;width:100%;flex-direction:column}.relation-list button span{font-weight:400}.relation-kind{display:block;margin-top:4px;color:var(--text-secondary);font-size:12px}
|
|
301
|
+
.task-diagnostics{margin:16px 0;color:var(--text-secondary);font-size:12px}.task-diagnostics summary{cursor:pointer;color:var(--text-primary);font-weight:600}.task-diagnostics dl{display:grid;grid-template-columns:auto 1fr;gap:4px 12px}.task-diagnostics dd{margin:0;overflow-wrap:anywhere}
|
|
302
|
+
.task-index>h1{margin:0 0 8px;font-size:19px;font-weight:650;line-height:1.45}.task-index>p{margin:0 0 24px;color:var(--text-secondary)}
|
|
303
|
+
.task-index-plan+.task-index-plan{margin-top:32px}.task-index-plan h2{margin:0 0 12px;font-size:16px;font-weight:600}.task-index-plan h2 code{font-size:inherit}
|
|
304
|
+
.task-index-list{margin:0;padding:0;list-style:none}.task-index-list li+li{margin-top:8px}.task-index-list button{display:grid;width:100%;grid-template-columns:1.5rem auto minmax(0,1fr);gap:4px 8px;align-items:baseline}
|
|
305
|
+
.task-index-status{grid-row:1 / span 2;color:var(--text-secondary);font-size:13.5px;text-align:center}.task-index-status.status-in-progress{color:var(--accent)}.task-index-status.status-done{color:var(--good)}.task-index-status.status-blocked{color:var(--critical)}
|
|
306
|
+
.task-index-reference{color:var(--text-secondary);font-size:12px;white-space:nowrap}.task-index-list strong{font-size:13.5px;font-weight:600;overflow-wrap:anywhere}.task-index-blocked-reason{grid-column:2 / -1;color:var(--text-secondary);font-size:12px;overflow-wrap:anywhere}
|
|
307
|
+
.todo-gantt text{font-family:system-ui,-apple-system,"Hiragino Sans","Yu Gothic UI",sans-serif;pointer-events:none}
|
|
308
|
+
.todo-node .node-surface{fill:var(--surface-2);stroke:var(--border);stroke-width:1}
|
|
309
|
+
.todo-node .node-meta{fill:var(--text-secondary);font-size:12px;font-weight:500}
|
|
310
|
+
.todo-node .node-title{fill:var(--text-primary);font-size:13.5px;font-weight:400}
|
|
311
|
+
.todo-node .node-title-line{font-size:13.5px;font-weight:400}
|
|
312
|
+
.todo-node .status-mark{fill:var(--text-secondary);font-size:13.5px;font-weight:400}
|
|
313
|
+
.status-in-progress .node-surface{fill:var(--surface-1);stroke:var(--accent);stroke-width:2}
|
|
314
|
+
.status-in-progress .status-mark{fill:var(--accent)}
|
|
315
|
+
.status-in-progress .status-bar{stroke:var(--accent);stroke-width:2;stroke-linecap:round}
|
|
316
|
+
.status-done .node-surface{fill:var(--surface-2);stroke:var(--border);stroke-width:1}
|
|
317
|
+
.status-done .status-mark{fill:var(--good)}
|
|
318
|
+
.status-blocked .node-surface{fill:var(--surface-1);stroke:var(--critical);stroke-width:2}
|
|
319
|
+
.status-blocked .status-mark{fill:var(--critical)}
|
|
320
|
+
.next-ready-node .node-surface{stroke:var(--accent);stroke-width:2;stroke-dasharray:4 3}
|
|
321
|
+
.todo-node:focus .node-surface,.selected-node .node-surface{stroke:var(--text-primary);stroke-width:2.5}
|
|
322
|
+
.dependency-edge .edge-route{fill:none;stroke:var(--text-secondary);stroke-width:1.5;stroke-linejoin:round;opacity:.4}
|
|
323
|
+
.dependency-edge .edge-arrow{fill:var(--text-secondary);opacity:.7}
|
|
324
|
+
.longest-chain-edge .edge-route,.selected-incident-edge .edge-route{stroke:var(--text-primary);stroke-width:2.5;opacity:1}
|
|
325
|
+
.longest-chain-edge .edge-arrow,.selected-incident-edge .edge-arrow{fill:var(--text-primary);opacity:1}
|
|
326
|
+
.join-marker circle{fill:var(--text-primary);stroke:none}
|
|
327
|
+
.join-contact-marker{fill:var(--text-primary);stroke:none}.join-connector .edge-route{fill:none;stroke:var(--text-secondary);stroke-width:1.5;opacity:.7}
|
|
328
|
+
.summary-container{fill:var(--surface-1);stroke:var(--border);stroke-width:1;stroke-opacity:.5}
|
|
329
|
+
.summary-chip{fill:var(--surface-2);stroke:none}
|
|
330
|
+
.summary-plan text{fill:var(--text-primary);font-size:12px;font-weight:500}
|
|
331
|
+
.summary-lane text{fill:var(--text-secondary);font-size:12px;font-weight:500}
|
|
332
|
+
.summary-lane{cursor:pointer}
|
|
333
|
+
.lane-dimmed{opacity:.35}
|
|
334
|
+
@media(max-width:900px){body{display:block;height:auto}.shell{display:block}.pane-divider{display:none}.gantt-pane,.narrative-pane{height:70vh}.gantt-pane{border-bottom:1px solid var(--border)}}
|
|
335
|
+
`;
|
|
336
|
+
|
|
337
|
+
const CONTROLLER = `
|
|
338
|
+
(()=>{
|
|
339
|
+
const root=document.querySelector('[data-gantt-root]');if(!root)return;
|
|
340
|
+
const shell=root.querySelector('.shell');
|
|
341
|
+
const divider=root.querySelector('[data-pane-divider]');
|
|
342
|
+
const overviewPanel=root.querySelector('[data-right-panel="overview"]');
|
|
343
|
+
const detailsPanel=root.querySelector('[data-right-panel="details"]');
|
|
344
|
+
const taskIndexPanel=root.querySelector('[data-right-panel="task-index"]');
|
|
345
|
+
const selectedReturnButton=root.querySelector('[data-show-selected]');
|
|
346
|
+
const detailPanels=[...root.querySelectorAll('[data-detail-key]')];
|
|
347
|
+
const nodes=[...root.querySelectorAll('[data-node-key]')];
|
|
348
|
+
const edges=[...root.querySelectorAll('[data-edge-id]')];
|
|
349
|
+
const laneChips=[...root.querySelectorAll('.summary-lane[data-lane-key]')];
|
|
350
|
+
const svg=root.querySelector('[data-gantt-svg]');
|
|
351
|
+
const scroller=root.querySelector('[data-diagram-scroll]');
|
|
352
|
+
const zoomOutput=root.querySelector('[data-zoom-output]');
|
|
353
|
+
const baseWidth=Number(svg?.dataset.svgWidth??0);
|
|
354
|
+
const baseHeight=Number(svg?.dataset.svgHeight??0);
|
|
355
|
+
let zoom=1;let activeLaneKey=null;let selectedKey=null;let resizePointerId=null;
|
|
356
|
+
const stacked=()=>window.matchMedia('(max-width:900px)').matches;
|
|
357
|
+
const setSplit=(clientX)=>{if(!shell||stacked())return;const bounds=shell.getBoundingClientRect();if(bounds.width<=0)return;const percent=Math.max(30,Math.min(75,(clientX-bounds.left)/bounds.width*100));shell.style.setProperty('--split',percent+'%');};
|
|
358
|
+
const finishResize=(event)=>{if(event.pointerId!==resizePointerId)return;if(divider?.hasPointerCapture(event.pointerId))divider.releasePointerCapture(event.pointerId);resizePointerId=null;};
|
|
359
|
+
const setZoom=(value,minimum=.2)=>{if(!svg||!Number.isFinite(value)||value<=0)return;zoom=Math.max(minimum,Math.min(2,value));svg.setAttribute('width',String(Math.max(1,Math.round(baseWidth*zoom))));svg.setAttribute('height',String(Math.max(1,Math.round(baseHeight*zoom))));if(zoomOutput){const percent=zoom>=.1?String(Math.round(zoom*100)):String(Number((zoom*100).toFixed(1)));zoomOutput.textContent=percent+'%';}};
|
|
360
|
+
const applyLane=(key)=>{activeLaneKey=key;for(const chip of laneChips)chip.setAttribute('aria-pressed',String(chip.dataset.laneKey===key));for(const node of nodes)node.classList.toggle('lane-dimmed',key!==null&&node.dataset.laneKey!==key);for(const edge of edges)edge.classList.toggle('lane-dimmed',key!==null&&edge.dataset.fromLaneKey!==key&&edge.dataset.toLaneKey!==key);};
|
|
361
|
+
const toggleLane=(key)=>applyLane(activeLaneKey===key?null:key);
|
|
362
|
+
const showPanel=(name)=>{if(overviewPanel)overviewPanel.hidden=name!=='overview';if(detailsPanel)detailsPanel.hidden=name!=='details';if(taskIndexPanel)taskIndexPanel.hidden=name!=='task-index';if(selectedReturnButton)selectedReturnButton.hidden=name!=='task-index'||selectedKey===null;root.dataset.viewState=name;};
|
|
363
|
+
const syncSelection=()=>{for(const detail of detailPanels)detail.hidden=detail.dataset.detailKey!==selectedKey;for(const node of nodes){const selected=node.dataset.nodeKey===selectedKey;node.setAttribute('aria-selected',String(selected));node.classList.toggle('selected-node',selected);}for(const edge of edges){const selected=selectedKey!==null;edge.classList.toggle('selected-incident-edge',selected&&(edge.dataset.fromNodeKey===selectedKey||edge.dataset.toNodeKey===selectedKey));}};
|
|
364
|
+
const showOverview=()=>{selectedKey=null;syncSelection();showPanel('overview');};
|
|
365
|
+
const showTaskIndex=()=>showPanel('task-index');
|
|
366
|
+
const showSelected=()=>{if(selectedKey===null){showOverview();return;}const target=detailPanels.find(detail=>detail.dataset.detailKey===selectedKey);showPanel('details');target?.scrollIntoView({block:'start'});};
|
|
367
|
+
const select=(key)=>{const target=detailPanels.find(detail=>detail.dataset.detailKey===key);if(!target)return;selectedKey=key;syncSelection();showPanel('details');target.scrollIntoView({block:'start'});};
|
|
368
|
+
root.addEventListener('click',event=>{
|
|
369
|
+
const overviewButton=event.target.closest('[data-show-overview]');if(overviewButton&&root.contains(overviewButton)){showOverview();return;}
|
|
370
|
+
const selectedButton=event.target.closest('[data-show-selected]');if(selectedButton&&root.contains(selectedButton)){showSelected();return;}
|
|
371
|
+
const taskIndexButton=event.target.closest('[data-show-task-index]');if(taskIndexButton&&root.contains(taskIndexButton)){showTaskIndex();return;}
|
|
372
|
+
const selectButton=event.target.closest('[data-select-node-key]');if(selectButton&&root.contains(selectButton)){select(selectButton.dataset.selectNodeKey);return;}
|
|
373
|
+
const zoomButton=event.target.closest('[data-zoom-action]');if(zoomButton&&root.contains(zoomButton)){const action=zoomButton.dataset.zoomAction;if(action==='in')setZoom(zoom<1&&zoom*1.25>=1?1:zoom*1.25,.001);else if(action==='out')setZoom(zoom>1&&zoom/1.25<=1?1:zoom/1.25,.001);else if(action==='reset')setZoom(1);else if(action==='fit'&&scroller){setZoom(Math.min(1,(scroller.clientWidth-16)/baseWidth),.001);scroller.scrollTo(0,0);}return;}
|
|
374
|
+
const laneChip=event.target.closest('.summary-lane[data-lane-key]');if(laneChip&&root.contains(laneChip)){toggleLane(laneChip.dataset.laneKey);return;}
|
|
375
|
+
const node=event.target.closest('[data-node-key]');if(node&&root.contains(node))select(node.dataset.nodeKey);
|
|
376
|
+
});
|
|
377
|
+
root.addEventListener('dblclick',event=>{const paneDivider=event.target.closest('[data-pane-divider]');if(paneDivider&&root.contains(paneDivider)&&shell&&!stacked())shell.style.setProperty('--split','58%');});
|
|
378
|
+
root.addEventListener('pointerdown',event=>{const paneDivider=event.target.closest('[data-pane-divider]');if(!paneDivider||!root.contains(paneDivider)||stacked())return;resizePointerId=event.pointerId;paneDivider.setPointerCapture(event.pointerId);setSplit(event.clientX);event.preventDefault();});
|
|
379
|
+
root.addEventListener('pointermove',event=>{if(event.pointerId===resizePointerId)setSplit(event.clientX);});
|
|
380
|
+
root.addEventListener('pointerup',finishResize);root.addEventListener('pointercancel',finishResize);
|
|
381
|
+
root.addEventListener('keydown',event=>{if(event.key==='Escape'){event.preventDefault();showOverview();applyLane(null);return;}const laneChip=event.target.closest('.summary-lane[data-lane-key]');if(laneChip&&(event.key==='Enter'||event.key===' ')){event.preventDefault();toggleLane(laneChip.dataset.laneKey);return;}const node=event.target.closest('[data-node-key]');if(node&&(event.key==='Enter'||event.key===' ')){event.preventDefault();select(node.dataset.nodeKey);}});
|
|
382
|
+
showPanel('overview');
|
|
383
|
+
})();
|
|
384
|
+
`;
|
|
385
|
+
|
|
386
|
+
export function renderTodoGanttHtml({
|
|
387
|
+
readModel, layout, narratives = [], anchorOutcomes = [], presentation = null, metadata = {},
|
|
388
|
+
}) {
|
|
389
|
+
if (readModel?.schema !== 'lattice.todo_store_read.v1' || !Array.isArray(readModel.members)) {
|
|
390
|
+
throw new TypeError('readModel must be lattice.todo_store_read.v1');
|
|
391
|
+
}
|
|
392
|
+
if (!Array.isArray(narratives)) throw new TypeError('narratives must be an array');
|
|
393
|
+
if (!Array.isArray(anchorOutcomes)) throw new TypeError('anchorOutcomes must be an array');
|
|
394
|
+
if (presentation !== null
|
|
395
|
+
&& (presentation?.schema !== 'lattice.todo_gantt_presentation_model.v1'
|
|
396
|
+
|| presentation.project_id !== readModel.project_id)) {
|
|
397
|
+
throw new TypeError('presentation must be lattice.todo_gantt_presentation_model.v1');
|
|
398
|
+
}
|
|
399
|
+
const normalized = normalizeSections(readModel, narratives, anchorOutcomes);
|
|
400
|
+
const displayName = projectDisplayName(readModel, metadata);
|
|
401
|
+
const svg = renderTodoGanttSvg(layout, { presentation });
|
|
402
|
+
const rightPane = renderRightPane(normalized.sections, layout, presentation, readModel);
|
|
403
|
+
const staticData = serializeJsonForScript({
|
|
404
|
+
renderer_version: TODO_GANTT_RENDERER_VERSION,
|
|
405
|
+
metadata,
|
|
406
|
+
presentation,
|
|
407
|
+
});
|
|
408
|
+
const html = `<!doctype html><html lang="ja"><head><meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Lattice — ${escapeHtmlText(displayName)} 依存工程図</title><style>${CSS}</style></head><body data-gantt-root data-view-state="overview"><main class="shell"><section class="gantt-pane" aria-label="${escapeHtmlAttribute(displayName)} 依存工程図"><div class="diagram-toolbar" role="group" aria-label="図のズーム"><strong class="project-heading">${escapeHtmlText(displayName)} 依存工程図</strong><button type="button" data-zoom-action="out" aria-label="縮小">−</button><button type="button" data-zoom-action="reset">等倍</button><button type="button" data-zoom-action="in" aria-label="拡大">+</button><button type="button" data-zoom-action="fit">全体表示</button><output class="zoom-readout" data-zoom-output aria-live="polite">100%</output><span class="diagram-note">縦=依存段階(時間ではない)</span></div>${renderDiagramLegend(presentation)}<div class="diagram-scroll" data-diagram-scroll tabindex="0" aria-label="縦方向を主にスクロール可能な依存工程図">${svg}</div></section><div class="pane-divider" data-pane-divider aria-hidden="true"></div><aside class="narrative-pane" aria-label="選択工程の詳細と全工程一覧">${rightPane}</aside></main><script type="application/json" id="todo-gantt-data">${staticData}</script><script>${CONTROLLER}</script></body></html>`;
|
|
409
|
+
const htmlBytes = Buffer.byteLength(html, 'utf8');
|
|
410
|
+
if (htmlBytes > TODO_GANTT_HTML_MAX_BYTES) {
|
|
411
|
+
throw new TodoGanttRenderError('TODO_SCALE_EXCEEDED', 'todo gantt HTML limit exceeded', {
|
|
412
|
+
html_bytes: htmlBytes, html_limit: TODO_GANTT_HTML_MAX_BYTES,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return { html, html_digest: digest(html), prose_bytes: normalized.proseBytes, html_bytes: htmlBytes };
|
|
416
|
+
}
|