@zq-silk/yui 2.3.0 → 2.4.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.
Files changed (183) hide show
  1. package/README.md +15 -1
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +17 -8
  7. package/dist/cli/invocationAuthority.js +1 -1
  8. package/dist/commands/executionAuditCommands.js +5 -0
  9. package/dist/commands/globalRoleCommands.js +41 -3
  10. package/dist/commands/projectCommands.js +89 -94
  11. package/dist/commands/roleSkillValidation.js +4 -8
  12. package/dist/commands/taskCommands.js +25 -2
  13. package/dist/commands/taskUsageCommand.js +38 -0
  14. package/dist/context/roleSessionContext.js +18 -2
  15. package/dist/context/runContextPack.js +5 -1
  16. package/dist/context/sessionBootstrapManifest.js +10 -1
  17. package/dist/context/skillCatalog.js +108 -0
  18. package/dist/context/skillPackage.js +161 -0
  19. package/dist/context/taskContext.js +3 -2
  20. package/dist/context/taskSearch.js +127 -0
  21. package/dist/controlPlaneCli.js +25 -20
  22. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  23. package/dist/controller/jobControl.js +2 -0
  24. package/dist/controller/nativeHostInput.js +37 -0
  25. package/dist/controller/runtime.js +93 -2
  26. package/dist/controller/runtimeEventInbox.js +2 -2
  27. package/dist/controller/runtimeEventProcessor.js +3 -3
  28. package/dist/doctor/doctor.js +10 -0
  29. package/dist/execution/reviewMainRun.js +1 -0
  30. package/dist/execution/workItemMainRun.js +1 -0
  31. package/dist/executor/codexConfigConflict.js +6 -4
  32. package/dist/executor/effectiveLaunch.js +24 -1
  33. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  34. package/dist/integration/gitIntegrationService.js +7 -12
  35. package/dist/integration/integrationSourceApplication.js +4 -2
  36. package/dist/job/httpPreview.js +61 -0
  37. package/dist/job/jobRunner.js +12 -2
  38. package/dist/kernel/builtinCapabilities.js +3 -1
  39. package/dist/message/message.js +15 -1
  40. package/dist/output/rolePresentation.js +4 -1
  41. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  42. package/dist/repository/gitCompatibility.js +62 -0
  43. package/dist/repository/gitWorkspace.js +48 -71
  44. package/dist/repository/gitWorktreeInventory.js +121 -0
  45. package/dist/repository/project.js +74 -25
  46. package/dist/resources/resourceDiscovery.js +2 -2
  47. package/dist/resources/resourceGc.js +7 -4
  48. package/dist/resources/resourceRegistrar.js +2 -1
  49. package/dist/runtime/agentEndpoint.js +8 -0
  50. package/dist/runtime/agentHost.js +170 -3
  51. package/dist/runtime/codexAppServerRuntime.js +1 -1
  52. package/dist/runtime/codexNativeAccess.js +175 -0
  53. package/dist/runtime/controlledCodexTui.js +116 -0
  54. package/dist/runtime/publicReply.js +23 -0
  55. package/dist/runtime/runtimeObservation.js +48 -0
  56. package/dist/runtime/structuredProviderHost.js +16 -7
  57. package/dist/runtime/taskCostMetrics.js +133 -0
  58. package/dist/runtime/taskUsageMetrics.js +11 -2
  59. package/dist/runtime/taskUsageQuery.js +38 -0
  60. package/dist/scheduler/taskExecutionProjection.js +2 -0
  61. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  62. package/dist/storage/homeLayout.js +13 -0
  63. package/dist/storage/sqliteSchema.js +44 -0
  64. package/dist/storage/sqliteStore.js +53 -0
  65. package/dist/storage/storageVersions.js +1 -1
  66. package/dist/storage/storeRpc.js +2 -0
  67. package/dist/storage/taskSearch.js +35 -0
  68. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  69. package/dist/task/ordinaryArchive.js +42 -0
  70. package/dist/tmux/tmuxManager.js +8 -8
  71. package/dist/web/assets/assetManifest.js +11 -51
  72. package/dist/web/assets/client/app/catalog.js +215 -0
  73. package/dist/web/assets/client/app/main.js +148 -0
  74. package/dist/web/assets/client/app/selection.js +139 -0
  75. package/dist/web/assets/client/app/settings.js +326 -0
  76. package/dist/web/assets/client/app/taskView.js +280 -0
  77. package/dist/web/assets/client/domain/context.js +96 -0
  78. package/dist/web/assets/client/domain/records.js +126 -0
  79. package/dist/web/assets/client/domain/runs.js +119 -0
  80. package/dist/web/assets/client/domain/taskForms.js +197 -0
  81. package/dist/web/assets/client/domain/vocab.js +69 -0
  82. package/dist/web/assets/client/domain/work.js +116 -0
  83. package/dist/web/assets/client/i18n/en.js +900 -0
  84. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  85. package/dist/web/assets/client/index.js +114 -0
  86. package/dist/web/assets/client/layout/resizer.js +37 -0
  87. package/dist/web/assets/client/layout/router.js +35 -0
  88. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  89. package/dist/web/assets/client/layout/sizing.js +95 -0
  90. package/dist/web/assets/client/layout/workspace.js +247 -0
  91. package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
  92. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  93. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  94. package/dist/web/assets/client/lib/i18n.js +54 -0
  95. package/dist/web/assets/client/lib/markdown.js +167 -0
  96. package/dist/web/assets/client/lib/prefs.js +28 -0
  97. package/dist/web/assets/client/lib/theme.js +68 -0
  98. package/dist/web/assets/client/ui/containers.js +101 -0
  99. package/dist/web/assets/client/ui/controls.js +51 -0
  100. package/dist/web/assets/client/ui/forms.js +48 -0
  101. package/dist/web/assets/client/ui/metrics.js +137 -0
  102. package/dist/web/assets/client/ui/primitives.js +92 -0
  103. package/dist/web/assets/client/ui/text.js +33 -0
  104. package/dist/web/assets/client/ui/toast.js +12 -0
  105. package/dist/web/assets/client/views/bodySearch.js +172 -0
  106. package/dist/web/assets/client/views/createTask.js +85 -0
  107. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  108. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  109. package/dist/web/assets/client/views/dock/preview.js +129 -0
  110. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  111. package/dist/web/assets/client/views/globalInput.js +67 -0
  112. package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
  113. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  114. package/dist/web/assets/client/views/skills.js +152 -0
  115. package/dist/web/assets/client/views/task/actions.js +36 -0
  116. package/dist/web/assets/client/views/task/delivery.js +185 -0
  117. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  118. package/dist/web/assets/client/views/task/files.js +272 -0
  119. package/dist/web/assets/client/views/task/observation.js +153 -0
  120. package/dist/web/assets/client/views/task/overview.js +161 -0
  121. package/dist/web/assets/client/views/task/page.js +131 -0
  122. package/dist/web/assets/client/views/task/records.js +163 -0
  123. package/dist/web/assets/client/views/task/runtime.js +130 -0
  124. package/dist/web/assets/client/views/task/timeline.js +190 -0
  125. package/dist/web/assets/shared/geometry.js +19 -0
  126. package/dist/web/assets/shared/taskTabs.js +8 -0
  127. package/dist/web/assets/shared/themes.js +93 -0
  128. package/dist/web/assets/shell/dialogs.js +63 -0
  129. package/dist/web/assets/shell/dock.js +40 -0
  130. package/dist/web/assets/shell/index.js +40 -0
  131. package/dist/web/assets/shell/settings.js +34 -0
  132. package/dist/web/assets/shell/sidebar.js +45 -0
  133. package/dist/web/assets/styles/index.js +41 -0
  134. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
  135. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  136. package/dist/web/assets/styles/markdown.js +22 -3
  137. package/dist/web/assets/styles/tokens.js +10 -63
  138. package/dist/web/assets/styles/ui/controls.js +76 -0
  139. package/dist/web/assets/styles/ui/forms.js +46 -0
  140. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  141. package/dist/web/assets/styles/views/delivery.js +29 -0
  142. package/dist/web/assets/styles/views/dock.js +71 -0
  143. package/dist/web/assets/styles/views/page.js +32 -0
  144. package/dist/web/assets/styles/views/records.js +70 -0
  145. package/dist/web/assets/styles/views/settings.js +26 -0
  146. package/dist/web/assets/styles/views/sidebar.js +74 -0
  147. package/dist/web/assets/styles/views/task.js +80 -0
  148. package/dist/web/assets/styles/views/terminal.js +38 -0
  149. package/dist/web/taskPreviews.js +205 -0
  150. package/dist/web/tmuxWebTerminal.js +16 -9
  151. package/dist/web/webConversation.js +373 -0
  152. package/dist/web/webMaterials.js +155 -0
  153. package/dist/web/webServer.js +295 -18
  154. package/dist/web/webSettings.js +331 -0
  155. package/dist/web/webSkills.js +86 -0
  156. package/dist/web/webSnapshot.js +40 -8
  157. package/dist/web/webTaskSurface.js +106 -8
  158. package/dist/workspace/gitChangeSetCapture.js +3 -1
  159. package/docs/managed-turn-and-session-runtime.md +9 -0
  160. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  161. package/docs/release-workflow.md +28 -0
  162. package/docs/release-workflow.zh-CN.md +21 -0
  163. package/docs/storage-baseline.md +38 -1
  164. package/docs/storage-baseline.zh-CN.md +22 -0
  165. package/docs/task-discovery.md +28 -0
  166. package/docs/testing/verification-levels.md +2 -2
  167. package/i18n/README.zh-CN.md +1 -1
  168. package/package.json +1 -1
  169. package/dist/web/assets/client/app.js +0 -808
  170. package/dist/web/assets/client/components.js +0 -477
  171. package/dist/web/assets/client/detail.js +0 -379
  172. package/dist/web/assets/client/dock.js +0 -309
  173. package/dist/web/assets/client/evidence.js +0 -364
  174. package/dist/web/assets/client/forms.js +0 -230
  175. package/dist/web/assets/client/i18n.js +0 -1277
  176. package/dist/web/assets/client/markdown.js +0 -114
  177. package/dist/web/assets/client/records.js +0 -198
  178. package/dist/web/assets/client/sections.js +0 -349
  179. package/dist/web/assets/client/theme.js +0 -71
  180. package/dist/web/assets/shell.js +0 -161
  181. package/dist/web/assets/styles/components.js +0 -216
  182. package/dist/web/assets/styles/views.js +0 -309
  183. /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
