@yemi33/minions 0.1.2448 → 0.1.2449
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/bin/cli-api-client.js +1 -1
- package/bin/install-internal-minions.js +1382 -44
- package/bin/install-layout.js +150 -0
- package/bin/minions.js +460 -167
- package/dashboard/docs/typography.md +65 -12
- package/dashboard/js/command-center.js +58 -4
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +179 -20
- package/dashboard/js/refresh.js +148 -12
- package/dashboard/js/render-dispatch.js +3 -4
- package/dashboard/js/render-inbox.js +2 -2
- package/dashboard/js/render-other.js +3 -3
- package/dashboard/js/render-pipelines.js +14 -0
- package/dashboard/js/render-plans.js +57 -9
- package/dashboard/js/render-prd.js +132 -23
- package/dashboard/js/render-prs.js +195 -166
- package/dashboard/js/render-schedules.js +63 -3
- package/dashboard/js/render-utils.js +3 -3
- package/dashboard/js/render-watches.js +19 -3
- package/dashboard/js/render-work-items.js +238 -30
- package/dashboard/js/settings.js +205 -54
- package/dashboard/js/utils.js +51 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard/pages/qa.html +1 -16
- package/dashboard/pages/work.html +40 -0
- package/dashboard/shared/cc-limits.js +79 -0
- package/dashboard/shared/pr-filters.js +21 -38
- package/dashboard/shared/project-git-summary.js +1 -1
- package/dashboard/shared/record-filters.js +169 -0
- package/dashboard/shared/watches-source.js +1 -1
- package/dashboard/shared/welcome-popup.js +1 -1
- package/dashboard/shared/wi-filters.js +302 -0
- package/dashboard/slim/body.html +1 -0
- package/dashboard/slim/js/command-send.js +26 -0
- package/dashboard/slim/js/modals-tiles.js +380 -39
- package/dashboard/slim/js/status.js +13 -21
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/panel-bootstrap.js +6 -2
- package/dashboard/slim/styles.css +38 -0
- package/dashboard/styles.css +159 -55
- package/dashboard-build.js +13 -2
- package/dashboard.js +956 -423
- package/docs/README.md +11 -6
- package/docs/api-errors.md +2 -2
- package/docs/architecture-review-2026-07-09.md +1 -1
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +18 -9
- package/docs/branch-derivation.md +4 -4
- package/docs/capture-demos.js +39 -2
- package/docs/ci-runner-canary.md +123 -0
- package/docs/claude-md-propagation.md +2 -2
- package/docs/cloud-agent-dispatch.md +204 -0
- package/docs/command-center.md +7 -7
- package/docs/completion-reports.md +43 -20
- package/docs/constants.md +10 -3
- package/docs/constellation-bridge.md +134 -6
- package/docs/constellation-style-telemetry.md +4 -4
- package/docs/contracts/capability-protocol.v1.json +165 -0
- package/docs/cooldown-merge-semantics.md +12 -12
- package/docs/copilot-cli-schema.md +7 -7
- package/docs/cross-repo-plans.md +10 -10
- package/docs/dead-code-audit-retractions.md +5 -5
- package/docs/default-branch-ci.md +173 -0
- package/docs/deprecated.json +31 -31
- package/docs/design-inbox-entries-schema.md +3 -3
- package/docs/design-language.md +1051 -0
- package/docs/design-state-storage.md +11 -11
- package/docs/diagnostics-crash-reports.md +9 -9
- package/docs/diagnostics-memory.md +5 -5
- package/docs/documentation-audit-2026-07-09.md +7 -7
- package/docs/engine-restart.md +90 -5
- package/docs/harness-mode.md +1 -1
- package/docs/internal-install.md +338 -39
- package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
- package/docs/kb-pr3223-cascade-archiving.md +1 -1
- package/docs/kb-pr696-merge-conflict-docs.md +6 -6
- package/docs/kb-sweep.md +35 -35
- package/docs/keep-processes.md +1 -1
- package/docs/live-checkout-mode.md +30 -30
- package/docs/managed-spawn.md +18 -14
- package/docs/named-agents.md +7 -7
- package/docs/plan-lifecycle.md +69 -2
- package/docs/pr-author-identity.md +114 -0
- package/docs/pr-auto-fix-dispatch.md +19 -4
- package/docs/pr-comment-followup.md +6 -6
- package/docs/pr-review-fix-loop.md +59 -10
- package/docs/process-termination.md +40 -0
- package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
- package/docs/qa-runbook-lifecycle.md +367 -17
- package/docs/qa-runbooks.md +3 -3
- package/docs/rfc-completion-json.md +18 -18
- package/docs/runtime-adapters.md +26 -21
- package/docs/security.md +6 -6
- package/docs/self-improvement.md +4 -4
- package/docs/shared-lifecycle-module-map.md +473 -472
- package/docs/skills.md +52 -3
- package/docs/slim-ux/concepts.md +121 -116
- package/docs/specs/agent-configurability.md +18 -18
- package/docs/specs/agent-rename.md +18 -18
- package/docs/team-memory.md +38 -21
- package/docs/timeouts-and-liveness.md +118 -10
- package/docs/tutorials/01-install-and-connect.md +1 -1
- package/docs/watches.md +40 -39
- package/docs/workspace-manifests.md +4 -4
- package/docs/worktree-lifecycle.md +293 -14
- package/engine/README.md +46 -0
- package/engine/{ado-comment.js → ado/comment.js} +8 -8
- package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
- package/engine/{ado.js → ado/index.js} +417 -63
- package/engine/{ado-status.js → ado/status.js} +6 -8
- package/engine/{ado-token.js → ado/token.js} +1 -1
- package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
- package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
- package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
- package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
- package/engine/{harness-context.js → agents/harness-context.js} +5 -5
- package/engine/{harness.js → agents/harness.js} +3 -3
- package/engine/{llm.js → agents/llm.js} +18 -14
- package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
- package/engine/{playbook.js → agents/playbook.js} +155 -22
- package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
- package/engine/{preflight.js → agents/preflight.js} +29 -10
- package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
- package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
- package/engine/{steering.js → agents/steering.js} +5 -5
- package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
- package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
- package/engine/{api-validation.js → api/api-validation.js} +1 -1
- package/engine/api/bridge.js +787 -0
- package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
- package/engine/api/companion.js +560 -0
- package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
- package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
- package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
- package/engine/api-contracts/agent-content.js +4 -4
- package/engine/api-contracts/capability-manifest.js +236 -0
- package/engine/api-contracts/capability-protocol.js +333 -0
- package/engine/api-contracts/cc-ops.js +1 -1
- package/engine/api-contracts/config-runtime.js +5 -0
- package/engine/api-contracts/core.js +28 -1
- package/engine/api-contracts/index.js +100 -0
- package/engine/api-contracts/orchestration.js +18 -5
- package/engine/api-contracts/pull-requests.js +37 -6
- package/engine/api-contracts/qa-process.js +29 -6
- package/engine/api-contracts/work-plan-prd.js +21 -1
- package/engine/cloud/contract.js +212 -0
- package/engine/cloud/index.js +159 -0
- package/engine/{execution-model.js → core/execution-model.js} +1 -1
- package/engine/{features.js → core/features.js} +4 -4
- package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
- package/engine/{queries.js → core/queries.js} +201 -36
- package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
- package/engine/{shared.js → core/shared.js} +1637 -175
- package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
- package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
- package/engine/db/index.js +11 -2
- package/engine/db/migrations/002-dispatches.js +3 -3
- package/engine/db/migrations/003-work-items.js +1 -1
- package/engine/db/migrations/004-pull-requests.js +1 -1
- package/engine/db/migrations/006-metrics.js +1 -1
- package/engine/db/migrations/007-watches.js +2 -2
- package/engine/db/migrations/008-small-state.js +1 -1
- package/engine/db/migrations/009-qa.js +1 -1
- package/engine/db/migrations/010-pr-links.js +1 -1
- package/engine/db/migrations/011-remaining-state.js +1 -1
- package/engine/db/migrations/012-steering-deliveries.js +2 -2
- package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
- package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/db/migrations/018-sql-only-cutover.js +2 -2
- package/engine/db/migrations/021-archived-work-items.js +1 -1
- package/engine/db/migrations/022-global-cc-session.js +1 -1
- package/engine/db/migrations/023-engine-state.js +1 -1
- package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
- package/engine/db/migrations/029-repair-reused-versions.js +20 -0
- package/engine/db/migrations/031-pr-author-identity.js +137 -0
- package/engine/{consolidation.js → memory/consolidation.js} +6 -6
- package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
- package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
- package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
- package/engine/{memory-store.js → memory/memory-store.js} +3 -3
- package/engine/{promotion.js → memory/promotion.js} +3 -3
- package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
- package/engine/{review-learning.js → memory/review-learning.js} +10 -5
- package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
- package/engine/{logs-store.js → observability/logs-store.js} +5 -5
- package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
- package/engine/{check-status.js → operations/check-status.js} +3 -3
- package/engine/{cli.js → operations/cli.js} +271 -113
- package/engine/{distribution.js → operations/distribution.js} +5 -6
- package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
- package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
- package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
- package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
- package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
- package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
- package/engine/{meeting.js → orchestration/meeting.js} +6 -16
- package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
- package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
- package/engine/{routing.js → orchestration/routing.js} +3 -3
- package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
- package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
- package/engine/{timeout.js → orchestration/timeout.js} +158 -109
- package/engine/{db-events.js → persistence/db-events.js} +2 -2
- package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
- package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
- package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
- package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
- package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
- package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
- package/engine/persistence/state-operations.js +350 -0
- package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
- package/engine/{issues.js → planning/issues.js} +2 -2
- package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
- package/engine/planning/prd-result-sidecar.js +190 -0
- package/engine/{prd-store.js → planning/prd-store.js} +17 -17
- package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
- package/engine/{projects.js → planning/projects.js} +10 -10
- package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
- package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
- package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
- package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
- package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
- package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
- package/engine/{process-utils.js → processes/process-utils.js} +599 -55
- package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
- package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
- package/engine/{comment-format.js → providers/comment-format.js} +5 -5
- package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
- package/engine/{gh-token.js → providers/gh-token.js} +4 -4
- package/engine/{github.js → providers/github.js} +131 -54
- package/engine/{pr-action.js → providers/pr-action.js} +13 -12
- package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
- package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
- package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
- package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
- package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
- package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
- package/engine/{pr-track.js → providers/pr-track.js} +11 -13
- package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
- package/engine/qa/auto-prd-qa.js +313 -0
- package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
- package/engine/qa/prd-session.js +240 -0
- package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
- package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
- package/engine/{qa-runs.js → qa/runs.js} +286 -15
- package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
- package/engine/qa/visual-journey.js +654 -0
- package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
- package/engine/qa-runners/maestro.js +3 -3
- package/engine/qa-runners/playwright.js +2 -2
- package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
- package/engine/recovery/stop-stack.js +607 -0
- package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
- package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
- package/engine/runtimes/claude.js +14 -12
- package/engine/runtimes/codex.js +8 -6
- package/engine/runtimes/copilot.js +17 -16
- package/engine/{watch-actions.js → watches/actions.js} +13 -13
- package/engine/{watches.js → watches/index.js} +43 -32
- package/engine/{watches-store.js → watches/store.js} +4 -4
- package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
- package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
- package/engine/worktrees/inventory.js +671 -0
- package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
- package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
- package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
- package/engine/worktrees/quarantine-refs.js +173 -0
- package/engine.js +1137 -208
- package/minions.js +147 -77
- package/package.json +10 -6
- package/playbooks/_pr-description-audit.md +110 -78
- package/playbooks/build-fix-complex.md +2 -0
- package/playbooks/fix.md +16 -12
- package/playbooks/implement-shared.md +2 -0
- package/playbooks/implement.md +19 -20
- package/playbooks/plan-to-prd.md +18 -3
- package/playbooks/qa-session-draft.md +136 -1
- package/playbooks/qa-session-execute.md +80 -2
- package/playbooks/qa-session-setup.md +17 -1
- package/playbooks/qa-validate.md +1 -1
- package/playbooks/setup.md +2 -0
- package/playbooks/shared-rules.md +25 -32
- package/playbooks/templates/followup-dispatch.md +4 -3
- package/playbooks/verify.md +1 -1
- package/prompts/cc-system.md +19 -27
- package/watch-plugins/README.md +92 -0
- package/watch-plugins/ado-author-prs.js +336 -0
- package/watch-plugins/gh-author-prs.js +375 -0
- package/watch-plugins/http.js +474 -0
- package/watch-plugins/teams-channel.js +869 -0
- package/docs/dev-composite-workflow.md +0 -101
- package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
- package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
- package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/visual-evidence-ci.md +0 -103
- package/engine/bridge.js +0 -379
- package/engine/quarantine-refs.js +0 -103
- package/engine/state-operations.js +0 -178
- /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
package/dashboard/slim/body.html
CHANGED
|
@@ -112,6 +112,7 @@
|
|
|
112
112
|
<div class="cockpit-detail">no open PRs</div>
|
|
113
113
|
</div>
|
|
114
114
|
<div class="cockpit-tile" data-tile="automation">
|
|
115
|
+
<button id="slim-tile-automation-add" class="linkpr-chip on-tile" type="button" title="Add automation" aria-label="Add automation" aria-haspopup="menu" aria-expanded="false">+</button>
|
|
115
116
|
<div class="cockpit-label"><span class="cockpit-dot"></span> Automation</div>
|
|
116
117
|
<div class="cockpit-value dim">0</div>
|
|
117
118
|
<div class="cockpit-detail">no automation set</div>
|
|
@@ -30,6 +30,18 @@
|
|
|
30
30
|
function sendMessage() {
|
|
31
31
|
var text = inputEl.value.trim();
|
|
32
32
|
if (!text) return;
|
|
33
|
+
// Length guard BEFORE clearing the composer / queueing (W-mscjjqdh). An
|
|
34
|
+
// oversized message fails server validation as `body.message has an invalid
|
|
35
|
+
// length`; persisting it would also poison the shared cc-tabs transcript the
|
|
36
|
+
// classic surface carries. Keep the rejected text in the box for editing.
|
|
37
|
+
var lenCheck = window.MinionsCcLimits ? window.MinionsCcLimits.checkMessage(text) : { ok: true };
|
|
38
|
+
if (!lenCheck.ok) {
|
|
39
|
+
appendSystemNotice(window.MinionsCcLimits
|
|
40
|
+
? window.MinionsCcLimits.messageTooLongText(lenCheck.length, lenCheck.limit)
|
|
41
|
+
: 'Message is too long. Shorten it and send again — your text is still in the box.');
|
|
42
|
+
inputEl.focus();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
33
45
|
inputEl.value = '';
|
|
34
46
|
inputEl.style.height = 'auto';
|
|
35
47
|
if (_isTabSending(tabId)) {
|
|
@@ -91,6 +103,20 @@
|
|
|
91
103
|
var turnOutcome = 'done';
|
|
92
104
|
var live = (turnTabId === tabId);
|
|
93
105
|
|
|
106
|
+
// Defensive length guard for paths that bypass sendMessage()'s composer check:
|
|
107
|
+
// queue drain (_drainQueueFor) and any programmatic/retry resend (W-mscjjqdh).
|
|
108
|
+
// Never record (recordTabMessage → shared cc-tabs) or POST an oversized
|
|
109
|
+
// message — drop it with a notice on the visible tab instead of looping.
|
|
110
|
+
var guard = window.MinionsCcLimits ? window.MinionsCcLimits.checkMessage(text) : { ok: true };
|
|
111
|
+
if (!guard.ok) {
|
|
112
|
+
if (live) {
|
|
113
|
+
appendSystemNotice(window.MinionsCcLimits
|
|
114
|
+
? window.MinionsCcLimits.messageTooLongText(guard.length, guard.limit)
|
|
115
|
+
: 'Message is too long. Shorten it and send again — your text is still in the box.');
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
94
120
|
// The tab title is derived from the first user message; only that message
|
|
95
121
|
// changes the chip, so re-render the bar once instead of on every send.
|
|
96
122
|
var wasFirstUser = !_tabHasUserMessage(turnTabId);
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
if (typeof _pauseActiveEmbed === 'function') _pauseActiveEmbed();
|
|
22
22
|
// The Automation composite is likewise cached + kept mounted (W-ms4r1t5q…);
|
|
23
23
|
// hide it and suspend its tab iframes' pollers rather than tearing it down.
|
|
24
|
+
// NOTE: the tab buttons tracked in _automationTabBtns stay live across
|
|
25
|
+
// opens now that the composite is never torn down, so they must NOT be
|
|
26
|
+
// cleared here — _applyAutomationTabLabels keeps writing to mounted nodes.
|
|
24
27
|
if (typeof _hideAutomationComposite === 'function') _hideAutomationComposite();
|
|
25
28
|
var transient = document.getElementById('slim-tile-transient');
|
|
26
29
|
if (transient) transient.textContent = '';
|
|
@@ -174,6 +177,44 @@
|
|
|
174
177
|
return { host: host, transient: transient };
|
|
175
178
|
}
|
|
176
179
|
|
|
180
|
+
// Ensure a lightweight loading affordance element exists inside the embed
|
|
181
|
+
// host (created once). Shown immediately when a not-yet-booted embed iframe is
|
|
182
|
+
// opened so the modal never looks frozen while the heavier /engine SPA boots;
|
|
183
|
+
// hidden the instant that iframe fires 'load'. Built with createElement (no
|
|
184
|
+
// innerHTML) to satisfy the dashboard no-unsanitized lint gate.
|
|
185
|
+
function _ensureEmbedLoadingEl() {
|
|
186
|
+
var containers = _ensureTileContainers();
|
|
187
|
+
if (!containers) return null;
|
|
188
|
+
var el = document.getElementById('slim-tile-embed-loading');
|
|
189
|
+
if (!el) {
|
|
190
|
+
el = document.createElement('div');
|
|
191
|
+
el.id = 'slim-tile-embed-loading';
|
|
192
|
+
el.className = 'slim-tile-embed-loading';
|
|
193
|
+
el.setAttribute('role', 'status');
|
|
194
|
+
el.setAttribute('aria-live', 'polite');
|
|
195
|
+
el.hidden = true;
|
|
196
|
+
var spinner = document.createElement('div');
|
|
197
|
+
spinner.className = 'slim-tile-embed-spinner';
|
|
198
|
+
el.appendChild(spinner);
|
|
199
|
+
var label = document.createElement('div');
|
|
200
|
+
label.className = 'slim-tile-embed-loading-label';
|
|
201
|
+
label.textContent = 'Loading…';
|
|
202
|
+
el.appendChild(label);
|
|
203
|
+
containers.host.appendChild(el);
|
|
204
|
+
}
|
|
205
|
+
return el;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function _showEmbedLoading() {
|
|
209
|
+
var el = _ensureEmbedLoadingEl();
|
|
210
|
+
if (el) el.hidden = false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function _hideEmbedLoading() {
|
|
214
|
+
var el = document.getElementById('slim-tile-embed-loading');
|
|
215
|
+
if (el) el.hidden = true;
|
|
216
|
+
}
|
|
217
|
+
|
|
177
218
|
// Create (once) the cached iframe for an embed tile and return it. Built with
|
|
178
219
|
// createElement (no innerHTML) to satisfy the dashboard no-unsanitized lint
|
|
179
220
|
// gate. Starts hidden; a load-time visibility message pauses its poll until
|
|
@@ -189,6 +230,15 @@
|
|
|
189
230
|
frame.src = spec[0];
|
|
190
231
|
frame.title = spec[1];
|
|
191
232
|
frame.hidden = true;
|
|
233
|
+
frame.dataset.embedLoaded = '0';
|
|
234
|
+
frame.addEventListener('load', function() {
|
|
235
|
+
frame.dataset.embedLoaded = '1';
|
|
236
|
+
// If this frame is the one currently shown, drop the loading affordance.
|
|
237
|
+
if (_activeEmbedKey === key) _hideEmbedLoading();
|
|
238
|
+
});
|
|
239
|
+
// Visibility posting is delegated to the shared helper, which evaluates the
|
|
240
|
+
// LIVE visibility at load time (not bind time) so a frame that finished
|
|
241
|
+
// loading while hidden still gets suspended.
|
|
192
242
|
_bindEmbedVisibilityOnLoad(frame, function() { return _activeEmbedKey === key; });
|
|
193
243
|
containers.host.appendChild(frame);
|
|
194
244
|
_embedFrames[key] = frame;
|
|
@@ -198,7 +248,7 @@
|
|
|
198
248
|
// Show one embed tile's cached iframe and hide the rest, pausing/resuming
|
|
199
249
|
// each one's embedded poll to match. Reused by every single-iframe embed tile.
|
|
200
250
|
function _showReusableEmbed(key) {
|
|
201
|
-
_getOrCreateEmbedFrame(key);
|
|
251
|
+
var frame = _getOrCreateEmbedFrame(key);
|
|
202
252
|
Object.keys(_embedFrames).forEach(function(k) {
|
|
203
253
|
var f = _embedFrames[k];
|
|
204
254
|
if (!f) return;
|
|
@@ -207,6 +257,13 @@
|
|
|
207
257
|
_postEmbedVisibility(f, on);
|
|
208
258
|
});
|
|
209
259
|
_activeEmbedKey = key;
|
|
260
|
+
// Immediate loading affordance while a not-yet-booted embed finishes: the
|
|
261
|
+
// frame's 'load' handler hides it (or it's hidden now if already booted).
|
|
262
|
+
if (frame && frame.dataset && frame.dataset.embedLoaded !== '1') {
|
|
263
|
+
_showEmbedLoading();
|
|
264
|
+
} else {
|
|
265
|
+
_hideEmbedLoading();
|
|
266
|
+
}
|
|
210
267
|
}
|
|
211
268
|
|
|
212
269
|
// Hide + pause whichever embed iframe is currently shown (kept mounted for an
|
|
@@ -219,6 +276,7 @@
|
|
|
219
276
|
f.hidden = true;
|
|
220
277
|
});
|
|
221
278
|
_activeEmbedKey = null;
|
|
279
|
+
_hideEmbedLoading();
|
|
222
280
|
}
|
|
223
281
|
|
|
224
282
|
// Prewarm the two hot embed tiles (Queued work + Active PRs) shortly after the
|
|
@@ -229,8 +287,35 @@
|
|
|
229
287
|
_getOrCreateEmbedFrame('queued');
|
|
230
288
|
_getOrCreateEmbedFrame('prs');
|
|
231
289
|
}
|
|
290
|
+
// Engine is heavier than Work/PRs (it cold-boots the full classic SPA plus the
|
|
291
|
+
// engine log, managed-process panel, and memory/diagnostics pollers), so it is
|
|
292
|
+
// NOT folded into the 800ms hot burst — that would regress cockpit first paint.
|
|
293
|
+
// Instead warm it on an idle callback AFTER the hot tiles, so its boot overlaps
|
|
294
|
+
// idle time and its first open is a near-instant show. Predictive prewarm on
|
|
295
|
+
// tile intent (below) is the earlier, finer-grained trigger; this is the
|
|
296
|
+
// guaranteed fallback for users who click without hovering first.
|
|
297
|
+
// (W-ms4odb8h009a5d16)
|
|
298
|
+
function prewarmEngineEmbedIdle() {
|
|
299
|
+
_getOrCreateEmbedFrame('engine');
|
|
300
|
+
}
|
|
301
|
+
// Predictive prewarm: the instant a user shows intent on an embed tile
|
|
302
|
+
// (hover / focus / pointer-down), start booting its cached iframe so the
|
|
303
|
+
// heavier /engine screen overlaps intent instead of blocking the click.
|
|
304
|
+
// _getOrCreateEmbedFrame is create-once/idempotent, so repeat intents are free
|
|
305
|
+
// and the already-prewarmed queued/prs tiles are unaffected.
|
|
306
|
+
function _predictivePrewarmEmbed(key) {
|
|
307
|
+
if (!Object.prototype.hasOwnProperty.call(_EMBED_TILES, key)) return;
|
|
308
|
+
try { _getOrCreateEmbedFrame(key); } catch (e) { /* lazy-create on open instead */ }
|
|
309
|
+
}
|
|
232
310
|
(function schedulePrewarm() {
|
|
233
311
|
try { setTimeout(prewarmHotEmbeds, 800); } catch (e) { /* no timers — skip prewarm */ }
|
|
312
|
+
try {
|
|
313
|
+
if (typeof requestIdleCallback === 'function') {
|
|
314
|
+
requestIdleCallback(prewarmEngineEmbedIdle, { timeout: 4000 });
|
|
315
|
+
} else {
|
|
316
|
+
setTimeout(prewarmEngineEmbedIdle, 2500);
|
|
317
|
+
}
|
|
318
|
+
} catch (e) { /* no idle/timers — engine warms on first intent/open instead */ }
|
|
234
319
|
})();
|
|
235
320
|
|
|
236
321
|
// Per-tile body renderers for the detail modal. Each reads the latest status
|
|
@@ -312,12 +397,107 @@
|
|
|
312
397
|
_showReusableEmbed('prs');
|
|
313
398
|
}
|
|
314
399
|
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
400
|
+
// ── Automation tile counts (Watches · Scheduled Tasks · Pipelines) ────
|
|
401
|
+
// The cockpit tile headline is the SUM of all three automation surfaces.
|
|
402
|
+
// Watches ride the live 5s /api/status poll (renderAutomationTile is fed the
|
|
403
|
+
// live watches count by applyStatus). Schedules + pipelines are NOT carried by
|
|
404
|
+
// the status snapshot, so they are fetched lazily off the hot poll path — once
|
|
405
|
+
// shortly after first paint and again whenever the Automation modal opens —
|
|
406
|
+
// mirroring knowledge.js#loadKnowledgeCounts / plans.js#loadPlansCounts. The
|
|
407
|
+
// same cached counts drive the "(N)" suffix on each modal tab label.
|
|
408
|
+
var _automationCounts = { watches: 0, schedules: null, pipelines: null };
|
|
409
|
+
// Live tab buttons ({ key: {el, base} }) so loadAutomationCounts can refresh
|
|
410
|
+
// their "(N)" labels. The composite is built once and kept mounted, so these
|
|
411
|
+
// stay valid across opens and are never cleared.
|
|
412
|
+
var _automationTabBtns = null;
|
|
413
|
+
// "+ Add" picker target: { tab, create } consumed by renderAutomationTileBody.
|
|
414
|
+
var _automationInitial = null;
|
|
415
|
+
// Last (count|detail|lit) signature painted onto the Automation cockpit tile,
|
|
416
|
+
// so the 5s status poll can skip a redundant updateTile when nothing changed
|
|
417
|
+
// (W-ms4r1t5q003h341a). Lives here because the painted value is the SUM, which
|
|
418
|
+
// also moves when the lazy schedule/pipeline counts land.
|
|
419
|
+
var _automationTileSig = null;
|
|
420
|
+
// Monotonic counter making each "+ Add" deep-link URL unique, so re-requesting
|
|
421
|
+
// Create on an ALREADY-MOUNTED cached tab frame is a real navigation (a bare
|
|
422
|
+
// "#create" hash change on the same URL would not reload the classic screen,
|
|
423
|
+
// and its boot hook only runs on load).
|
|
424
|
+
var _automationCreateSeq = 0;
|
|
425
|
+
|
|
426
|
+
function _automationTabCount(key) {
|
|
427
|
+
if (key === 'watches') return _automationCounts.watches;
|
|
428
|
+
if (key === 'schedule') return _automationCounts.schedules;
|
|
429
|
+
if (key === 'pipelines') return _automationCounts.pipelines;
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function _automationTabLabel(base, count) {
|
|
434
|
+
return base + ' (' + (count == null ? '…' : count) + ')';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function _applyAutomationTabLabels() {
|
|
438
|
+
if (!_automationTabBtns) return;
|
|
439
|
+
Object.keys(_automationTabBtns).forEach(function(key) {
|
|
440
|
+
var b = _automationTabBtns[key];
|
|
441
|
+
if (b && b.el) b.el.textContent = _automationTabLabel(b.base, _automationTabCount(key));
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Repaint the cockpit tile from the cached sum. Called each 5s poll from
|
|
446
|
+
// applyStatus (with the live watches count) and after the lazy count fetch.
|
|
447
|
+
// Change-gated: the poll runs every 5s, so skip the updateTile DOM work
|
|
448
|
+
// (querySelectors + textContent + classList churn) when nothing moved.
|
|
449
|
+
function renderAutomationTile(watchesCount) {
|
|
450
|
+
if (typeof watchesCount === 'number') _automationCounts.watches = watchesCount;
|
|
451
|
+
var w = _automationCounts.watches || 0;
|
|
452
|
+
var s = _automationCounts.schedules || 0;
|
|
453
|
+
var p = _automationCounts.pipelines || 0;
|
|
454
|
+
var total = w + s + p;
|
|
455
|
+
var autoDetail = total ? 'monitoring' : 'no automation set';
|
|
456
|
+
var autoLit = total ? 'blue' : null;
|
|
457
|
+
var autoSig = total + '|' + autoDetail + '|' + (autoLit || '');
|
|
458
|
+
if (_automationTileSig !== autoSig) {
|
|
459
|
+
_automationTileSig = autoSig;
|
|
460
|
+
updateTile('automation', total, autoDetail, autoLit);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Best-effort lazy fetch of the three automation-surface counts. Runs off the
|
|
465
|
+
// hot 5s status poll. Failures leave the cached value untouched so a transient
|
|
466
|
+
// error doesn't blank the tile / tab labels. /api/watches responds
|
|
467
|
+
// { watches: [...] } (unwrapped via the shared source); /api/schedules and
|
|
468
|
+
// /api/pipelines respond bare arrays.
|
|
469
|
+
async function loadAutomationCounts() {
|
|
470
|
+
try {
|
|
471
|
+
var wres = await fetch('/api/watches', { headers: { 'Accept': 'application/json' } });
|
|
472
|
+
if (wres.ok) {
|
|
473
|
+
var wlist = (typeof MinionsWatchesSource !== 'undefined' && MinionsWatchesSource.unwrap)
|
|
474
|
+
? MinionsWatchesSource.unwrap(await wres.json())
|
|
475
|
+
: null;
|
|
476
|
+
if (Array.isArray(wlist)) _automationCounts.watches = wlist.length;
|
|
477
|
+
}
|
|
478
|
+
} catch (e) { /* keep last-known */ }
|
|
479
|
+
try {
|
|
480
|
+
var sres = await fetch('/api/schedules', { headers: { 'Accept': 'application/json' } });
|
|
481
|
+
if (sres.ok) { var sdata = await sres.json(); _automationCounts.schedules = Array.isArray(sdata) ? sdata.length : 0; }
|
|
482
|
+
} catch (e) { /* keep last-known */ }
|
|
483
|
+
try {
|
|
484
|
+
var pres = await fetch('/api/pipelines', { headers: { 'Accept': 'application/json' } });
|
|
485
|
+
if (pres.ok) { var pdata = await pres.json(); _automationCounts.pipelines = Array.isArray(pdata) ? pdata.length : 0; }
|
|
486
|
+
} catch (e) { /* keep last-known */ }
|
|
487
|
+
renderAutomationTile();
|
|
488
|
+
_applyAutomationTabLabels();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Automation tile body — a tabbed composite (Watches · Scheduled Tasks ·
|
|
492
|
+
// Pipelines) that reuses the LITERAL classic dashboard screens (/watches,
|
|
493
|
+
// /schedule, /pipelines) instead of a slim-specific list, so there is one
|
|
494
|
+
// Automation UI, not two (W-mrz0x2a400030f90 / "reuse, don't fork").
|
|
495
|
+
//
|
|
496
|
+
// Persistent Automation composite (W-ms4r1t5q003h341a): the composite + its
|
|
497
|
+
// lazy iframes are built ONCE into #slim-tile-automation-host and shown/hidden
|
|
498
|
+
// across opens — mirroring the _embedFrames cache used by the single-iframe
|
|
499
|
+
// tiles — so reopening the Automation tile is an instant show, NOT a rebuild
|
|
500
|
+
// that cold-reloads all three classic screens. { activate, openCreate, frames,
|
|
321
501
|
// activeKey } or null until first open.
|
|
322
502
|
var _automationComposite = null;
|
|
323
503
|
|
|
@@ -354,21 +534,23 @@
|
|
|
354
534
|
}
|
|
355
535
|
}
|
|
356
536
|
|
|
357
|
-
// Build the tabbed Watches·
|
|
358
|
-
// persistent host. Returns { activate, frames, activeKey }. Each
|
|
359
|
-
// is LAZY (mounted only on first activation) and CACHED (kept
|
|
360
|
-
// reopening never rebuilds/reloads it). Hidden tabs' pollers are
|
|
361
|
-
// the same visibility message the single-iframe embeds use, so a
|
|
362
|
-
// inactive tab isn't polling in the background. Built with
|
|
537
|
+
// Build the tabbed Watches·Scheduled Tasks·Pipelines composite ONCE into the
|
|
538
|
+
// persistent host. Returns { activate, openCreate, frames, activeKey }. Each
|
|
539
|
+
// tab's iframe is LAZY (mounted only on first activation) and CACHED (kept
|
|
540
|
+
// mounted so reopening never rebuilds/reloads it). Hidden tabs' pollers are
|
|
541
|
+
// suspended via the same visibility message the single-iframe embeds use, so a
|
|
542
|
+
// cached-but-inactive tab isn't polling in the background. Built with
|
|
543
|
+
// createElement (no
|
|
363
544
|
// innerHTML) to satisfy the dashboard no-unsanitized lint gate; tab buttons
|
|
364
545
|
// reuse the shared .kn-tab pill primitive and carry role="tab"/aria-selected
|
|
365
546
|
// for keyboard/AT parity with the Knowledge modal tabs.
|
|
366
547
|
function _buildAutomationComposite(host) {
|
|
367
548
|
var TABS = [
|
|
368
|
-
{ key: 'watches',
|
|
369
|
-
{ key: 'schedule',
|
|
370
|
-
{ key: 'pipelines',
|
|
549
|
+
{ key: 'watches', base: 'Watches', src: '/watches?embed=1' },
|
|
550
|
+
{ key: 'schedule', base: 'Scheduled Tasks', src: '/schedule?embed=1' },
|
|
551
|
+
{ key: 'pipelines', base: 'Pipelines', src: '/pipelines?embed=1' },
|
|
371
552
|
];
|
|
553
|
+
|
|
372
554
|
var tablist = document.createElement('div');
|
|
373
555
|
tablist.className = 'kn-tabs slim-automation-tabs';
|
|
374
556
|
tablist.setAttribute('role', 'tablist');
|
|
@@ -380,9 +562,15 @@
|
|
|
380
562
|
var btns = {};
|
|
381
563
|
var panels = {};
|
|
382
564
|
var frames = {};
|
|
383
|
-
var composite = { activate: null, frames: frames, activeKey: null };
|
|
565
|
+
var composite = { activate: null, openCreate: null, frames: frames, activeKey: null };
|
|
566
|
+
// Track the live tab buttons so loadAutomationCounts can refresh the "(N)"
|
|
567
|
+
// labels once the list endpoints resolve. The composite is built once and
|
|
568
|
+
// kept mounted, so these stay valid for the life of the page.
|
|
569
|
+
_automationTabBtns = {};
|
|
384
570
|
|
|
385
|
-
|
|
571
|
+
// `createSrc`, when given, is the deep-link URL the ACTIVE tab's iframe must
|
|
572
|
+
// load instead of its plain src (see openCreate).
|
|
573
|
+
function activate(key, createSrc) {
|
|
386
574
|
TABS.forEach(function(t) {
|
|
387
575
|
var isActive = t.key === key;
|
|
388
576
|
var btn = btns[t.key];
|
|
@@ -395,8 +583,10 @@
|
|
|
395
583
|
if (isActive && !frames[t.key]) {
|
|
396
584
|
var frame = document.createElement('iframe');
|
|
397
585
|
frame.className = 'slim-automation-embed';
|
|
398
|
-
|
|
399
|
-
|
|
586
|
+
// Mount straight at the deep-link when one was requested, so the
|
|
587
|
+
// create flow costs one load, not a load + immediate re-navigate.
|
|
588
|
+
frame.src = createSrc || t.src;
|
|
589
|
+
frame.title = t.base;
|
|
400
590
|
// A visibility message posted before this frame navigates is dropped
|
|
401
591
|
// on about:blank, so re-post the live state once it has loaded —
|
|
402
592
|
// otherwise a tab hidden mid-load becomes a permanent background
|
|
@@ -406,6 +596,9 @@
|
|
|
406
596
|
});
|
|
407
597
|
panel.appendChild(frame);
|
|
408
598
|
frames[t.key] = frame;
|
|
599
|
+
} else if (isActive && createSrc) {
|
|
600
|
+
// Already-cached frame: re-navigate it to run the deep-link boot hook.
|
|
601
|
+
frames[t.key].src = createSrc;
|
|
409
602
|
}
|
|
410
603
|
// Suspend hidden cached frames' pollers; resume the shown one.
|
|
411
604
|
if (frames[t.key] && typeof _postEmbedVisibility === 'function') {
|
|
@@ -416,16 +609,32 @@
|
|
|
416
609
|
}
|
|
417
610
|
composite.activate = activate;
|
|
418
611
|
|
|
612
|
+
// Activate `key` AND make its embedded classic screen auto-open its own
|
|
613
|
+
// Create dialog via the ?embed=1#create deep-link (the classic create flow is
|
|
614
|
+
// reused, not forked — CLAUDE.md slim-vs-classic). The tab's iframe is cached
|
|
615
|
+
// across opens, so a bare "#create" hash on an already-loaded frame would be
|
|
616
|
+
// a same-document fragment change that never reloads the page and never runs
|
|
617
|
+
// its boot hook — hence the unique `&create=<seq>` cache-buster, which the
|
|
618
|
+
// classic screen ignores.
|
|
619
|
+
function openCreate(key) {
|
|
620
|
+
var tab = null;
|
|
621
|
+
TABS.forEach(function(t) { if (t.key === key) tab = t; });
|
|
622
|
+
if (!tab) { activate(key); return; }
|
|
623
|
+
activate(key, tab.src + '&create=' + (++_automationCreateSeq) + '#create');
|
|
624
|
+
}
|
|
625
|
+
composite.openCreate = openCreate;
|
|
626
|
+
|
|
419
627
|
TABS.forEach(function(t) {
|
|
420
628
|
var btn = document.createElement('button');
|
|
421
629
|
btn.type = 'button';
|
|
422
630
|
btn.className = 'kn-tab';
|
|
423
631
|
btn.id = 'slim-automation-tab-' + t.key;
|
|
424
|
-
btn.textContent = t.
|
|
632
|
+
btn.textContent = _automationTabLabel(t.base, _automationTabCount(t.key));
|
|
425
633
|
btn.setAttribute('role', 'tab');
|
|
426
634
|
btn.setAttribute('aria-controls', 'slim-automation-panel-' + t.key);
|
|
427
635
|
btn.addEventListener('click', function() { activate(t.key); });
|
|
428
636
|
btns[t.key] = btn;
|
|
637
|
+
_automationTabBtns[t.key] = { el: btn, base: t.base };
|
|
429
638
|
tablist.appendChild(btn);
|
|
430
639
|
|
|
431
640
|
var panel = document.createElement('div');
|
|
@@ -443,22 +652,22 @@
|
|
|
443
652
|
return composite;
|
|
444
653
|
}
|
|
445
654
|
|
|
446
|
-
// Automation tile body — a tabbed composite (Watches ·
|
|
447
|
-
// that reuses the LITERAL classic dashboard screens (/watches,
|
|
448
|
-
// /pipelines) instead of a slim-specific list, so there is one
|
|
449
|
-
// not two (W-mrz0x2a400030f90 / "reuse, don't fork" — mirrors
|
|
450
|
-
// for the Plans tile and renderPrsBody for the PRs tile). Each
|
|
451
|
-
// real classic screen in an iframe with the chrome-off ?embed=1
|
|
452
|
-
// iframed page IS the classic screen — full create/edit/pause/run +
|
|
453
|
-
// actions, backed by the same /api/{watches,schedules,pipelines} endpoints
|
|
454
|
-
// with zero duplicated rendering logic. Classic is reachable at each route
|
|
455
|
-
// with slim-ux ON (only / is taken over). The composite + its lazy iframes
|
|
456
|
-
// CACHED in the persistent #slim-tile-automation-host and SHOWN/HIDDEN
|
|
457
|
-
// opens (W-ms4r1t5q003h341a) rather than rebuilt into the wiped
|
|
458
|
-
// container — so reopening the tile is an instant show, not a rebuild
|
|
459
|
-
// cold-reloads all three classic screens. Mirrors the _embedFrames reuse
|
|
460
|
-
// single-iframe tiles already use. The `body` arg (transient) is
|
|
461
|
-
// ignored; the composite lives in its own persistent host.
|
|
655
|
+
// Automation tile body — a tabbed composite (Watches · Scheduled Tasks ·
|
|
656
|
+
// Pipelines) that reuses the LITERAL classic dashboard screens (/watches,
|
|
657
|
+
// /schedule, /pipelines) instead of a slim-specific list, so there is one
|
|
658
|
+
// Automation UI, not two (W-mrz0x2a400030f90 / "reuse, don't fork" — mirrors
|
|
659
|
+
// renderPlansBody for the Plans tile and renderPrsBody for the PRs tile). Each
|
|
660
|
+
// tab embeds the real classic screen in an iframe with the chrome-off ?embed=1
|
|
661
|
+
// mode; the iframed page IS the classic screen — full create/edit/pause/run +
|
|
662
|
+
// row actions, backed by the same /api/{watches,schedules,pipelines} endpoints
|
|
663
|
+
// — with zero duplicated rendering logic. Classic is reachable at each route
|
|
664
|
+
// even with slim-ux ON (only / is taken over). The composite + its lazy iframes
|
|
665
|
+
// are CACHED in the persistent #slim-tile-automation-host and SHOWN/HIDDEN
|
|
666
|
+
// across opens (W-ms4r1t5q003h341a) rather than rebuilt into the wiped
|
|
667
|
+
// transient container — so reopening the tile is an instant show, not a rebuild
|
|
668
|
+
// that cold-reloads all three classic screens. Mirrors the _embedFrames reuse
|
|
669
|
+
// the single-iframe tiles already use. The `body` arg (transient) is
|
|
670
|
+
// intentionally ignored; the composite lives in its own persistent host.
|
|
462
671
|
function renderAutomationTileBody(/* body */) {
|
|
463
672
|
var host = _ensureAutomationHost();
|
|
464
673
|
if (!host) return;
|
|
@@ -466,8 +675,23 @@
|
|
|
466
675
|
_automationComposite = _buildAutomationComposite(host);
|
|
467
676
|
}
|
|
468
677
|
host.hidden = false;
|
|
469
|
-
//
|
|
470
|
-
|
|
678
|
+
// Slim "+ Add" picker → open on a specific tab with that tab's classic
|
|
679
|
+
// create dialog auto-opened. Consumed HERE (every open) rather than inside
|
|
680
|
+
// _buildAutomationComposite, which only runs once now that the composite is
|
|
681
|
+
// cached — otherwise "+ Add" would work only on the very first open.
|
|
682
|
+
var initial = _automationInitial;
|
|
683
|
+
_automationInitial = null;
|
|
684
|
+
if (initial && initial.create) {
|
|
685
|
+
_automationComposite.openCreate(initial.tab || 'watches');
|
|
686
|
+
} else {
|
|
687
|
+
// Reopen on the last-active tab (default Watches on first open).
|
|
688
|
+
_automationComposite.activate(initial && initial.tab
|
|
689
|
+
? initial.tab
|
|
690
|
+
: (_automationComposite.activeKey || 'watches'));
|
|
691
|
+
}
|
|
692
|
+
// Refresh the live "(N)" counts (and the cockpit tile sum) whenever the
|
|
693
|
+
// modal opens — these are off the hot 5s status poll (loadAutomationCounts).
|
|
694
|
+
if (typeof loadAutomationCounts === 'function') loadAutomationCounts();
|
|
471
695
|
}
|
|
472
696
|
|
|
473
697
|
// tile key -> { title, render }. Adding a tile is a one-line table edit.
|
|
@@ -542,6 +766,12 @@
|
|
|
542
766
|
if (ev.target !== tile) return; // ignore keys from inner controls (e.g. the Link-PR chip)
|
|
543
767
|
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); openTileModal(key); }
|
|
544
768
|
});
|
|
769
|
+
// Predictive prewarm: start booting the embed the instant the user shows
|
|
770
|
+
// intent (hover / focus / pointer-down) so the click lands on an
|
|
771
|
+
// already-booting iframe rather than a cold SPA boot (esp. Engine).
|
|
772
|
+
tile.addEventListener('pointerenter', function() { _predictivePrewarmEmbed(key); });
|
|
773
|
+
tile.addEventListener('pointerdown', function() { _predictivePrewarmEmbed(key); });
|
|
774
|
+
tile.addEventListener('focus', function() { _predictivePrewarmEmbed(key); });
|
|
545
775
|
})(tiles[i]);
|
|
546
776
|
}
|
|
547
777
|
})();
|
|
@@ -556,3 +786,114 @@
|
|
|
556
786
|
(function bindPlansTileCount() {
|
|
557
787
|
if (typeof loadPlansCounts === 'function') setTimeout(loadPlansCounts, 1500);
|
|
558
788
|
})();
|
|
789
|
+
|
|
790
|
+
// ── Automation tile wiring ─────────────────────────────────────────
|
|
791
|
+
// (1) Lazy-seed the summed cockpit count (watches + scheduled tasks +
|
|
792
|
+
// pipelines) shortly after first paint, mirroring the Plans/Knowledge
|
|
793
|
+
// tiles — the schedule/pipeline counts are not on the hot status poll.
|
|
794
|
+
// (2) The on-tile "+ Add" chip opens a small picker (Watch / Scheduled Task /
|
|
795
|
+
// Pipeline); picking one opens the Automation modal on the matching tab and
|
|
796
|
+
// auto-opens that tab's classic Create dialog via openAutomationCreate.
|
|
797
|
+
(function bindAutomationTileCount() {
|
|
798
|
+
if (typeof loadAutomationCounts === 'function') setTimeout(loadAutomationCounts, 1500);
|
|
799
|
+
})();
|
|
800
|
+
|
|
801
|
+
// Open the Automation modal on `tabKey` with the embedded classic screen's
|
|
802
|
+
// Create dialog auto-opened (?embed=1#create). tabKey is a TABS key
|
|
803
|
+
// ('watches' | 'schedule' | 'pipelines').
|
|
804
|
+
function openAutomationCreate(tabKey) {
|
|
805
|
+
_automationInitial = { tab: tabKey, create: true };
|
|
806
|
+
openTileModal('automation');
|
|
807
|
+
// Single-call handoff: the automation view consumes (and nulls) the flag
|
|
808
|
+
// synchronously during openTileModal's render. Clearing here covers the
|
|
809
|
+
// path where openTileModal early-returns because the modal shell is
|
|
810
|
+
// missing — otherwise the flag would survive and a later plain tile click
|
|
811
|
+
// would silently open in create mode.
|
|
812
|
+
_automationInitial = null;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
var _automationAddMenu = null;
|
|
816
|
+
function closeAutomationAddPicker() {
|
|
817
|
+
var menu = _automationAddMenu;
|
|
818
|
+
// openAutomationAddPicker moves focus INTO the menu, so removing that node
|
|
819
|
+
// would drop focus to <body> and lose a keyboard user's place. Reclaim it
|
|
820
|
+
// for the chip (WAI-ARIA menu-button pattern) — but ONLY when focus is
|
|
821
|
+
// still inside the menu we're removing, so the open-path reset call and an
|
|
822
|
+
// outside click that already landed on another control keep their focus.
|
|
823
|
+
// Must be sampled BEFORE removeChild: once detached, activeElement is body.
|
|
824
|
+
var reclaimFocus = !!(menu && document.activeElement
|
|
825
|
+
&& (menu === document.activeElement
|
|
826
|
+
|| (menu.contains && menu.contains(document.activeElement))));
|
|
827
|
+
if (menu && menu.parentNode) {
|
|
828
|
+
menu.parentNode.removeChild(menu);
|
|
829
|
+
}
|
|
830
|
+
_automationAddMenu = null;
|
|
831
|
+
document.removeEventListener('click', _automationAddOutside, true);
|
|
832
|
+
document.removeEventListener('keydown', _automationAddKeydown, true);
|
|
833
|
+
var chip = document.getElementById('slim-tile-automation-add');
|
|
834
|
+
if (chip) {
|
|
835
|
+
chip.setAttribute('aria-expanded', 'false');
|
|
836
|
+
if (reclaimFocus && typeof chip.focus === 'function') chip.focus();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function _automationAddOutside(ev) {
|
|
840
|
+
if (_automationAddMenu && !_automationAddMenu.contains(ev.target)
|
|
841
|
+
&& ev.target.id !== 'slim-tile-automation-add') {
|
|
842
|
+
closeAutomationAddPicker();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function _automationAddKeydown(ev) {
|
|
846
|
+
if (ev.key === 'Escape') { ev.stopPropagation(); closeAutomationAddPicker(); }
|
|
847
|
+
}
|
|
848
|
+
function openAutomationAddPicker(anchorEl) {
|
|
849
|
+
closeAutomationAddPicker();
|
|
850
|
+
var menu = document.createElement('div');
|
|
851
|
+
menu.className = 'slim-add-menu';
|
|
852
|
+
menu.id = 'slim-automation-add-menu';
|
|
853
|
+
menu.setAttribute('role', 'menu');
|
|
854
|
+
menu.setAttribute('aria-label', 'Add automation');
|
|
855
|
+
[
|
|
856
|
+
{ key: 'watches', label: 'Watch' },
|
|
857
|
+
{ key: 'schedule', label: 'Scheduled Task' },
|
|
858
|
+
{ key: 'pipelines', label: 'Pipeline' },
|
|
859
|
+
].forEach(function(o) {
|
|
860
|
+
var item = document.createElement('button');
|
|
861
|
+
item.type = 'button';
|
|
862
|
+
item.className = 'slim-add-menu-item';
|
|
863
|
+
item.setAttribute('role', 'menuitem');
|
|
864
|
+
item.textContent = o.label;
|
|
865
|
+
item.addEventListener('click', function(ev) {
|
|
866
|
+
ev.stopPropagation();
|
|
867
|
+
closeAutomationAddPicker();
|
|
868
|
+
openAutomationCreate(o.key);
|
|
869
|
+
});
|
|
870
|
+
menu.appendChild(item);
|
|
871
|
+
});
|
|
872
|
+
document.body.appendChild(menu);
|
|
873
|
+
_automationAddMenu = menu;
|
|
874
|
+
// Anchor under the chip (fixed, so tile overflow can't clip it).
|
|
875
|
+
var r = anchorEl.getBoundingClientRect();
|
|
876
|
+
menu.style.top = (r.bottom + 4) + 'px';
|
|
877
|
+
menu.style.left = Math.max(8, r.right - menu.offsetWidth) + 'px';
|
|
878
|
+
if (anchorEl.setAttribute) anchorEl.setAttribute('aria-expanded', 'true');
|
|
879
|
+
var first = menu.querySelector('.slim-add-menu-item');
|
|
880
|
+
if (first) first.focus();
|
|
881
|
+
// Defer listener attach so the opening click doesn't immediately dismiss.
|
|
882
|
+
setTimeout(function() {
|
|
883
|
+
document.addEventListener('click', _automationAddOutside, true);
|
|
884
|
+
document.addEventListener('keydown', _automationAddKeydown, true);
|
|
885
|
+
}, 0);
|
|
886
|
+
}
|
|
887
|
+
(function bindAutomationAddUi() {
|
|
888
|
+
var chip = document.getElementById('slim-tile-automation-add');
|
|
889
|
+
if (!chip) return;
|
|
890
|
+
chip.addEventListener('click', function(ev) {
|
|
891
|
+
ev.stopPropagation(); // don't trigger the cockpit tile's own click
|
|
892
|
+
if (_automationAddMenu) { closeAutomationAddPicker(); return; }
|
|
893
|
+
openAutomationAddPicker(chip);
|
|
894
|
+
});
|
|
895
|
+
// Keyboard activation on the chip shouldn't bubble to the tile handler.
|
|
896
|
+
chip.addEventListener('keydown', function(ev) {
|
|
897
|
+
if (ev.key === 'Enter' || ev.key === ' ') { ev.stopPropagation(); }
|
|
898
|
+
});
|
|
899
|
+
})();
|
|
@@ -21,10 +21,6 @@
|
|
|
21
21
|
// — only deltas from a previously-seen number trigger the fade, so opening
|
|
22
22
|
// the dashboard doesn't animate every tile.
|
|
23
23
|
var lastTileValues = Object.create(null);
|
|
24
|
-
// Last (count|detail|lit) signature painted onto the Automation cockpit tile,
|
|
25
|
-
// so applyStatus can skip a redundant updateTile on every 5s status poll when
|
|
26
|
-
// nothing changed (W-ms4r1t5q003h341a).
|
|
27
|
-
var _automationTileSig = null;
|
|
28
24
|
|
|
29
25
|
// Apply lit-state and value/detail text to a tile.
|
|
30
26
|
function updateTile(key, value, detail, lit) {
|
|
@@ -229,24 +225,20 @@
|
|
|
229
225
|
activePrs.length === 0 ? null : (failingPrs.length ? 'red' : 'blue')
|
|
230
226
|
);
|
|
231
227
|
|
|
232
|
-
// ── Automation tile (Watches ·
|
|
233
|
-
// The
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
228
|
+
// ── Automation tile (Watches · Scheduled Tasks · Pipelines) ───
|
|
229
|
+
// The cockpit headline is the SUM of all three automation surfaces. Watches
|
|
230
|
+
// ride the live 5s status poll (fed here). Schedules + pipelines are NOT in
|
|
231
|
+
// the /api/status snapshot, so modals-tiles.js#loadAutomationCounts fetches
|
|
232
|
+
// them lazily off the hot poll path (mirroring the Knowledge + Plans tiles)
|
|
233
|
+
// and caches them; renderAutomationTile combines this live watches total with
|
|
234
|
+
// those cached counts. Total watches (not just active) is used to match the
|
|
235
|
+
// "sum of all 3 set" summed-count contract.
|
|
236
|
+
// This runs on EVERY 5s status poll, so the change-check that gates the
|
|
237
|
+
// updateTile DOM work (querySelectors + textContent + classList churn) lives
|
|
238
|
+
// inside renderAutomationTile — it has to compare the SUM, which also moves
|
|
239
|
+
// when the lazy schedule/pipeline counts land (W-ms4r1t5q003h341a).
|
|
241
240
|
var watches = Array.isArray(data.watches) ? data.watches : [];
|
|
242
|
-
|
|
243
|
-
var autoDetail = activeWatches.length ? 'monitoring' : 'no automation set';
|
|
244
|
-
var autoLit = activeWatches.length ? 'blue' : null;
|
|
245
|
-
var autoSig = activeWatches.length + '|' + autoDetail + '|' + (autoLit || '');
|
|
246
|
-
if (_automationTileSig !== autoSig) {
|
|
247
|
-
_automationTileSig = autoSig;
|
|
248
|
-
updateTile('automation', activeWatches.length, autoDetail, autoLit);
|
|
249
|
-
}
|
|
241
|
+
if (typeof renderAutomationTile === 'function') renderAutomationTile(watches.length);
|
|
250
242
|
|
|
251
243
|
// ── Knowledge tile (Pinned Context + Notes + KB) ──────────────
|
|
252
244
|
// Pinned count is live from the poll; notes/KB counts are fetched lazily
|