@zq-silk/yui 2.3.0 → 2.5.0
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 +15 -1
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +18 -9
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +65 -14
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +18 -3
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +25 -20
- package/dist/controller/fileSchedulerStoreAdapter.js +39 -8
- package/dist/controller/globalInputDelivery.js +2 -4
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/providerRetryDelivery.js +4 -2
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/agentAdapter.js +3 -5
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +15 -7
- package/dist/integration/gitIntegrationService.js +7 -12
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/interaction/notificationPresentation.js +85 -0
- package/dist/interaction/operatorPresentation.js +1 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +48 -71
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +2 -1
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/leaderWakeupProcessor.js +1 -9
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +295 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +106 -8
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/cli-information-contract.md +1 -1
- package/docs/cli-information-contract.zh-CN.md +1 -1
- package/docs/managed-turn-and-session-runtime.md +31 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +22 -0
- package/docs/release-workflow.md +54 -0
- package/docs/release-workflow.zh-CN.md +39 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -4
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-operator/SKILL.md +3 -0
- package/skills/yui-reviewer/SKILL.md +4 -2
- package/skills/yui-runtime/SKILL.md +43 -2
- package/skills/yui-worker/SKILL.md +3 -2
- package/dist/web/assets/client/app.js +0 -808
- package/dist/web/assets/client/components.js +0 -477
- package/dist/web/assets/client/detail.js +0 -379
- package/dist/web/assets/client/dock.js +0 -309
- package/dist/web/assets/client/evidence.js +0 -364
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1277
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -198
- package/dist/web/assets/client/sections.js +0 -349
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -216
- package/dist/web/assets/styles/views.js +0 -309
- /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const TASK_SEARCH_KINDS = ["brief", "message", "decision", "result", "completion"];
|
|
2
|
+
/** A read-only view of authoritative JSON, not a persisted index. Apply Task
|
|
3
|
+
* scope before body matching and return only page-sized snippets/identities.
|
|
4
|
+
* No derived state needs rebuilding when a source changes or is removed. */
|
|
5
|
+
export function queryTaskSearch(db, input) {
|
|
6
|
+
if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 101) {
|
|
7
|
+
throw new Error("Task search query exceeds its bounded contract.");
|
|
8
|
+
}
|
|
9
|
+
const branches = [
|
|
10
|
+
`SELECT task_id AS taskId, 'brief' AS kind, 'task-brief' AS store, task_id AS refId,
|
|
11
|
+
j.fullkey AS field, j.value AS body FROM task_records, json_tree(brief) j
|
|
12
|
+
WHERE j.type = 'text' AND (j.key IN ('objective','technicalApproach','currentFocus','leaderSummary')
|
|
13
|
+
OR j.path = '$.boundaries')`,
|
|
14
|
+
`SELECT task_id, 'message', 'task-message', message_id, '$.body', json_extract(payload, '$.body') FROM messages`,
|
|
15
|
+
`SELECT task_id, 'decision', 'task-decision', decision_id, j.fullkey, j.value
|
|
16
|
+
FROM decisions, json_each(payload) j WHERE j.key IN ('title','rationale','supersededReason') AND j.type = 'text'`,
|
|
17
|
+
`SELECT task_id, 'result', 'run', turn_id, '$.result.output', json_extract(payload, '$.result.output') FROM turns`,
|
|
18
|
+
`SELECT task_id, 'completion', 'task', task_id, '$.completionSummary',
|
|
19
|
+
json_extract(payload, '$.completionSummary') FROM task_records`
|
|
20
|
+
];
|
|
21
|
+
const params = {
|
|
22
|
+
query: input.query, taskId: input.taskId ?? null, kind: input.kind ?? null, limit: input.limit,
|
|
23
|
+
afterTask: input.after?.taskId ?? "", afterKind: input.after?.kind ?? "",
|
|
24
|
+
afterRef: input.after?.refId ?? "", afterField: input.after?.field ?? ""
|
|
25
|
+
};
|
|
26
|
+
// SQLite lower() folds ASCII only; other Unicode is matched literally.
|
|
27
|
+
// Positions and substr use Unicode code points, not UTF-16 units or bytes.
|
|
28
|
+
return db.prepare(`WITH sources AS (${branches.join(" UNION ALL ")}),
|
|
29
|
+
matches AS (SELECT *, instr(lower(body), lower(@query)) AS hit FROM sources
|
|
30
|
+
WHERE (@taskId IS NULL OR taskId = @taskId) AND (@kind IS NULL OR kind = @kind)
|
|
31
|
+
AND (taskId, kind, refId, field) > (@afterTask, @afterKind, @afterRef, @afterField))
|
|
32
|
+
SELECT taskId, kind, store, refId, field, max(0, hit - 81) AS offset,
|
|
33
|
+
substr(body, max(1, hit - 80), 320) AS snippet
|
|
34
|
+
FROM matches WHERE hit > 0 ORDER BY taskId, kind, refId, field LIMIT @limit`).all(params);
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { knowledgeProposalFingerprint, validateProject } from "../../repository/project.js";
|
|
3
|
+
/**
|
|
4
|
+
* The declared 1.3 -> 1.4 payload transition. Never used by ordinary readers.
|
|
5
|
+
* Version 1 is the observed pre-migration head, not a claim that no earlier
|
|
6
|
+
* edits happened. Existing proposals and frozen Context snapshots retain their
|
|
7
|
+
* original identities, decisions and evidence; snapshots are never rewritten.
|
|
8
|
+
*/
|
|
9
|
+
export function migrateProjectKnowledge(db) {
|
|
10
|
+
const rows = db.prepare("SELECT id, payload FROM projects ORDER BY id").all();
|
|
11
|
+
for (const row of rows) {
|
|
12
|
+
const project = JSON.parse(row.payload);
|
|
13
|
+
if (project.id !== row.id || !Array.isArray(project.knowledge) || !Array.isArray(project.knowledgeProposals)) {
|
|
14
|
+
throw new Error(`Invalid 1.3 Project knowledge container: ${row.id}.`);
|
|
15
|
+
}
|
|
16
|
+
const knowledgeProposals = project.knowledgeProposals.map(proposal => {
|
|
17
|
+
if (proposal.schemaVersion !== 1)
|
|
18
|
+
throw new Error(`Invalid 1.3 Knowledge proposal: ${proposal.id}.`);
|
|
19
|
+
const sourceKey = [proposal.projectId, proposal.source.taskId, proposal.source.decisionId ?? "",
|
|
20
|
+
proposal.source.milestoneId ?? "", proposal.source.commitSha ?? ""].join("|");
|
|
21
|
+
const previousFingerprint = createHash("sha256")
|
|
22
|
+
.update(`${sourceKey}\u0000${proposal.title}\u0000${proposal.body}`).digest("hex");
|
|
23
|
+
if (proposal.fingerprint !== previousFingerprint) {
|
|
24
|
+
throw new Error(`Invalid 1.3 Knowledge fingerprint: ${proposal.id}.`);
|
|
25
|
+
}
|
|
26
|
+
return { ...proposal, schemaVersion: 2, fingerprint: knowledgeProposalFingerprint(proposal) };
|
|
27
|
+
});
|
|
28
|
+
const knowledge = project.knowledge.map(entry => {
|
|
29
|
+
if (entry.schemaVersion !== 1 || "version" in entry || "history" in entry
|
|
30
|
+
|| "scope" in entry || "expiresWhen" in entry) {
|
|
31
|
+
throw new Error(`Invalid 1.3 Project knowledge: ${entry.id}.`);
|
|
32
|
+
}
|
|
33
|
+
const proposal = knowledgeProposals.find(candidate => candidate.id === entry.provenance?.proposalId);
|
|
34
|
+
const backed = proposal !== undefined && proposal.status === "accepted"
|
|
35
|
+
&& proposal.knowledgeId === entry.id && proposal.title === entry.title && proposal.body === entry.body
|
|
36
|
+
&& proposal.source.taskId === entry.provenance?.taskId
|
|
37
|
+
&& proposal.source.decisionId === entry.provenance?.decisionId
|
|
38
|
+
&& proposal.source.milestoneId === entry.provenance?.milestoneId
|
|
39
|
+
&& proposal.source.commitSha === entry.provenance?.commitSha;
|
|
40
|
+
return {
|
|
41
|
+
...entry, schemaVersion: 2, version: 1, history: [],
|
|
42
|
+
...(backed ? {
|
|
43
|
+
...(proposal.scope === undefined ? {} : { scope: proposal.scope }),
|
|
44
|
+
...(proposal.expiresWhen === undefined ? {} : { expiresWhen: proposal.expiresWhen }),
|
|
45
|
+
provenance: { ...entry.provenance, fingerprint: proposal.fingerprint }
|
|
46
|
+
} : {})
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
const migrated = validateProject({ ...project, knowledge, knowledgeProposals });
|
|
50
|
+
db.prepare("UPDATE projects SET payload=? WHERE id=?").run(JSON.stringify(migrated), row.id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { runTaskCommand, validateTaskArchiveRequest } from "../commands/taskCommands.js";
|
|
2
|
+
import { taskArchiveDiagnostics } from "./archiveDiagnostics.js";
|
|
3
|
+
import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./remoteDeliveryService.js";
|
|
4
|
+
/** Read-only admission shared by CLI and Web. Cancelled Project Tasks need a
|
|
5
|
+
* workspace snapshot before delivery can be proved; that later preparation
|
|
6
|
+
* may have effects and must not be reported as a known non-submission. */
|
|
7
|
+
export function validateOrdinaryTaskArchive(store, taskId, options) {
|
|
8
|
+
validateTaskArchiveRequest([taskId, "--integrated"], store, options);
|
|
9
|
+
const task = store.getTask(taskId);
|
|
10
|
+
if (task.status !== "archived" && (task.status === "completed" || !task.projectBindings.length)) {
|
|
11
|
+
assertTaskRemoteDeliveryIntegrated(createTaskRemoteDeliveryProof(store, task).delivery);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** The local user's ordinary archive path. No force, abandonment, source
|
|
15
|
+
* impersonation, or private cleanup policy is accepted by this port. */
|
|
16
|
+
export async function archiveOrdinaryTask(coordinator, taskId, options) {
|
|
17
|
+
const { store, preparer } = coordinator;
|
|
18
|
+
const args = [taskId, "--integrated"];
|
|
19
|
+
validateOrdinaryTaskArchive(store, taskId, options);
|
|
20
|
+
const task = store.getTask(taskId);
|
|
21
|
+
if (task.status !== "archived") {
|
|
22
|
+
let candidate = null;
|
|
23
|
+
if (task.status === "cancelled" && task.projectBindings.length) {
|
|
24
|
+
await preparer.prepareTaskWorkspace(taskId);
|
|
25
|
+
const workspace = store.getTaskWorkspace(taskId);
|
|
26
|
+
if (!workspace || workspace.owner.type !== "task" || workspace.owner.taskId !== taskId) {
|
|
27
|
+
throw new Error(`Task has no authoritative main workspace: ${taskId}.`);
|
|
28
|
+
}
|
|
29
|
+
const snapshot = await preparer.snapshotDirectTaskMain(workspace, task.projectBindings.map(binding => binding.projectId));
|
|
30
|
+
candidate = { schemaVersion: 1, projects: snapshot.projects.map(project => ({
|
|
31
|
+
projectId: project.projectId, commit: project.headCommit
|
|
32
|
+
})) };
|
|
33
|
+
}
|
|
34
|
+
const proof = createTaskRemoteDeliveryProof(store, task, candidate);
|
|
35
|
+
assertTaskRemoteDeliveryIntegrated(proof.delivery);
|
|
36
|
+
await coordinator.prepareTaskForArchive(taskId);
|
|
37
|
+
runTaskCommand(["archive", ...args], store, { ...options, archiveRemoteDeliveryProof: proof });
|
|
38
|
+
await coordinator.cleanupArchivedTask(taskId, "integrated");
|
|
39
|
+
}
|
|
40
|
+
const current = store.getTask(taskId);
|
|
41
|
+
return { task: current, ...taskArchiveDiagnostics(store, current) };
|
|
42
|
+
}
|
package/dist/tmux/tmuxManager.js
CHANGED
|
@@ -263,16 +263,16 @@ export class TmuxManager {
|
|
|
263
263
|
// with terminal output that predates this attach.
|
|
264
264
|
this.run(["set-option", "-t", clientSession, "status", "off"]);
|
|
265
265
|
this.run(["set-option", "-t", clientSession, "mouse", "on"]);
|
|
266
|
+
// Arm automatic release only after a client has attached. Enabling it
|
|
267
|
+
// on the initially detached session would destroy it before attach.
|
|
268
|
+
// A client-detached hook is unsafe: after explicit session destruction,
|
|
269
|
+
// tmux can resolve that notification in a surviving session's context
|
|
270
|
+
// and execute its hook, cascading into unrelated viewer/writer leases.
|
|
266
271
|
this.run([
|
|
267
|
-
"set-hook", "-t", clientSession, "client-
|
|
268
|
-
`
|
|
272
|
+
"set-hook", "-t", clientSession, "client-attached",
|
|
273
|
+
`set-option -t ${clientSession} destroy-unattached on`
|
|
274
|
+
+ (attachedNotice === undefined ? "" : ` ; display-message -d 10000 ${tmuxWord(attachedNotice)}`)
|
|
269
275
|
]);
|
|
270
|
-
if (attachedNotice !== undefined) {
|
|
271
|
-
this.run([
|
|
272
|
-
"set-hook", "-t", clientSession, "client-attached",
|
|
273
|
-
`display-message -d 10000 ${tmuxWord(attachedNotice)}`
|
|
274
|
-
]);
|
|
275
|
-
}
|
|
276
276
|
return clientSession;
|
|
277
277
|
}
|
|
278
278
|
catch (error) {
|
|
@@ -1,63 +1,23 @@
|
|
|
1
|
+
// Every asset the dashboard serves, keyed by URL path. Stylesheets come from
|
|
2
|
+
// the ordered styles registry, client modules from the client registry, and
|
|
3
|
+
// fonts and xterm from their packages; the shell links them in this order.
|
|
1
4
|
import { readFileSync } from "node:fs";
|
|
2
5
|
import { createRequire } from "node:module";
|
|
3
|
-
import {
|
|
4
|
-
import { APP_SCRIPT } from "./client/app.js";
|
|
5
|
-
import { COMPONENTS_SCRIPT } from "./client/components.js";
|
|
6
|
-
import { DETAIL_SCRIPT } from "./client/detail.js";
|
|
7
|
-
import { DOCK_SCRIPT } from "./client/dock.js";
|
|
8
|
-
import { DOM_SCRIPT } from "./client/dom.js";
|
|
9
|
-
import { EVIDENCE_SCRIPT } from "./client/evidence.js";
|
|
10
|
-
import { FORMAT_SCRIPT } from "./client/format.js";
|
|
11
|
-
import { FORMS_SCRIPT } from "./client/forms.js";
|
|
12
|
-
import { I18N_SCRIPT } from "./client/i18n.js";
|
|
13
|
-
import { MARKDOWN_SCRIPT } from "./client/markdown.js";
|
|
14
|
-
import { OVERVIEW_SCRIPT } from "./client/overview.js";
|
|
15
|
-
import { RECORDS_SCRIPT } from "./client/records.js";
|
|
16
|
-
import { SECTIONS_SCRIPT } from "./client/sections.js";
|
|
17
|
-
import { SIDEBAR_SCRIPT } from "./client/sidebar.js";
|
|
18
|
-
import { THEME_SCRIPT } from "./client/theme.js";
|
|
19
|
-
import { DASHBOARD_HTML } from "./shell.js";
|
|
20
|
-
import { FONT_FACE_STYLES } from "./fonts.js";
|
|
6
|
+
import { APP_SCRIPT, CLIENT_MODULES } from "./client/index.js";
|
|
21
7
|
import { FONT_WOFF2_BASE64 } from "./fontData.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { LAYOUT_STYLES } from "./styles/layout.js";
|
|
25
|
-
import { MARKDOWN_STYLES } from "./styles/markdown.js";
|
|
26
|
-
import { RESPONSIVE_STYLES } from "./styles/responsive.js";
|
|
27
|
-
import { TOKEN_STYLES } from "./styles/tokens.js";
|
|
28
|
-
import { VIEW_STYLES } from "./styles/views.js";
|
|
8
|
+
import { DASHBOARD_HTML } from "./shell/index.js";
|
|
9
|
+
import { STYLESHEETS } from "./styles/index.js";
|
|
29
10
|
const require = createRequire(import.meta.url);
|
|
30
11
|
const CSS = "text/css; charset=utf-8";
|
|
31
12
|
const JS = "text/javascript; charset=utf-8";
|
|
32
13
|
export const WEB_ASSETS = Object.freeze({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"/assets/css/base.css": { contentType: CSS, body: BASE_STYLES },
|
|
36
|
-
"/assets/css/layout.css": { contentType: CSS, body: LAYOUT_STYLES },
|
|
37
|
-
"/assets/css/components.css": { contentType: CSS, body: COMPONENT_STYLES },
|
|
38
|
-
"/assets/css/views.css": { contentType: CSS, body: VIEW_STYLES },
|
|
39
|
-
"/assets/css/markdown.css": { contentType: CSS, body: MARKDOWN_STYLES },
|
|
40
|
-
"/assets/css/responsive.css": { contentType: CSS, body: RESPONSIVE_STYLES },
|
|
41
|
-
"/assets/js/dom.js": { contentType: JS, body: DOM_SCRIPT },
|
|
42
|
-
"/assets/js/format.js": { contentType: JS, body: FORMAT_SCRIPT },
|
|
43
|
-
"/assets/js/i18n.js": { contentType: JS, body: I18N_SCRIPT },
|
|
44
|
-
"/assets/js/markdown.js": { contentType: JS, body: MARKDOWN_SCRIPT },
|
|
45
|
-
"/assets/js/theme.js": { contentType: JS, body: THEME_SCRIPT },
|
|
46
|
-
"/assets/js/api.js": { contentType: JS, body: API_SCRIPT },
|
|
47
|
-
"/assets/js/components.js": { contentType: JS, body: COMPONENTS_SCRIPT },
|
|
48
|
-
"/assets/js/records.js": { contentType: JS, body: RECORDS_SCRIPT },
|
|
49
|
-
"/assets/js/forms.js": { contentType: JS, body: FORMS_SCRIPT },
|
|
50
|
-
"/assets/js/evidence.js": { contentType: JS, body: EVIDENCE_SCRIPT },
|
|
51
|
-
"/assets/js/sidebar.js": { contentType: JS, body: SIDEBAR_SCRIPT },
|
|
52
|
-
"/assets/js/overview.js": { contentType: JS, body: OVERVIEW_SCRIPT },
|
|
53
|
-
"/assets/js/sections.js": { contentType: JS, body: SECTIONS_SCRIPT },
|
|
54
|
-
"/assets/js/detail.js": { contentType: JS, body: DETAIL_SCRIPT },
|
|
55
|
-
"/assets/js/dock.js": { contentType: JS, body: DOCK_SCRIPT },
|
|
14
|
+
...Object.fromEntries(STYLESHEETS.map(({ name, body }) => [`/assets/css/${name}.css`, { contentType: CSS, body }])),
|
|
15
|
+
...Object.fromEntries(Object.entries(CLIENT_MODULES).map(([path, body]) => [`/assets/js/${path}.js`, { contentType: JS, body }])),
|
|
56
16
|
"/assets/app.js": { contentType: JS, body: APP_SCRIPT },
|
|
57
17
|
...fontAssets(),
|
|
58
|
-
"/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs",
|
|
59
|
-
"/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs",
|
|
60
|
-
"/assets/vendor/xterm.css": vendorAsset("@xterm/xterm/css/xterm.css",
|
|
18
|
+
"/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", JS),
|
|
19
|
+
"/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs", JS),
|
|
20
|
+
"/assets/vendor/xterm.css": vendorAsset("@xterm/xterm/css/xterm.css", CSS)
|
|
61
21
|
});
|
|
62
22
|
export { DASHBOARD_HTML };
|
|
63
23
|
export function findWebAsset(pathname) {
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
export const CATALOG_SCRIPT = String.raw `
|
|
2
|
+
// The Task catalog: the refresh loop over the dashboard read, the sidebar
|
|
3
|
+
// (attention, status tabs, search, paged list), its filters from the URL and
|
|
4
|
+
// the explicit Session read of the current catalog page. This module is the
|
|
5
|
+
// only writer of the catalog fields of state. Only the newest request may
|
|
6
|
+
// apply its result.
|
|
7
|
+
import { fill, formatClock } from "/assets/js/lib/format.js";
|
|
8
|
+
import { h } from "/assets/js/lib/dom.js";
|
|
9
|
+
import { loadingBlock } from "/assets/js/ui/primitives.js";
|
|
10
|
+
import { renderAttentionBar, renderFilters, renderTasks, STATUS_FILTERS, ATTENTION_KINDS } from "/assets/js/views/sidebar.js";
|
|
11
|
+
import { syncUrl } from "/assets/js/layout/router.js";
|
|
12
|
+
|
|
13
|
+
// deps: { el, state, t, locale, api, toast, selectTask, showOverview, reloadSelected }
|
|
14
|
+
// el the sidebar elements, the sync state and the refresh button
|
|
15
|
+
// state writes the catalog fields; reads state.selected
|
|
16
|
+
// selectTask(taskId) a task row was chosen (app/selection)
|
|
17
|
+
// showOverview() redraw the center overview while nothing is selected
|
|
18
|
+
// reloadSelected() re-read the selected Task; rejects when the read fails
|
|
19
|
+
export function createCatalog(deps) {
|
|
20
|
+
// progress: whether a refresh is in flight and the newest request number.
|
|
21
|
+
const progress = { refreshing: false, request: 0 };
|
|
22
|
+
const catalog = {
|
|
23
|
+
refresh: function (options) { return refresh(deps, progress, options); },
|
|
24
|
+
reset: function () { reset(deps, progress); },
|
|
25
|
+
renderSidebar: function () { renderSidebar(deps, progress); },
|
|
26
|
+
renderTaskList: function () { renderTaskList(deps); },
|
|
27
|
+
pickAttention: function (kind) { pickAttention(deps, progress, kind); },
|
|
28
|
+
readPageSessions: function () { return readPageSessions(deps); },
|
|
29
|
+
applyFilters: function (params) { applyFilters(deps, params); }
|
|
30
|
+
};
|
|
31
|
+
bindCatalogControls(deps.el, deps.state, catalog);
|
|
32
|
+
return catalog;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function reset(deps, progress) {
|
|
36
|
+
deps.state.catalogCursor = null;
|
|
37
|
+
deps.state.nextCursor = null;
|
|
38
|
+
refresh(deps, progress);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function refresh(deps, progress, options) {
|
|
42
|
+
const el = deps.el;
|
|
43
|
+
const state = deps.state;
|
|
44
|
+
const quiet = options && options.quiet;
|
|
45
|
+
if (progress.refreshing && quiet) return;
|
|
46
|
+
const request = ++progress.request;
|
|
47
|
+
progress.refreshing = true;
|
|
48
|
+
beginRefresh(el, state, deps.t, quiet);
|
|
49
|
+
const previousInputs = state.counts ? state.counts.openInputs : null;
|
|
50
|
+
try {
|
|
51
|
+
const query = catalogQuery(state);
|
|
52
|
+
const dashboard = await deps.api.dashboard(query);
|
|
53
|
+
if (request !== progress.request) return;
|
|
54
|
+
applyDashboard(state, dashboard, query);
|
|
55
|
+
showDashboard(deps, progress, previousInputs);
|
|
56
|
+
// A selected Task may live on another page or outside current filters;
|
|
57
|
+
// only its own read can establish that it no longer exists.
|
|
58
|
+
if (state.selected) {
|
|
59
|
+
try { await deps.reloadSelected(); }
|
|
60
|
+
catch { deps.toast(deps.t("errors.disconnected")); }
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
if (request !== progress.request) return;
|
|
64
|
+
dashboardFailed(deps, quiet);
|
|
65
|
+
} finally {
|
|
66
|
+
if (request === progress.request) {
|
|
67
|
+
progress.refreshing = false;
|
|
68
|
+
el.refresh.disabled = false;
|
|
69
|
+
el.catalogNext.disabled = !state.nextCursor;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function showDashboard(deps, progress, previousInputs) {
|
|
75
|
+
deps.el.sync.dataset.state = "ok";
|
|
76
|
+
renderSidebar(deps, progress);
|
|
77
|
+
if (!deps.state.selected) deps.showOverview();
|
|
78
|
+
if (previousInputs !== null && deps.state.counts.openInputs > previousInputs) deps.toast(deps.t("input.new"));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function dashboardFailed(deps, quiet) {
|
|
82
|
+
deps.el.sync.dataset.state = "error";
|
|
83
|
+
if (quiet) return;
|
|
84
|
+
deps.el.tasks.replaceChildren(h("div.list-empty.is-error", null, h("p", null, deps.t("errors.dashboard"))));
|
|
85
|
+
deps.toast(deps.t("errors.dashboard"));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderSidebar(deps, progress) {
|
|
89
|
+
const el = deps.el;
|
|
90
|
+
const state = deps.state;
|
|
91
|
+
const t = deps.t;
|
|
92
|
+
renderAttentionBar(el.attention, state, t, function (kind) { pickAttention(deps, progress, kind); });
|
|
93
|
+
renderFilters(el.filters, state, t, function (filter) {
|
|
94
|
+
state.filter = filter;
|
|
95
|
+
state.attentionFilter = null;
|
|
96
|
+
state.catalogAll = false;
|
|
97
|
+
syncUrl(state, false);
|
|
98
|
+
reset(deps, progress);
|
|
99
|
+
});
|
|
100
|
+
el.catalogCount.textContent = fill(t("catalog.count"), { shown: state.tasks.length, total: state.catalogTotal });
|
|
101
|
+
el.catalogReset.hidden = !state.catalogCursor;
|
|
102
|
+
el.catalogNext.hidden = !state.nextCursor && !state.catalogCursor;
|
|
103
|
+
el.catalogAttentionReset.hidden = !state.attentionFilter;
|
|
104
|
+
renderTaskList(deps);
|
|
105
|
+
el.lastSync.textContent = state.generatedAt ? formatClock(state.generatedAt, deps.locale()) : "—";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Redraw the list in place, keeping its scroll position.
|
|
109
|
+
function renderTaskList(deps) {
|
|
110
|
+
const scroll = deps.el.tasks.scrollTop;
|
|
111
|
+
renderTasks(deps.el.tasks, deps.state, deps.t, deps.locale(), deps.selectTask);
|
|
112
|
+
deps.el.tasks.scrollTop = scroll;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function pickAttention(deps, progress, kind) {
|
|
116
|
+
const state = deps.state;
|
|
117
|
+
state.attentionFilter = kind;
|
|
118
|
+
state.catalogAll = !!kind && !!state.catalogScope && state.catalogScope.archived === "included";
|
|
119
|
+
state.filter = "all";
|
|
120
|
+
state.query = "";
|
|
121
|
+
deps.el.search.value = "";
|
|
122
|
+
syncUrl(state, false);
|
|
123
|
+
reset(deps, progress);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function beginRefresh(el, state, t, quiet) {
|
|
127
|
+
el.catalogNext.disabled = true;
|
|
128
|
+
el.sync.dataset.state = "syncing";
|
|
129
|
+
if (quiet) return;
|
|
130
|
+
el.refresh.disabled = true;
|
|
131
|
+
if (!state.tasks.length) el.tasks.replaceChildren(loadingBlock(t("loading.dashboard")));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function catalogQuery(state) {
|
|
135
|
+
const query = new URLSearchParams();
|
|
136
|
+
if (state.catalogAll) query.set("all", "true");
|
|
137
|
+
if (state.filter !== "all") query.set("status", state.filter);
|
|
138
|
+
if (state.query.trim()) query.set("search", state.query.trim());
|
|
139
|
+
if (state.attentionFilter) query.set("attention", state.attentionFilter);
|
|
140
|
+
if (state.catalogCursor) query.set("cursor", state.catalogCursor);
|
|
141
|
+
return query.toString();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function taskIds(tasks, key) {
|
|
145
|
+
return JSON.stringify(tasks.map(function (task) { return task[key]; }));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function applyDashboard(state, dashboard, query) {
|
|
149
|
+
state.tasks = dashboard.tasks;
|
|
150
|
+
// A Session read describes exactly one catalog page.
|
|
151
|
+
if (state.catalogQuery !== query || (state.sessionOverview
|
|
152
|
+
&& taskIds(state.sessionOverview.tasks, "taskId") !== taskIds(dashboard.tasks, "id"))) {
|
|
153
|
+
state.sessionOverview = null;
|
|
154
|
+
}
|
|
155
|
+
state.catalogQuery = query;
|
|
156
|
+
state.counts = { ...dashboard.counts, openInputs: dashboard.attention.openInputs.count };
|
|
157
|
+
state.catalogAttention = dashboard.attention;
|
|
158
|
+
state.catalogScope = dashboard.scope;
|
|
159
|
+
state.catalogTotal = dashboard.total;
|
|
160
|
+
state.nextCursor = dashboard.nextCursor;
|
|
161
|
+
state.attention = dashboard.attention.openInputs.refs.map(function (ref) {
|
|
162
|
+
const task = state.tasks.find(function (entry) { return entry.id === ref.taskId; });
|
|
163
|
+
return { taskId: ref.taskId, taskTitle: task ? task.title : ref.taskId };
|
|
164
|
+
});
|
|
165
|
+
state.generatedAt = new Date().toISOString();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function readPageSessions(deps) {
|
|
169
|
+
const state = deps.state;
|
|
170
|
+
if (state.sessionLoading) return;
|
|
171
|
+
const query = state.catalogQuery;
|
|
172
|
+
state.sessionLoading = true;
|
|
173
|
+
deps.showOverview();
|
|
174
|
+
try {
|
|
175
|
+
const result = await deps.api.pageSessions(query);
|
|
176
|
+
if (query !== state.catalogQuery) return;
|
|
177
|
+
if (taskIds(result.tasks, "taskId") !== taskIds(state.tasks, "id")) throw new Error(deps.t("overview.sessionsPageChanged"));
|
|
178
|
+
state.sessionOverview = result;
|
|
179
|
+
state.sessionError = null;
|
|
180
|
+
} catch (error) { state.sessionError = error.message; }
|
|
181
|
+
finally {
|
|
182
|
+
state.sessionLoading = false;
|
|
183
|
+
if (!state.selected) deps.showOverview();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Filters from the URL, on load and on back/forward.
|
|
188
|
+
function applyFilters(deps, params) {
|
|
189
|
+
const state = deps.state;
|
|
190
|
+
const filter = params.get("filter");
|
|
191
|
+
state.filter = STATUS_FILTERS.includes(filter) ? filter : "all";
|
|
192
|
+
state.query = params.get("q") || "";
|
|
193
|
+
state.attentionFilter = ATTENTION_KINDS.includes(params.get("attention")) ? params.get("attention") : null;
|
|
194
|
+
state.catalogAll = params.get("all") === "true";
|
|
195
|
+
deps.el.search.value = state.query;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function bindCatalogControls(el, state, catalog) {
|
|
199
|
+
let searchTimer = null;
|
|
200
|
+
el.search.addEventListener("input", function () {
|
|
201
|
+
state.query = el.search.value;
|
|
202
|
+
syncUrl(state, true);
|
|
203
|
+
window.clearTimeout(searchTimer);
|
|
204
|
+
searchTimer = window.setTimeout(catalog.reset, 200);
|
|
205
|
+
});
|
|
206
|
+
el.catalogNext.addEventListener("click", function () {
|
|
207
|
+
if (!state.nextCursor) return;
|
|
208
|
+
state.catalogCursor = state.nextCursor;
|
|
209
|
+
catalog.refresh();
|
|
210
|
+
});
|
|
211
|
+
el.catalogReset.addEventListener("click", catalog.reset);
|
|
212
|
+
el.catalogAttentionReset.addEventListener("click", function () { catalog.pickAttention(null); });
|
|
213
|
+
el.refresh.addEventListener("click", function () { catalog.refresh(); });
|
|
214
|
+
}
|
|
215
|
+
`;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export const APP_SCRIPT = String.raw `
|
|
2
|
+
// Composition root (served as /assets/app.js). It binds the static shell,
|
|
3
|
+
// owns the view state object and is the only place where controllers are
|
|
4
|
+
// wired to each other. Each controller receives one explicit deps object,
|
|
5
|
+
// documented at its factory: services, controllers created before it, and
|
|
6
|
+
// named callbacks into controllers created after it (bound here, late).
|
|
7
|
+
// Everything kept in the browser is view state; Task facts always come from
|
|
8
|
+
// the loopback API.
|
|
9
|
+
//
|
|
10
|
+
// layout/workspace sidebar | center | dock columns, dock modes, sizing
|
|
11
|
+
// app/taskView draws the selected Task, the dock discussion, the overview
|
|
12
|
+
// app/selection selected Task, its detail reads, URL-driven navigation
|
|
13
|
+
// app/catalog refresh loop, sidebar, catalog paging and filters
|
|
14
|
+
// layout/shortcuts keyboard shortcuts
|
|
15
|
+
//
|
|
16
|
+
// Flows:
|
|
17
|
+
// refresh catalog.refresh → sidebar, then the overview (nothing selected)
|
|
18
|
+
// or selection.reload → taskView.render
|
|
19
|
+
// select sidebar row / overview row / URL → selection.selectTask
|
|
20
|
+
// leave back button or Escape → selection.clearSelection
|
|
21
|
+
// write Task page or discussion afterWrite → quiet catalog refresh
|
|
22
|
+
// URL load and back/forward → catalog.applyFilters + selection.followUrl
|
|
23
|
+
import { createI18n } from "/assets/js/lib/i18n.js";
|
|
24
|
+
import { createThemeController } from "/assets/js/lib/theme.js";
|
|
25
|
+
import { api } from "/assets/js/lib/api.js";
|
|
26
|
+
import { createToast } from "/assets/js/ui/toast.js";
|
|
27
|
+
import { createTerminalController } from "/assets/js/views/dock/terminal.js";
|
|
28
|
+
import { createConversationController } from "/assets/js/views/dock/conversation.js";
|
|
29
|
+
import { createPreviewController } from "/assets/js/views/dock/preview.js";
|
|
30
|
+
import { bindGlobalInput } from "/assets/js/views/globalInput.js";
|
|
31
|
+
import { bindCreateTask } from "/assets/js/views/createTask.js";
|
|
32
|
+
import { bindBodySearch } from "/assets/js/views/bodySearch.js";
|
|
33
|
+
import { readQuery } from "/assets/js/layout/router.js";
|
|
34
|
+
import { createWorkspace } from "/assets/js/layout/workspace.js";
|
|
35
|
+
import { bindShortcuts } from "/assets/js/layout/shortcuts.js";
|
|
36
|
+
import { createTaskView } from "/assets/js/app/taskView.js";
|
|
37
|
+
import { createSelection } from "/assets/js/app/selection.js";
|
|
38
|
+
import { createCatalog } from "/assets/js/app/catalog.js";
|
|
39
|
+
|
|
40
|
+
const $ = function (selector) { return document.querySelector(selector); };
|
|
41
|
+
const el = {
|
|
42
|
+
sidebar: $(".sidebar"), sidebarDivider: $("#sidebar-divider"), center: $("#center"), detail: $("#detail"),
|
|
43
|
+
search: $("#search"), filters: $("#status-filters"), attention: $("#attention-bar"), tasks: $("#task-list"),
|
|
44
|
+
catalogNext: $("#catalog-next"), catalogReset: $("#catalog-reset"), catalogCount: $("#catalog-count"),
|
|
45
|
+
catalogAttentionReset: $("#catalog-attention-reset"),
|
|
46
|
+
refresh: $("#refresh"), sync: $("#sync-state"), lastSync: $("#last-sync"), toast: $("#toast"),
|
|
47
|
+
operator: $("#operator-terminal"), settingsOpen: $("#settings-open"), settings: $("#settings-dialog"),
|
|
48
|
+
locale: $("#locale-select"), themeOptions: $("#theme-options"),
|
|
49
|
+
dock: $("#dock"), divider: $("#dock-divider"), dockSwap: $("#dock-swap"), dockClose: $("#dock-close"),
|
|
50
|
+
dockTabDiscussion: $("#dock-tab-discussion"), dockTabSession: $("#dock-tab-session"),
|
|
51
|
+
dockTabConversation: $("#dock-tab-conversation"), conversation: $("#dock-conversation"),
|
|
52
|
+
dockTabPreview: $("#dock-tab-preview"), preview: $("#dock-preview"),
|
|
53
|
+
discussion: $("#dock-discussion"), session: $("#dock-session"),
|
|
54
|
+
terminalHost: $("#terminal-host"), terminalEmpty: $("#terminal-empty"), terminalState: $("#terminal-state"),
|
|
55
|
+
sessionTargets: $("#session-targets"), terminalCli: $("#terminal-cli")
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// View state. Each group has exactly one writer.
|
|
59
|
+
const state = {
|
|
60
|
+
// app/catalog: the dashboard read, its filters and paging, the Session read.
|
|
61
|
+
tasks: [], counts: null, attention: [], catalogAttention: null, catalogScope: null, catalogQuery: "",
|
|
62
|
+
catalogAll: false, catalogTotal: 0, catalogCursor: null, nextCursor: null,
|
|
63
|
+
sessionOverview: null, sessionLoading: false, sessionError: null,
|
|
64
|
+
attentionFilter: null, generatedAt: null, filter: "all", query: "",
|
|
65
|
+
// app/selection: the selected Task and its loaded detail.
|
|
66
|
+
selected: null, detail: null, detailKey: null,
|
|
67
|
+
// app/taskView: the tab the reader is on.
|
|
68
|
+
activeTab: "overview"
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const i18n = createI18n(el.locale);
|
|
72
|
+
const t = i18n.t;
|
|
73
|
+
const locale = i18n.getLocale;
|
|
74
|
+
const theme = createThemeController(el.themeOptions, t);
|
|
75
|
+
const toast = createToast(el.toast);
|
|
76
|
+
const conversation = createConversationController(el.conversation, t, locale);
|
|
77
|
+
const preview = createPreviewController(el.preview, t);
|
|
78
|
+
const terminal = createTerminalController({
|
|
79
|
+
host: el.terminalHost, empty: el.terminalEmpty, state: el.terminalState, targets: el.sessionTargets, cli: el.terminalCli
|
|
80
|
+
}, t, locale, toast);
|
|
81
|
+
theme.subscribe(function () { terminal.retheme(); });
|
|
82
|
+
|
|
83
|
+
// Controllers are created in dependency order; the arrow callbacks below are
|
|
84
|
+
// the only edges pointing at controllers created later.
|
|
85
|
+
let taskView = null;
|
|
86
|
+
let selection = null;
|
|
87
|
+
let catalog = null;
|
|
88
|
+
const workspace = createWorkspace({
|
|
89
|
+
el: el, state: state, terminal: terminal, conversation: conversation, preview: preview,
|
|
90
|
+
sessionOpened: function () { taskView.updateSessionTargets(); }
|
|
91
|
+
});
|
|
92
|
+
taskView = createTaskView({
|
|
93
|
+
el: el, state: state, t: t, locale: locale, api: api, toast: toast, terminal: terminal, workspace: workspace,
|
|
94
|
+
selectTask: function (taskId) { return selection.selectTask(taskId); },
|
|
95
|
+
clearSelection: function () { selection.clearSelection(); },
|
|
96
|
+
pickAttention: function (kind) { catalog.pickAttention(kind); },
|
|
97
|
+
readPageSessions: function () { return catalog.readPageSessions(); },
|
|
98
|
+
refreshCatalog: function () { return catalog.refresh({ quiet: true }); }
|
|
99
|
+
});
|
|
100
|
+
selection = createSelection({
|
|
101
|
+
state: state, t: t, api: api, toast: toast, workspace: workspace, taskView: taskView,
|
|
102
|
+
renderTaskList: function () { catalog.renderTaskList(); }
|
|
103
|
+
});
|
|
104
|
+
catalog = createCatalog({
|
|
105
|
+
el: el, state: state, t: t, locale: locale, api: api, toast: toast,
|
|
106
|
+
selectTask: selection.selectTask,
|
|
107
|
+
showOverview: taskView.showOverview,
|
|
108
|
+
reloadSelected: selection.reload
|
|
109
|
+
});
|
|
110
|
+
const globalInput = bindGlobalInput({ t: t, api: api });
|
|
111
|
+
bindCreateTask({ t: t, api: api, selectTask: selection.selectTask,
|
|
112
|
+
afterWrite: function () { catalog.refresh({ quiet: true }); } });
|
|
113
|
+
bindBodySearch({ t: t, api: api, selectTask: selection.selectTask,
|
|
114
|
+
currentTask: function () { return state.selected; },
|
|
115
|
+
captureDraftTarget: conversation.captureDraftTarget, insertReference: conversation.insertReference });
|
|
116
|
+
bindShortcuts({
|
|
117
|
+
el: el, state: state, globalInput: globalInput, workspace: workspace,
|
|
118
|
+
selection: selection, catalog: catalog, taskView: taskView
|
|
119
|
+
});
|
|
120
|
+
el.settingsOpen.addEventListener("click", function () { window.location.assign("/settings"); });
|
|
121
|
+
|
|
122
|
+
// The URL names the catalog filters and the selected Task (with its section).
|
|
123
|
+
function applyStateFromUrl() {
|
|
124
|
+
const params = readQuery();
|
|
125
|
+
catalog.applyFilters(params);
|
|
126
|
+
selection.followUrl(params);
|
|
127
|
+
}
|
|
128
|
+
window.addEventListener("popstate", function () {
|
|
129
|
+
applyStateFromUrl();
|
|
130
|
+
catalog.reset();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
i18n.subscribe(function () {
|
|
134
|
+
theme.render();
|
|
135
|
+
terminal.relabel();
|
|
136
|
+
catalog.renderSidebar();
|
|
137
|
+
taskView.relabel();
|
|
138
|
+
workspace.updateLayout();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
workspace.updateLayout();
|
|
142
|
+
taskView.showOverview();
|
|
143
|
+
conversation.open({ scope: "global", roleName: "operator" });
|
|
144
|
+
applyStateFromUrl();
|
|
145
|
+
workspace.restoreDefaultMode();
|
|
146
|
+
catalog.refresh();
|
|
147
|
+
window.setInterval(function () { catalog.refresh({ quiet: true }); }, 5000);
|
|
148
|
+
`;
|