@@ -0,0 +1,130 @@
1
+ export const TASK_RUNTIME_SCRIPT = String.raw `
2
+ // Task › Runtime: what is live now — Roles and their Sessions, the redirect
3
+ // control, open execution, open operations, usage and diagnostics. Session
4
+ // lines, counts and raw facts are [data-slot]s the observation fills in.
5
+ import { h } from "/assets/js/lib/dom.js";
6
+ import { fill } from "/assets/js/lib/format.js";
7
+ import { dot, emptyState, mono, note, timeTag } from "/assets/js/ui/primitives.js";
8
+ import { card, cardDisclosure, listCard } from "/assets/js/ui/containers.js";
9
+ import { observabilityMetricCard } from "/assets/js/ui/metrics.js";
10
+ import { statusBadge, tone } from "/assets/js/domain/vocab.js";
11
+ import { cachedExact, entriesOf, exactCache, totalOf } from "/assets/js/domain/context.js";
12
+ import { exactRow, recordCard } from "/assets/js/domain/records.js";
13
+ import { roleCard, runCard } from "/assets/js/domain/runs.js";
14
+ import { controlForm } from "/assets/js/domain/taskForms.js";
15
+
16
+ export function renderRuntime(panel, data, t, locale, ctx) {
17
+ exactCache(data.viewState);
18
+ const roleEntries = entriesOf(data.core, "role");
19
+ panel.append(rolesCard(data, roleEntries, t, locale, ctx));
20
+ panel.append(controlCard(data.task, roleEntries, t, ctx));
21
+ panel.append(runsCard(data, t, locale, ctx));
22
+ panel.append(jobsCard(data, t, locale, ctx));
23
+ panel.append(usageCard(data, t));
24
+ panel.append(diagnosticsCard(data.core, t));
25
+ }
26
+
27
+ // --- Roles -------------------------------------------------------------------
28
+ function rolesCard(data, roleEntries, t, locale, ctx) {
29
+ const runs = knownRuns(data);
30
+ const roles = listCard({
31
+ id: "detail-roles", title: t("roles.title"), icon: "user", count: totalOf(data.core, "role"), hint: t("roles.hint"),
32
+ actions: h("div.chip-row", { dataset: { slot: "session-counts" } })
33
+ });
34
+ roleEntries.forEach(function (entry) {
35
+ roles.list.append(entry.omitted ? recordCard(entry, data.task.id, t, ctx) : roleRow(entry, data, runs, t, locale, ctx));
36
+ });
37
+ if (!roleEntries.length) roles.list.append(emptyState(t("records.none")));
38
+ return roles;
39
+ }
40
+
41
+ // Active runs already read exactly (this view or an earlier render).
42
+ function knownRuns(data) {
43
+ return entriesOf(data.core, "run").map(function (entry) {
44
+ const hit = cachedExact(data.viewState.exact, entry);
45
+ return hit ? hit.value : null;
46
+ }).filter(Boolean);
47
+ }
48
+
49
+ // A Role card with the slots the observation fills: its live status in the
50
+ // head and its native Session line below it.
51
+ function roleRow(entry, data, runs, t, locale, ctx) {
52
+ const role = entry.value;
53
+ const active = runs.find(function (run) { return run.roleName === role.name && run.status === "active"; });
54
+ const runtimeRole = ((data.runtime && data.runtime.roles) || []).find(function (item) { return item.name === role.name; });
55
+ const element = roleCard(role, t, locale, {
56
+ runtimeRole: runtimeRole,
57
+ retry: entry.providerRetry,
58
+ launchDrift: active && active.effective && active.effective.sourceDesiredRevision !== role.launchRevision,
59
+ onOpen: data.task.status === "archived" ? null : ctx.openSession
60
+ });
61
+ const head = element.querySelector(".role-head");
62
+ head.append(h("span", { dataset: { roleStatus: role.name } }));
63
+ head.after(h("div.role-session", { dataset: { roleSession: role.name } }));
64
+ return element;
65
+ }
66
+
67
+ function controlCard(task, roleEntries, t, ctx) {
68
+ const redirect = cardDisclosure("detail-control", t("control.title"), "control", t("control.meta"));
69
+ redirect.body.append(note(t("control.help")), controlForm(task, t, ctx, roleEntries.map(function (entry) { return entry.ref.refId; })));
70
+ return redirect;
71
+ }
72
+
73
+ // --- Execution and operations --------------------------------------------------
74
+ function runsCard(data, t, locale, ctx) {
75
+ const runEntries = entriesOf(data.core, "run");
76
+ const runTotal = totalOf(data.core, "run");
77
+ const runList = listCard({ id: "detail-exec", title: t("runs.title"), icon: "pulse", count: runTotal || null, hint: t("runs.hint") });
78
+ if (!runEntries.length) runList.list.append(emptyState(t("runs.none"), "pulse"));
79
+ runEntries.forEach(function (entry) {
80
+ runList.list.append(exactRow(entry, data.task.id, t, ctx, data.viewState.exact, function (value, result) {
81
+ return runCard({ ...value, execution: (result && result.execution) || entry.execution }, t, locale);
82
+ }));
83
+ });
84
+ if (runTotal > runEntries.length) runList.list.append(note(fill(t("runs.more"), { count: runTotal - runEntries.length })));
85
+ return runList;
86
+ }
87
+
88
+ function jobsCard(data, t, locale, ctx) {
89
+ const jobs = entriesOf(data.core, "job");
90
+ const jobTotal = totalOf(data.core, "job");
91
+ const jobList = listCard({
92
+ id: "detail-operations", title: t("jobs.title"), icon: "refresh", count: jobTotal || null,
93
+ hint: jobTotal ? t("jobs.hint") : null
94
+ });
95
+ if (!jobs.length) jobList.list.append(emptyState(t("jobs.none")));
96
+ jobs.forEach(function (entry) {
97
+ jobList.list.append(exactRow(entry, data.task.id, t, ctx, data.viewState.exact, function (job) { return jobRecord(job, data.task, t, locale); }));
98
+ });
99
+ return jobList;
100
+ }
101
+
102
+ function jobRecord(job, task, t, locale) {
103
+ return h("article.record", null,
104
+ h("header.record-head", null, dot(tone("job", job.status)), mono(task.id + "/" + job.id), h("span.spacer"),
105
+ statusBadge(t, "job", "job", job.status)),
106
+ h("p.small", null, t("job." + job.status + ".note", t("job.terminal.note"))),
107
+ h("p.faint.small", null, (job.steps || []).map(function (step) { return step.name; }).join(" → ")),
108
+ timeTag(job.terminalAt || job.startedAt || job.createdAt, locale, t));
109
+ }
110
+
111
+ // --- Usage and diagnostics -------------------------------------------------------
112
+ function usageCard(data, t) {
113
+ const usage = card({ id: "detail-usage", title: t("usage.title"), icon: "clock" });
114
+ const metrics = data.runtime && observabilityMetricCard(data.runtime.observability, t, data.viewState);
115
+ usage.body.append(h("div", { dataset: { slot: "usage" } }, metrics || emptyState(t("usage.unavailable"))));
116
+ return usage;
117
+ }
118
+
119
+ function diagnosticsCard(core, t) {
120
+ const diagnostics = cardDisclosure("detail-diagnostics", t("diagnostics.title"), "diagnostics", t("diagnostics.meta"));
121
+ diagnostics.body.append(note(t("observation.help")));
122
+ (core.observations || []).forEach(function (observation) {
123
+ diagnostics.body.append(h("p.small.mono-line", null,
124
+ observation.source + " · " + observation.status + " · " + observation.coverage + " · " + observation.observedAt));
125
+ });
126
+ diagnostics.body.append(h("div.stack", { dataset: { slot: "session-facts" } }),
127
+ h("p.faint.small", { dataset: { slot: "runtime-status" } }), h("pre.code-block", { dataset: { slot: "runtime-raw" } }));
128
+ return diagnostics;
129
+ }
130
+ `;
@@ -0,0 +1,190 @@
1
+ export const TASK_TIMELINE_SCRIPT = String.raw `
2
+ // Task › Records › History: one timeline over four Context families, read as
3
+ // paged summaries. Messages live in the Discussion dock. Ascending families
4
+ // are read in full (bounded); descending runs page on request, and older rows
5
+ // of other families are held back until the run pages reach them so the
6
+ // merged order stays honest.
7
+ import { h, icon, clear, markSelected } from "/assets/js/lib/dom.js";
8
+ import { button, chip, emptyState, mono, note, timeTag } from "/assets/js/ui/primitives.js";
9
+ import { card, filterChips, loadedNote, moreButton } from "/assets/js/ui/containers.js";
10
+ import { richText } from "/assets/js/ui/text.js";
11
+ import { label, statusBadge } from "/assets/js/domain/vocab.js";
12
+ import { exactCache, familyState, loadFamily, summaryFields } from "/assets/js/domain/context.js";
13
+ import { lazyRow } from "/assets/js/domain/records.js";
14
+ import { runBody } from "/assets/js/domain/runs.js";
15
+
16
+ const TIMELINE = [
17
+ { kind: "decision", store: "task-decision", icon: "check", ascending: true },
18
+ { kind: "milestone", store: "task-milestone", icon: "flag", ascending: true },
19
+ { kind: "run", store: "run", icon: "pulse", ascending: false },
20
+ { kind: "question", store: "input-request", icon: "inbox", ascending: true }
21
+ ];
22
+ const FULL_PAGES = 5;
23
+
24
+ function at(item) { return Date.parse(item.createdAt || 0) || 0; }
25
+
26
+ // The History card. It becomes the panel's redraw(), and showing the panel
27
+ // reads the family pages for the current Context cursor.
28
+ export function historyCard(panel, data, t, locale, ctx) {
29
+ const view = data.viewState;
30
+ view.panels = view.panels || {};
31
+ view.panels.records = panel;
32
+ exactCache(view);
33
+ view.openRows = view.openRows || {};
34
+ if (!TIMELINE.some(function (family) { return family.kind === view.recordsFilter; })) view.recordsFilter = "all";
35
+ const history = card({
36
+ id: "detail-history", title: t("history.title"), icon: "history", hint: t("history.hint"),
37
+ actions: filterChips(t("history.filter"), [{ value: "all", label: t("history.all") }].concat(TIMELINE.map(function (family) {
38
+ return { value: family.kind, label: t("history." + family.kind) };
39
+ })), view.recordsFilter, function (value) { view.recordsFilter = value; draw(); })
40
+ });
41
+ const scope = {
42
+ view: view, core: data.core, task: data.task, t: t, locale: locale, ctx: ctx, history: history,
43
+ known: knownValues(data.core), list: h("ol.timeline"), foot: h("div.list-foot")
44
+ };
45
+ history.body.append(scope.list, scope.foot);
46
+ function draw() { drawTimeline(scope); }
47
+ panel.redraw = draw;
48
+ panel.onShow = function () { show(scope, false); };
49
+ draw();
50
+ return history;
51
+ }
52
+
53
+ // Values the current snapshot already carries need no read.
54
+ function knownValues(core) {
55
+ const known = {};
56
+ core.records.forEach(function (entry) { if (!entry.omitted) known[entry.ref.store + "/" + entry.ref.refId] = entry; });
57
+ return known;
58
+ }
59
+
60
+ function drawTimeline(scope) {
61
+ const view = scope.view;
62
+ const t = scope.t;
63
+ clear(scope.list);
64
+ clear(scope.foot);
65
+ markSelected(scope.history.querySelectorAll(".chip-tab"), "aria-pressed", function (chipButton) { return chipButton.dataset.filter === view.recordsFilter; });
66
+ const states = TIMELINE.filter(function (family) { return view.recordsFilter === "all" || view.recordsFilter === family.kind; })
67
+ .map(function (family) { return { family: family, state: familyState(view, family.store) }; });
68
+ const items = mergedItems(states);
69
+ const loading = states.some(function (row) { return row.state.pending && !row.state.pages; });
70
+ const loaded = states.every(function (row) { return row.state.pages || row.state.error; });
71
+ states.forEach(function (row) {
72
+ if (row.state.error) scope.list.append(h("li.timeline-empty", null, note(t("history." + row.family.kind) + " · " + t("records.unavailable") + " " + row.state.error, "bad")));
73
+ });
74
+ if (!items.length) {
75
+ scope.list.append(h("li.timeline-empty", null, emptyState(loading || !loaded ? t("history.loading") : t("records.none"), "history")));
76
+ }
77
+ items.forEach(function (row) { scope.list.append(timelineItem(scope, row.family, row.item)); });
78
+ if (loaded) drawFoot(scope, states, items.length);
79
+ }
80
+
81
+ // Newest first. While a descending family has more pages, rows older than
82
+ // its oldest loaded row wait for those pages.
83
+ function mergedItems(states) {
84
+ let horizon = -Infinity;
85
+ states.forEach(function (row) {
86
+ if (row.state.nextCursor && !row.family.ascending && row.state.items.length) {
87
+ horizon = Math.max(horizon, Math.min.apply(null, row.state.items.map(at)));
88
+ }
89
+ });
90
+ const items = [];
91
+ states.forEach(function (row) {
92
+ row.state.items.forEach(function (item) { if (at(item) >= horizon) items.push({ family: row.family, item: item }); });
93
+ });
94
+ return items.sort(function (a, b) { return at(b.item) - at(a.item); });
95
+ }
96
+
97
+ function drawFoot(scope, states, shown) {
98
+ const t = scope.t;
99
+ const total = states.reduce(function (sum, row) { return sum + (row.state.total || 0); }, 0);
100
+ scope.foot.append(loadedNote(t, shown, total));
101
+ const stale = states.some(function (row) { return row.state.key && row.state.key !== scope.core.coreCursor; });
102
+ if (stale) {
103
+ scope.foot.append(button(t("history.stale"), { icon: "refresh", variant: "ghost", onClick: function () { show(scope, true); } }));
104
+ }
105
+ if (!states.some(function (row) { return row.state.nextCursor; })) return;
106
+ scope.foot.append(moreButton(t, function () {
107
+ Promise.all(states.filter(function (row) { return row.state.nextCursor; }).map(function (row) {
108
+ return loadFamily(scope.view, scope.task.id, row.family.store, row.state.key, scope.ctx, { more: true, limit: 40 });
109
+ })).then(function () { refresh(scope); });
110
+ }));
111
+ }
112
+
113
+ // --- Rows ----------------------------------------------------------------------
114
+ function timelineItem(scope, family, item) {
115
+ const t = scope.t;
116
+ const fields = summaryFields(item.summary);
117
+ const entry = scope.known[item.ref.store + "/" + item.ref.refId];
118
+ const value = entry && entry.ref.digest === item.ref.digest ? entry.value : null;
119
+ const element = h("li.timeline-item.kind-" + family.kind);
120
+ element.append(h("span.timeline-mark", { "aria-hidden": "true" }, icon(family.icon, "icon-sm")));
121
+ const title = timelineTitle(family, item, fields, t);
122
+ const head = h("div.timeline-summary", null, h("header.timeline-head", null, h("strong.timeline-title", null, title),
123
+ family.kind === "run" ? mono(item.ref.refId) : null,
124
+ item.workItemId ? chip(item.workItemId) : null,
125
+ item.history === "retired" ? chip(t("history.retired")) : null,
126
+ item.status ? timelineStatus(family, item, t) : null, h("span.spacer"), timeTag(item.createdAt, scope.locale, t)));
127
+ const preview = value && family.kind === "decision" ? value.rationale : fields.summary !== title ? fields.summary : null;
128
+ if (preview) head.append(h("p.small.muted.timeline-preview", null, preview));
129
+ // The row opens in place; its exact record is read on first open.
130
+ element.append(h("div.timeline-body", null, lazyRow(item, scope.task.id, t, scope.ctx, {
131
+ cache: scope.view.exact, openRows: scope.view.openRows, head: head,
132
+ render: function (exact, result) { return timelineDetail(family, exact, result, t, scope.locale); }
133
+ })));
134
+ return element;
135
+ }
136
+
137
+ function timelineTitle(family, item, fields, t) {
138
+ if (family.kind === "run") return [item.roleName || t("store.run"), item.purpose ? label(t, "run.purpose", item.purpose) : null].filter(Boolean).join(" · ");
139
+ if (family.kind === "question") return fields.question || fields.title || item.ref.refId;
140
+ return fields.title || item.ref.refId;
141
+ }
142
+
143
+ function timelineStatus(family, item, t) {
144
+ if (family.kind === "decision") return statusBadge(t, "decision", "decision", item.status);
145
+ if (family.kind === "run") return statusBadge(t, "run", "run", item.status);
146
+ if (family.kind === "question") return statusBadge(t, "input", "inputStatus", item.status);
147
+ return null;
148
+ }
149
+
150
+ function timelineDetail(family, exact, result, t, locale) {
151
+ if (family.kind === "run") return h("div.stack", null, runBody({ ...exact, execution: result && result.execution }, t, locale));
152
+ if (family.kind === "decision") return h("div.stack", null,
153
+ exact.rationale ? richText(t("record.rationale"), exact.rationale, t, { muted: true }) : null,
154
+ exact.supersededReason ? richText(t("history.superseded"), exact.supersededReason, t, { muted: true }) : null);
155
+ if (family.kind === "milestone") return h("div.stack", null, exact.summary ? richText(null, exact.summary, t) : null);
156
+ if (family.kind !== "question") return null;
157
+ const answer = exact.resolution && exact.resolution.answer;
158
+ return h("div.stack", null, exact.question ? richText(null, exact.question, t) : null,
159
+ answer ? richText(t("history.answer") + " · " + label(t, "answeredBy", exact.resolution.answeredBy),
160
+ answer.text || answer.choiceKey, t, { muted: true }) : null,
161
+ exact.cancellation ? note(exact.cancellation.reason) : null);
162
+ }
163
+
164
+ // --- Reads -----------------------------------------------------------------------
165
+ // Reads redraw whichever Records panel is current.
166
+ function refresh(scope) {
167
+ const current = scope.view.panels.records;
168
+ if (current && current.isConnected) current.redraw();
169
+ }
170
+
171
+ function loadFull(scope, family, key) {
172
+ return loadFamily(scope.view, scope.task.id, family.store, key, scope.ctx, { limit: 40 }).then(function (state) {
173
+ if (!family.ascending || !state.nextCursor || state.pages >= FULL_PAGES || state.error) return state;
174
+ return loadFamily(scope.view, scope.task.id, family.store, key, scope.ctx, { more: true, limit: 40 }).then(function () { return loadFull(scope, family, key); });
175
+ });
176
+ }
177
+
178
+ // A stale first page re-reads silently; once the user paged further, the
179
+ // read waits for an explicit refresh so their position is kept.
180
+ function show(scope, force) {
181
+ const key = scope.core.coreCursor;
182
+ TIMELINE.forEach(function (family) {
183
+ const state = familyState(scope.view, family.store);
184
+ if (state.pending || state.key === key || (!force && state.errorKey === key)) return;
185
+ if (!force && state.key && !family.ascending && state.pages > 1) return;
186
+ loadFull(scope, family, key).then(function () { refresh(scope); });
187
+ });
188
+ refresh(scope);
189
+ }
190
+ `;
@@ -0,0 +1,19 @@
1
+ // Layout geometry shared by the static shell (separator ARIA ranges), the
2
+ // stylesheets (breakpoints, default widths) and the client layout controller
3
+ // (clamping and keyboard steps), so the three can never drift apart.
4
+ export const BREAKPOINTS = Object.freeze({
5
+ // ≤ wide: compact sidebar default and a three-column metric strip.
6
+ wide: 1200,
7
+ // ≤ narrow: master–detail; the dock becomes a full-screen sheet.
8
+ narrow: 900,
9
+ // ≤ small: phone layout for page content.
10
+ small: 620
11
+ });
12
+ export const SIDEBAR = Object.freeze({ min: 240, max: 560, compact: 264, step: 16 });
13
+ export const DOCK = Object.freeze({ min: 320, max: 760, initial: 440, step: 24 });
14
+ // The task column never shrinks below this while a divider is dragged.
15
+ export const CENTER_MIN = 420;
16
+ export const DIVIDER_WIDTH = 9;
17
+ export const LAYOUT_GEOMETRY = Object.freeze({
18
+ narrow: BREAKPOINTS.narrow, sidebar: SIDEBAR, dock: DOCK, centerMin: CENTER_MIN, divider: DIVIDER_WIDTH
19
+ });
@@ -0,0 +1,8 @@
1
+ // Task page sections, shared by the shell's shortcut list and the client.
2
+ // Overview: where it stands. Delivery: what it produced. Runtime: what is live
3
+ // now. Records: what happened. Retired section names stay addressable.
4
+ export const TASK_TABS = ["overview", "delivery", "runtime", "records"];
5
+ export const TASK_TAB_ICONS = {
6
+ overview: "target", delivery: "package", runtime: "pulse", records: "history"
7
+ };
8
+ export const TASK_TAB_ALIASES = Object.freeze({ work: "delivery", execution: "runtime", history: "records", details: "records" });
@@ -0,0 +1,93 @@
1
+ // The theme registry: one typed source for every theme's colour and effect
2
+ // tokens. tokens.css, the settings swatches and the client's theme list are
3
+ // generated from it, so adding a theme is one entry here and tsc rejects an
4
+ // entry that omits a token.
5
+ // sumi (墨) graphite dark, vermilion accent — default dark
6
+ // washi (和紙) warm paper light, vermilion accent — default light
7
+ // ai (藍) deep indigo dark, periwinkle accent
8
+ // "system" is not a theme: the client resolves it to the default dark or
9
+ // light theme from prefers-color-scheme.
10
+ export const THEME_TOKENS = [
11
+ "canvas", "surface", "surface-2", "surface-3", "sunken",
12
+ "line", "line-2", "line-3",
13
+ "ink", "ink-2", "ink-3", "ink-4",
14
+ "accent", "accent-ink", "accent-soft", "accent-line",
15
+ "info", "info-soft", "ok", "ok-soft", "warn", "warn-soft", "bad", "bad-soft", "idle", "idle-soft",
16
+ "shadow-1", "shadow-2",
17
+ "term-bg", "term-fg", "term-cursor", "term-sel"
18
+ ];
19
+ const SUMI = {
20
+ id: "sumi",
21
+ scheme: "dark",
22
+ tokens: {
23
+ canvas: "#0e0f11", surface: "#141518", "surface-2": "#191a1e", "surface-3": "#202126", sunken: "#0b0c0d",
24
+ line: "rgba(255,255,255,.065)", "line-2": "rgba(255,255,255,.11)", "line-3": "rgba(255,255,255,.18)",
25
+ ink: "#ececee", "ink-2": "#b4b5bb", "ink-3": "#85868e", "ink-4": "#5c5d64",
26
+ accent: "#ff7a52", "accent-ink": "#1a0c06", "accent-soft": "rgba(255,122,82,.12)", "accent-line": "rgba(255,122,82,.38)",
27
+ info: "#6cb6ff", "info-soft": "rgba(108,182,255,.13)",
28
+ ok: "#4fd18b", "ok-soft": "rgba(79,209,139,.13)",
29
+ warn: "#f5c065", "warn-soft": "rgba(245,192,101,.14)",
30
+ bad: "#ff6b7a", "bad-soft": "rgba(255,107,122,.13)",
31
+ idle: "#8e8f97", "idle-soft": "rgba(142,143,151,.14)",
32
+ "shadow-1": "0 1px 0 rgba(255,255,255,.03) inset,0 1px 2px rgba(0,0,0,.35)",
33
+ "shadow-2": "0 12px 40px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.35)",
34
+ "term-bg": "#0b0c0d", "term-fg": "#e6e6e8", "term-cursor": "#ff7a52", "term-sel": "#3a2a24"
35
+ }
36
+ };
37
+ const WASHI = {
38
+ id: "washi",
39
+ scheme: "light",
40
+ tokens: {
41
+ canvas: "#f4f2ed", surface: "#fbfaf7", "surface-2": "#ffffff", "surface-3": "#f1eee7", sunken: "#ebe8e0",
42
+ line: "rgba(40,32,20,.08)", "line-2": "rgba(40,32,20,.13)", "line-3": "rgba(40,32,20,.22)",
43
+ ink: "#1d1b18", "ink-2": "#45413b", "ink-3": "#77726a", "ink-4": "#a39e95",
44
+ accent: "#d24a26", "accent-ink": "#ffffff", "accent-soft": "rgba(210,74,38,.09)", "accent-line": "rgba(210,74,38,.34)",
45
+ info: "#1f6fd1", "info-soft": "rgba(31,111,209,.09)",
46
+ ok: "#17844f", "ok-soft": "rgba(23,132,79,.1)",
47
+ warn: "#a86500", "warn-soft": "rgba(168,101,0,.1)",
48
+ bad: "#c72f45", "bad-soft": "rgba(199,47,69,.09)",
49
+ idle: "#7a756c", "idle-soft": "rgba(122,117,108,.11)",
50
+ "shadow-1": "0 1px 2px rgba(40,32,20,.06)",
51
+ "shadow-2": "0 16px 40px rgba(40,32,20,.16),0 2px 8px rgba(40,32,20,.08)",
52
+ "term-bg": "#1b1a18", "term-fg": "#efece6", "term-cursor": "#ff8a63", "term-sel": "#4a3a32"
53
+ }
54
+ };
55
+ const AI = {
56
+ id: "ai",
57
+ scheme: "dark",
58
+ tokens: {
59
+ canvas: "#0a0e1a", surface: "#0f1424", "surface-2": "#141a2e", "surface-3": "#1a2139", sunken: "#080b15",
60
+ line: "rgba(160,180,255,.08)", "line-2": "rgba(160,180,255,.13)", "line-3": "rgba(160,180,255,.22)",
61
+ ink: "#e7eaf6", "ink-2": "#b0b7d2", "ink-3": "#8088a8", "ink-4": "#565e7d",
62
+ accent: "#8fa8ff", "accent-ink": "#0a0e1a", "accent-soft": "rgba(143,168,255,.13)", "accent-line": "rgba(143,168,255,.4)",
63
+ info: "#6fd3ff", "info-soft": "rgba(111,211,255,.12)",
64
+ ok: "#5ad6a0", "ok-soft": "rgba(90,214,160,.12)",
65
+ warn: "#f2c46d", "warn-soft": "rgba(242,196,109,.13)",
66
+ bad: "#ff7d93", "bad-soft": "rgba(255,125,147,.13)",
67
+ idle: "#8a91ad", "idle-soft": "rgba(138,145,173,.14)",
68
+ "shadow-1": "0 1px 0 rgba(255,255,255,.03) inset,0 1px 2px rgba(0,0,0,.4)",
69
+ "shadow-2": "0 14px 44px rgba(0,0,0,.55),0 2px 8px rgba(0,0,0,.4)",
70
+ "term-bg": "#080b15", "term-fg": "#e2e6f5", "term-cursor": "#8fa8ff", "term-sel": "#26305a"
71
+ }
72
+ };
73
+ export const THEMES = [SUMI, WASHI, AI];
74
+ export const DEFAULT_DARK_THEME = SUMI;
75
+ export const DEFAULT_LIGHT_THEME = WASHI;
76
+ // The default dark theme's block also matches :root, so the page has colours
77
+ // before the client stamps the resolved theme on <html>.
78
+ export function themeTokenStyles() {
79
+ return THEMES.map(theme => {
80
+ const selector = theme === DEFAULT_DARK_THEME ? `:root,[data-theme="${theme.id}"]` : `[data-theme="${theme.id}"]`;
81
+ const tokens = THEME_TOKENS.map(token => `--${token}:${theme.tokens[token]};`).join("");
82
+ return `${selector}{color-scheme:${theme.scheme};${tokens}}`;
83
+ }).join("\n");
84
+ }
85
+ // Settings options for the client: each swatch shows canvas, raised surface
86
+ // and accent; "system" pairs the default dark and light surfaces.
87
+ export const THEME_OPTIONS = [
88
+ {
89
+ id: "system",
90
+ swatch: [DEFAULT_DARK_THEME.tokens.surface, DEFAULT_LIGHT_THEME.tokens.surface, DEFAULT_DARK_THEME.tokens.accent]
91
+ },
92
+ ...THEMES.map(theme => ({ id: theme.id, swatch: [theme.tokens.canvas, theme.tokens["surface-2"], theme.tokens.accent] }))
93
+ ];
@@ -0,0 +1,63 @@
1
+ import { iconSvg } from "../shared/icons.js";
2
+ import { TASK_TABS } from "../shared/taskTabs.js";
3
+ // Modal dialogs: settings (theme, language, keyboard shortcuts) and the
4
+ // global Role input form. The string keeps its indentation inside <body> and
5
+ // has no trailing newline.
6
+ export const DIALOGS_HTML = ` <dialog id="settings-dialog" class="dialog settings-dialog" aria-labelledby="settings-title">
7
+ <form method="dialog" class="dialog-body">
8
+ <header class="dialog-head">
9
+ <h2 id="settings-title" data-i18n="settings.title">Settings</h2>
10
+ <button class="icon-btn" value="close" aria-label="Close" data-i18n-aria-label="actions.close">${iconSvg("close")}</button>
11
+ </header>
12
+ <fieldset class="field">
13
+ <legend data-i18n="settings.theme">Theme</legend>
14
+ <div id="theme-options" class="theme-options"></div>
15
+ </fieldset>
16
+ <label class="field">
17
+ <span data-i18n="settings.language">Language</span>
18
+ <select id="locale-select">
19
+ <option value="en">English</option>
20
+ <option value="zh-CN">简体中文</option>
21
+ </select>
22
+ </label>
23
+ <div class="field">
24
+ <span data-i18n="settings.shortcuts">Keyboard</span>
25
+ <dl class="shortcut-list">
26
+ <dt><kbd>/</kbd></dt><dd data-i18n="shortcut.search">Search tasks</dd>
27
+ <dt><kbd>R</kbd></dt><dd data-i18n="shortcut.refresh">Refresh</dd>
28
+ <dt><kbd>D</kbd></dt><dd data-i18n="shortcut.dock">Show or hide the session panel</dd>
29
+ <dt><kbd>O</kbd></dt><dd data-i18n="shortcut.operator">Operator session</dd>
30
+ <dt><kbd>1</kbd>–<kbd>${TASK_TABS.length}</kbd></dt><dd data-i18n="shortcut.tabs">Switch task sections</dd>
31
+ <dt><kbd>Esc</kbd></dt><dd data-i18n="shortcut.escape">Close panel / leave task</dd>
32
+ </dl>
33
+ </div>
34
+ </form>
35
+ </dialog>
36
+ <dialog id="global-input-dialog" class="dialog" aria-labelledby="global-input-title">
37
+ <form id="global-input-form" class="dialog-body">
38
+ <header class="dialog-head">
39
+ <div>
40
+ <h2 id="global-input-title" data-i18n="global.title">Global Role input</h2>
41
+ <p class="dialog-sub" data-i18n="global.help">Queue is the default. A cancel request does not prove the Turn stopped.</p>
42
+ </div>
43
+ <button id="global-input-close" class="icon-btn" type="button" aria-label="Close" data-i18n-aria-label="actions.close">${iconSvg("close")}</button>
44
+ </header>
45
+ <div class="field-row">
46
+ <label class="field"><span data-i18n="global.role">Role</span><input id="global-input-role" value="operator" required pattern="[A-Za-z0-9_-]+"></label>
47
+ <label class="field"><span data-i18n="control.action">Action</span><select id="global-input-action">
48
+ <option value="queue" data-i18n="control.queue">Queue</option>
49
+ <option value="steer" data-i18n="control.steer">Steer</option>
50
+ <option value="interrupt" data-i18n="control.interrupt">Interrupt</option>
51
+ </select></label>
52
+ </div>
53
+ <label id="global-input-body-label" class="field"><span data-i18n="control.message">Message</span><textarea id="global-input-body" maxlength="8000" required rows="4"></textarea></label>
54
+ <label id="global-input-target-label" class="field" hidden><span data-i18n="control.expectedTurn">Expected current Turn</span><input id="global-input-target" class="mono"></label>
55
+ <label id="global-input-then-label" class="field" hidden><span data-i18n="control.then">Then-message reference</span><input id="global-input-then" class="mono"></label>
56
+ <div class="dialog-actions">
57
+ <button id="global-input-inspect" type="button" class="btn" data-i18n="global.inspect">Read state</button>
58
+ <button id="global-input-submit" type="submit" class="btn btn-primary" data-i18n="actions.submit">Submit</button>
59
+ </div>
60
+ <p id="global-input-receipt" role="status" class="receipt" data-i18n="receipt.notSubmitted">Not submitted</p>
61
+ <pre id="global-input-state" class="code-block" hidden></pre>
62
+ </form>
63
+ </dialog>`;
@@ -0,0 +1,40 @@
1
+ import { DOCK } from "../shared/geometry.js";
2
+ import { iconSvg } from "../shared/icons.js";
3
+ // The dock divider and the dock region: the Discussion / Live session tabs,
4
+ // their actions, the discussion mount and the terminal pane, whose host,
5
+ // target chips and connection state must survive re-renders.
6
+ // The string keeps its indentation inside .app and has no trailing newline.
7
+ export const DOCK_HTML = ` <div id="dock-divider" class="dock-divider" role="separator" tabindex="0" aria-orientation="vertical" aria-valuemin="${DOCK.min}" aria-valuemax="${DOCK.max}" aria-valuenow="${DOCK.initial}" aria-label="Resize session panel" data-i18n-aria-label="dock.resize" hidden><span aria-hidden="true"></span></div>
8
+ <aside id="dock" class="dock" aria-label="Session" data-i18n-aria-label="dock.label" hidden>
9
+ <header class="dock-head">
10
+ <div class="seg" role="tablist" aria-label="Session view" data-i18n-aria-label="dock.label">
11
+ <button id="dock-tab-conversation" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-conversation"><span data-i18n="conversation.title">Conversation</span></button>
12
+ <button id="dock-tab-discussion" class="seg-btn" type="button" role="tab" aria-selected="true" aria-controls="dock-discussion">${iconSvg("chat")}<span data-i18n="dock.discussion">Discussion</span></button>
13
+ <button id="dock-tab-session" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-session">${iconSvg("terminal")}<span data-i18n="dock.session">Live session</span></button>
14
+ <button id="dock-tab-preview" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-preview" disabled><span data-i18n="preview.title">Preview</span></button>
15
+ </div>
16
+ <div class="dock-actions">
17
+ <button id="dock-swap" class="icon-btn" type="button" aria-label="Swap with task details" data-i18n-aria-label="dock.swap" title="Swap with task details">${iconSvg("swap")}</button>
18
+ <button id="dock-close" class="icon-btn" type="button" aria-label="Hide session panel" data-i18n-aria-label="dock.hide" title="Hide · D">${iconSvg("close")}</button>
19
+ </div>
20
+ </header>
21
+ <section id="dock-conversation" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-conversation" hidden></section>
22
+ <section id="dock-discussion" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-discussion"></section>
23
+ <section id="dock-preview" class="dock-pane dock-preview" role="tabpanel" aria-labelledby="dock-tab-preview" hidden></section>
24
+ <section id="dock-session" class="dock-pane dock-session" role="tabpanel" aria-labelledby="dock-tab-session" hidden>
25
+ <div class="session-bar">
26
+ <div id="session-targets" class="session-targets" role="group" aria-label="Session target" data-i18n-aria-label="dock.target"></div>
27
+ <span id="terminal-state" class="conn" data-state="idle"><i aria-hidden="true"></i><span data-i18n="terminal.idle">Not connected</span></span>
28
+ </div>
29
+ <div id="terminal-host" class="terminal-host"></div>
30
+ <div id="terminal-empty" class="terminal-empty">
31
+ ${iconSvg("terminal", "icon icon-xl")}
32
+ <p class="terminal-empty-title" data-i18n="terminal.emptyTitle">Attach to a native Session</p>
33
+ <p class="terminal-empty-text" data-i18n="terminal.emptyText">Pick a Role above. The panel shows the Session's current screen; attaching never starts or restarts an Agent.</p>
34
+ </div>
35
+ <footer class="session-foot">
36
+ <span data-i18n="terminal.cliHint">Full terminal</span>
37
+ <code id="terminal-cli">yui operator enter</code>
38
+ </footer>
39
+ </section>
40
+ </aside>`;
@@ -0,0 +1,40 @@
1
+ import { DEFAULT_DARK_THEME } from "../shared/themes.js";
2
+ import { STYLESHEETS } from "../styles/index.js";
3
+ import { DIALOGS_HTML } from "./dialogs.js";
4
+ import { DOCK_HTML } from "./dock.js";
5
+ import { SIDEBAR_HTML } from "./sidebar.js";
6
+ // Static application frame. Everything inside #detail, #task-list and the dock
7
+ // panes is rendered by the client modules; the shell only fixes the regions
8
+ // and the controls whose identity must survive re-renders. Stylesheets are
9
+ // linked in registry (cascade) order, followed by the terminal's vendor CSS.
10
+ const STYLESHEET_LINKS = STYLESHEETS
11
+ .map(sheet => ` <link rel="stylesheet" href="/assets/css/${sheet.name}.css">`)
12
+ .join("\n");
13
+ export const DASHBOARD_HTML = `<!doctype html>
14
+ <html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
15
+ <head>
16
+ <meta charset="utf-8">
17
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
18
+ <meta name="color-scheme" content="dark light">
19
+ <meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
20
+ <link rel="icon" href="data:,">
21
+ <title>Yui</title>
22
+ <link rel="preload" href="/assets/fonts/inter-500.woff2" as="font" type="font/woff2" crossorigin>
23
+ <link rel="preload" href="/assets/fonts/inter-600.woff2" as="font" type="font/woff2" crossorigin>
24
+ ${STYLESHEET_LINKS}
25
+ <link rel="stylesheet" href="/assets/vendor/xterm.css">
26
+ </head>
27
+ <body>
28
+ <a class="skip-link" href="#center" data-i18n="a11y.skip">Skip to task details</a>
29
+ <div class="app">
30
+ ${SIDEBAR_HTML}
31
+ <main id="center" class="center" tabindex="-1">
32
+ <div id="detail" class="detail"></div>
33
+ </main>
34
+ ${DOCK_HTML}
35
+ </div>
36
+ ${DIALOGS_HTML}
37
+ <div id="toast" class="toast" role="status" aria-live="polite"></div>
38
+ <script type="module" src="/assets/app.js"></script>
39
+ </body>
40
+ </html>`;
@@ -0,0 +1,34 @@
1
+ import { STYLESHEETS } from "../styles/index.js";
2
+ import { DEFAULT_DARK_THEME } from "../shared/themes.js";
3
+ export const SETTINGS_HTML = `<!doctype html>
4
+ <html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
5
+ <head>
6
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
8
+ <link rel="icon" href="data:,"><title>Yui · Settings</title>
9
+ ${STYLESHEETS.map(s => `<link rel="stylesheet" href="/assets/css/${s.name}.css">`).join("\n")}
10
+ </head>
11
+ <body class="settings-body">
12
+ <main class="settings-page">
13
+ <header class="settings-header"><a class="btn" href="/">← Yui</a><h1 data-i18n="settings.title">Settings</h1></header>
14
+ <p data-i18n="settings.scope">Browser preferences apply here immediately. Server settings are desired configuration, not proof that existing Sessions adopted them.</p>
15
+ <section class="settings-card">
16
+ <h2 data-i18n="settings.browser">This browser</h2>
17
+ <div class="settings-grid">
18
+ <fieldset class="field"><legend data-i18n="settings.theme">Theme</legend><div id="theme-options" class="theme-options"></div></fieldset>
19
+ <div class="settings-stack">
20
+ <label class="field"><span data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
21
+ <label class="field"><span data-i18n="settings.accessMode">Default session access</span><select id="access-mode"><option value="native" data-i18n="settings.native">Native terminal</option><option value="structured" data-i18n="settings.structured">Structured view</option></select></label>
22
+ <p data-i18n="settings.accessHelp">Preference for subsequent session selection where supported. It does not switch a live Session.</p>
23
+ <button id="reset-layout" type="button" class="btn" data-i18n="settings.resetLayout">Reset saved workspace layout</button>
24
+ <p id="browser-receipt" role="status"></p>
25
+ </div>
26
+ </div>
27
+ </section>
28
+ <section id="skill-browser" class="settings-card settings-stack"></section>
29
+ <label class="field settings-search"><span data-i18n="settings.search">Find a setting</span><input id="settings-search" type="search" autocomplete="off"></label>
30
+ <p id="settings-status" role="status"></p>
31
+ <div id="settings-groups" class="settings-stack"></div>
32
+ </main>
33
+ <script type="module" src="/assets/js/app/settings.js"></script>
34
+ </body></html>`;