@zq-silk/yui 2.2.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 (206) hide show
  1. package/README.md +19 -3
  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 +25 -11
  7. package/dist/cli/interactionPolicy.js +4 -0
  8. package/dist/cli/invocationAuthority.js +1 -1
  9. package/dist/commands/executionAuditCommands.js +5 -0
  10. package/dist/commands/globalRoleCommands.js +41 -3
  11. package/dist/commands/projectCommands.js +89 -94
  12. package/dist/commands/roleSkillValidation.js +4 -8
  13. package/dist/commands/taskCommands.js +30 -9
  14. package/dist/commands/taskIntegrationCommands.js +5 -16
  15. package/dist/commands/taskUsageCommand.js +38 -0
  16. package/dist/context/roleSessionContext.js +18 -2
  17. package/dist/context/runContextPack.js +5 -1
  18. package/dist/context/sessionBootstrapManifest.js +10 -1
  19. package/dist/context/skillCatalog.js +108 -0
  20. package/dist/context/skillPackage.js +161 -0
  21. package/dist/context/taskContext.js +3 -2
  22. package/dist/context/taskSearch.js +127 -0
  23. package/dist/controlPlaneCli.js +51 -39
  24. package/dist/controller/clientRuntime.js +3 -2
  25. package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
  26. package/dist/controller/jobControl.js +2 -0
  27. package/dist/controller/nativeHostInput.js +37 -0
  28. package/dist/controller/runtime.js +93 -2
  29. package/dist/controller/runtimeEventInbox.js +2 -2
  30. package/dist/controller/runtimeEventProcessor.js +3 -3
  31. package/dist/doctor/doctor.js +10 -0
  32. package/dist/execution/reviewMainRun.js +1 -0
  33. package/dist/execution/workItemMainRun.js +1 -0
  34. package/dist/executor/codexConfigConflict.js +6 -4
  35. package/dist/executor/effectiveLaunch.js +24 -1
  36. package/dist/executor/fileRoleLaunchPlanner.js +13 -5
  37. package/dist/integration/gitIntegrationService.js +15 -79
  38. package/dist/integration/integrationSourceApplication.js +4 -2
  39. package/dist/job/httpPreview.js +61 -0
  40. package/dist/job/jobCleanupInspection.js +67 -0
  41. package/dist/job/jobRunner.js +12 -2
  42. package/dist/kernel/builtinCapabilities.js +3 -1
  43. package/dist/message/message.js +15 -1
  44. package/dist/output/rolePresentation.js +4 -1
  45. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  46. package/dist/repository/gitCompatibility.js +62 -0
  47. package/dist/repository/gitWorkspace.js +86 -80
  48. package/dist/repository/gitWorktreeInventory.js +121 -0
  49. package/dist/repository/project.js +74 -25
  50. package/dist/repository/taskWorkspaceCoordinator.js +72 -145
  51. package/dist/repository/taskWorkspacePreparer.js +39 -43
  52. package/dist/repository/workspaceCleanupInspection.js +88 -7
  53. package/dist/resources/resourceDiscovery.js +2 -2
  54. package/dist/resources/resourceGc.js +7 -4
  55. package/dist/resources/resourceRegistrar.js +18 -11
  56. package/dist/runtime/agentEndpoint.js +8 -0
  57. package/dist/runtime/agentHost.js +170 -3
  58. package/dist/runtime/codexAppServerRuntime.js +1 -1
  59. package/dist/runtime/codexNativeAccess.js +175 -0
  60. package/dist/runtime/controlledCodexTui.js +116 -0
  61. package/dist/runtime/integrationRuntimeIsolation.js +21 -0
  62. package/dist/runtime/publicReply.js +23 -0
  63. package/dist/runtime/runtimeObservation.js +48 -0
  64. package/dist/runtime/structuredProviderHost.js +16 -7
  65. package/dist/runtime/taskCostMetrics.js +133 -0
  66. package/dist/runtime/taskRuntimeIsolation.js +91 -21
  67. package/dist/runtime/taskUsageMetrics.js +11 -2
  68. package/dist/runtime/taskUsageQuery.js +38 -0
  69. package/dist/scheduler/taskExecutionProjection.js +2 -0
  70. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  71. package/dist/storage/homeLayout.js +13 -0
  72. package/dist/storage/sqliteSchema.js +44 -0
  73. package/dist/storage/sqliteStore.js +53 -0
  74. package/dist/storage/storageVersions.js +1 -1
  75. package/dist/storage/storeRpc.js +2 -0
  76. package/dist/storage/taskSearch.js +35 -0
  77. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  78. package/dist/task/archiveDiagnostics.js +26 -8
  79. package/dist/task/archivePreflight.js +26 -9
  80. package/dist/task/leaderArchive.js +9 -15
  81. package/dist/task/ordinaryArchive.js +42 -0
  82. package/dist/tmux/tmuxManager.js +8 -8
  83. package/dist/web/assets/assetManifest.js +11 -51
  84. package/dist/web/assets/client/app/catalog.js +215 -0
  85. package/dist/web/assets/client/app/main.js +148 -0
  86. package/dist/web/assets/client/app/selection.js +139 -0
  87. package/dist/web/assets/client/app/settings.js +326 -0
  88. package/dist/web/assets/client/app/taskView.js +280 -0
  89. package/dist/web/assets/client/domain/context.js +96 -0
  90. package/dist/web/assets/client/domain/records.js +126 -0
  91. package/dist/web/assets/client/domain/runs.js +119 -0
  92. package/dist/web/assets/client/domain/taskForms.js +197 -0
  93. package/dist/web/assets/client/domain/vocab.js +69 -0
  94. package/dist/web/assets/client/domain/work.js +116 -0
  95. package/dist/web/assets/client/i18n/en.js +900 -0
  96. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  97. package/dist/web/assets/client/index.js +114 -0
  98. package/dist/web/assets/client/layout/resizer.js +37 -0
  99. package/dist/web/assets/client/layout/router.js +35 -0
  100. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  101. package/dist/web/assets/client/layout/sizing.js +95 -0
  102. package/dist/web/assets/client/layout/workspace.js +247 -0
  103. package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
  104. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  105. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  106. package/dist/web/assets/client/lib/i18n.js +54 -0
  107. package/dist/web/assets/client/lib/markdown.js +167 -0
  108. package/dist/web/assets/client/lib/prefs.js +28 -0
  109. package/dist/web/assets/client/lib/theme.js +68 -0
  110. package/dist/web/assets/client/ui/containers.js +101 -0
  111. package/dist/web/assets/client/ui/controls.js +51 -0
  112. package/dist/web/assets/client/ui/forms.js +48 -0
  113. package/dist/web/assets/client/ui/metrics.js +137 -0
  114. package/dist/web/assets/client/ui/primitives.js +92 -0
  115. package/dist/web/assets/client/ui/text.js +33 -0
  116. package/dist/web/assets/client/ui/toast.js +12 -0
  117. package/dist/web/assets/client/views/bodySearch.js +172 -0
  118. package/dist/web/assets/client/views/createTask.js +85 -0
  119. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  120. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  121. package/dist/web/assets/client/views/dock/preview.js +129 -0
  122. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  123. package/dist/web/assets/client/views/globalInput.js +67 -0
  124. package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
  125. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  126. package/dist/web/assets/client/views/skills.js +152 -0
  127. package/dist/web/assets/client/views/task/actions.js +36 -0
  128. package/dist/web/assets/client/views/task/delivery.js +185 -0
  129. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  130. package/dist/web/assets/client/views/task/files.js +272 -0
  131. package/dist/web/assets/client/views/task/observation.js +153 -0
  132. package/dist/web/assets/client/views/task/overview.js +161 -0
  133. package/dist/web/assets/client/views/task/page.js +131 -0
  134. package/dist/web/assets/client/views/task/records.js +163 -0
  135. package/dist/web/assets/client/views/task/runtime.js +130 -0
  136. package/dist/web/assets/client/views/task/timeline.js +190 -0
  137. package/dist/web/assets/shared/geometry.js +19 -0
  138. package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
  139. package/dist/web/assets/shared/taskTabs.js +8 -0
  140. package/dist/web/assets/shared/themes.js +93 -0
  141. package/dist/web/assets/shell/dialogs.js +63 -0
  142. package/dist/web/assets/shell/dock.js +40 -0
  143. package/dist/web/assets/shell/index.js +40 -0
  144. package/dist/web/assets/shell/settings.js +34 -0
  145. package/dist/web/assets/shell/sidebar.js +45 -0
  146. package/dist/web/assets/styles/index.js +41 -0
  147. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
  148. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  149. package/dist/web/assets/styles/markdown.js +22 -3
  150. package/dist/web/assets/styles/tokens.js +10 -63
  151. package/dist/web/assets/styles/ui/controls.js +76 -0
  152. package/dist/web/assets/styles/ui/forms.js +46 -0
  153. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  154. package/dist/web/assets/styles/views/delivery.js +29 -0
  155. package/dist/web/assets/styles/views/dock.js +71 -0
  156. package/dist/web/assets/styles/views/page.js +32 -0
  157. package/dist/web/assets/styles/views/records.js +70 -0
  158. package/dist/web/assets/styles/views/settings.js +26 -0
  159. package/dist/web/assets/styles/views/sidebar.js +74 -0
  160. package/dist/web/assets/styles/views/task.js +80 -0
  161. package/dist/web/assets/styles/views/terminal.js +38 -0
  162. package/dist/web/taskPreviews.js +205 -0
  163. package/dist/web/tmuxWebTerminal.js +16 -9
  164. package/dist/web/webConversation.js +373 -0
  165. package/dist/web/webMaterials.js +155 -0
  166. package/dist/web/webServer.js +306 -18
  167. package/dist/web/webSettings.js +331 -0
  168. package/dist/web/webSkills.js +86 -0
  169. package/dist/web/webSnapshot.js +40 -8
  170. package/dist/web/webTaskSurface.js +114 -9
  171. package/dist/workspace/gitChangeSetCapture.js +3 -1
  172. package/docs/managed-turn-and-session-runtime.md +9 -0
  173. package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
  174. package/docs/observability/README.md +13 -3
  175. package/docs/observability/README.zh-CN.md +10 -3
  176. package/docs/release-workflow.md +53 -0
  177. package/docs/release-workflow.zh-CN.md +40 -0
  178. package/docs/storage-baseline.md +38 -1
  179. package/docs/storage-baseline.zh-CN.md +22 -0
  180. package/docs/task-delivery.md +36 -8
  181. package/docs/task-delivery.zh-CN.md +24 -7
  182. package/docs/task-discovery.md +28 -0
  183. package/docs/testing/verification-levels.md +2 -2
  184. package/i18n/README.zh-CN.md +4 -3
  185. package/package.json +1 -1
  186. package/skills/yui-leader/references/execution.md +2 -1
  187. package/skills/yui-leader/references/integration.md +20 -0
  188. package/skills/yui-operator/SKILL.md +4 -0
  189. package/skills/yui-reviewer/SKILL.md +3 -0
  190. package/skills/yui-runtime/SKILL.md +4 -0
  191. package/skills/yui-runtime/references/publication.md +20 -4
  192. package/skills/yui-worker/SKILL.md +3 -0
  193. package/dist/web/assets/client/app.js +0 -804
  194. package/dist/web/assets/client/components.js +0 -418
  195. package/dist/web/assets/client/detail.js +0 -333
  196. package/dist/web/assets/client/dock.js +0 -225
  197. package/dist/web/assets/client/evidence.js +0 -172
  198. package/dist/web/assets/client/forms.js +0 -230
  199. package/dist/web/assets/client/i18n.js +0 -1161
  200. package/dist/web/assets/client/markdown.js +0 -114
  201. package/dist/web/assets/client/records.js +0 -63
  202. package/dist/web/assets/client/sections.js +0 -313
  203. package/dist/web/assets/client/theme.js +0 -71
  204. package/dist/web/assets/shell.js +0 -161
  205. package/dist/web/assets/styles/components.js +0 -169
  206. package/dist/web/assets/styles/views.js +0 -292
@@ -1,4 +1,4 @@
1
- import { ICON_PATHS } from "../icons.js";
1
+ import { ICON_PATHS } from "../../shared/icons.js";
2
2
  export const DOM_SCRIPT = String.raw `
3
3
  // Dependency-free DOM helpers shared by every client module.
4
4
  const ICONS = ${JSON.stringify(ICON_PATHS)};
@@ -23,21 +23,20 @@ export function h(spec, attrs) {
23
23
  children.unshift(attrs);
24
24
  attrs = null;
25
25
  }
26
- if (attrs) {
27
- Object.keys(attrs).forEach(function (key) {
28
- const value = attrs[key];
29
- if (value === undefined || value === null || value === false) return;
30
- if (key === "class") element.className = (element.className ? element.className + " " : "") + value;
31
- else if (key === "dataset") Object.assign(element.dataset, value);
32
- else if (key.startsWith("on") && typeof value === "function") element.addEventListener(key.slice(2), value);
33
- else if (key in element && typeof value !== "string") element[key] = value;
34
- else element.setAttribute(key, value === true ? "" : String(value));
35
- });
36
- }
26
+ if (attrs) Object.keys(attrs).forEach(function (key) { applyAttribute(element, key, attrs[key]); });
37
27
  append(element, children);
38
28
  return element;
39
29
  }
40
30
 
31
+ function applyAttribute(element, key, value) {
32
+ if (value === undefined || value === null || value === false) return;
33
+ if (key === "class") element.className = (element.className ? element.className + " " : "") + value;
34
+ else if (key === "dataset") Object.assign(element.dataset, value);
35
+ else if (key.startsWith("on") && typeof value === "function") element.addEventListener(key.slice(2), value);
36
+ else if (key in element && typeof value !== "string") element[key] = value;
37
+ else element.setAttribute(key, value === true ? "" : String(value));
38
+ }
39
+
41
40
  function append(element, children) {
42
41
  children.forEach(function (child) {
43
42
  if (child === undefined || child === null || child === false) return;
@@ -63,4 +62,17 @@ export function icon(name, className) {
63
62
  export function clear(element) {
64
63
  element.replaceChildren();
65
64
  }
65
+
66
+ // Mark the selected members of a group (pressed chips, checked radios,
67
+ // selected tabs) with one ARIA state, e.g. markSelected(buttons, "aria-pressed", isCurrent).
68
+ export function markSelected(elements, attribute, isSelected) {
69
+ Array.prototype.forEach.call(elements, function (element) {
70
+ element.setAttribute(attribute, String(!!isSelected(element)));
71
+ });
72
+ }
73
+
74
+ // True while the user is typing into a control; shortcuts and polling yield.
75
+ export function isEditing(element) {
76
+ return !!element && (["INPUT", "SELECT", "TEXTAREA"].includes(element.tagName) || element.isContentEditable);
77
+ }
66
78
  `;
@@ -33,6 +33,13 @@ export function formatBytes(value) {
33
33
  return (value / 1024 / 1024).toFixed(1) + " MB";
34
34
  }
35
35
 
36
+ // Fill "{name}" placeholders of a translated template; unknown names stay.
37
+ export function fill(template, values) {
38
+ return String(template).replace(/\{(\w+)\}/g, function (match, key) {
39
+ return Object.prototype.hasOwnProperty.call(values, key) ? String(values[key]) : match;
40
+ });
41
+ }
42
+
36
43
  export function byNewest(left, right) {
37
44
  return Date.parse(right.updatedAt || right.createdAt) - Date.parse(left.updatedAt || left.createdAt);
38
45
  }
@@ -0,0 +1,54 @@
1
+ import { EN } from "../i18n/en.js";
2
+ import { ZH_CN } from "../i18n/zh-CN.js";
3
+ export const I18N_SCRIPT = String.raw `
4
+ // Translation runtime. Static shell markup opts in with data-i18n (text) and
5
+ // data-i18n-placeholder / -aria-label / -title (attributes). A missing zh-CN
6
+ // key falls back to en, then to the caller's fallback, then to the key.
7
+ import { readPreference, writePreference } from "/assets/js/lib/prefs.js";
8
+
9
+ const messages = ${JSON.stringify({ en: EN, "zh-CN": ZH_CN })};
10
+ export const SUPPORTED_LOCALES = Object.freeze(Object.keys(messages));
11
+ const TRANSLATED_ATTRIBUTES = ["placeholder", "aria-label", "title"];
12
+
13
+ function preferredLocale() {
14
+ const saved = readPreference("yui.locale", null);
15
+ if (SUPPORTED_LOCALES.includes(saved)) return saved;
16
+ const languages = navigator.languages || [navigator.language];
17
+ return languages.some(function (language) { return String(language).toLowerCase().startsWith("zh"); }) ? "zh-CN" : "en";
18
+ }
19
+
20
+ export function createI18n(select) {
21
+ let locale = preferredLocale();
22
+ const subscribers = new Set();
23
+ function t(key, fallback) {
24
+ const value = messages[locale][key] || messages.en[key];
25
+ return value !== undefined ? value : fallback !== undefined ? fallback : key;
26
+ }
27
+ function apply() {
28
+ document.documentElement.lang = locale;
29
+ document.title = t("app.title");
30
+ document.querySelectorAll("[data-i18n]").forEach(function (element) { element.textContent = t(element.dataset.i18n); });
31
+ TRANSLATED_ATTRIBUTES.forEach(function (attribute) {
32
+ document.querySelectorAll("[data-i18n-" + attribute + "]").forEach(function (element) {
33
+ element.setAttribute(attribute, t(element.getAttribute("data-i18n-" + attribute)));
34
+ });
35
+ });
36
+ if (select) select.value = locale;
37
+ }
38
+ function setLocale(next) {
39
+ if (!SUPPORTED_LOCALES.includes(next) || next === locale) return;
40
+ locale = next;
41
+ writePreference("yui.locale", locale);
42
+ apply();
43
+ subscribers.forEach(function (subscriber) { subscriber(locale); });
44
+ }
45
+ if (select) select.addEventListener("change", function () { setLocale(select.value); });
46
+ apply();
47
+ return {
48
+ t: t,
49
+ getLocale: function () { return locale; },
50
+ setLocale: setLocale,
51
+ subscribe: function (subscriber) { subscribers.add(subscriber); }
52
+ };
53
+ }
54
+ `;
@@ -0,0 +1,167 @@
1
+ export const MARKDOWN_SCRIPT = String.raw `
2
+ // Parse raw text and escape leaves once. Generated markup is never re-parsed.
3
+ // Images stay inert unless a trusted caller supplies a placeholder factory.
4
+ export function escapeHtml(text) {
5
+ return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;")
6
+ .replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
7
+ }
8
+
9
+ export function resolveMarkdownImage(sourcePath, target) {
10
+ let value;
11
+ try { value = decodeURIComponent(target); } catch { throw new Error("Invalid image path encoding."); }
12
+ if (!value || /^[\/\\]/.test(value) || /[:\\?#\x00-\x1f]/.test(value)) {
13
+ throw new Error("Only relative images in this fixed artifact version are supported.");
14
+ }
15
+ const parts = sourcePath.split("/").slice(0, -1);
16
+ value.split("/").forEach(function (part) {
17
+ if (part === ".") return;
18
+ if (part === "..") {
19
+ if (!parts.length) throw new Error("Image path escapes the artifact repository.");
20
+ parts.pop();
21
+ } else {
22
+ if (!part || part.trim() !== part || [".git", "__proto__", "prototype", "constructor"].includes(part.toLowerCase())
23
+ || /[*[\]]/.test(part)) throw new Error("Invalid artifact image path.");
24
+ parts.push(part);
25
+ }
26
+ });
27
+ if (!parts.length || parts.join("/").length > 512) throw new Error("Invalid artifact image path.");
28
+ return parts.join("/");
29
+ }
30
+
31
+ function safeLink(value) {
32
+ return /^(https?:\/\/|mailto:)/i.test(value) && !/[\x00-\x20]/.test(value);
33
+ }
34
+
35
+ function inline(text, options, depth) {
36
+ if (depth > 6) return escapeHtml(text);
37
+ const token = /(\x60{1,32})([^\n]*?)\1|(!?)\[([^[\]\n]*)\]\((<[^<>\n]+>|[^\s\n()[\]]+)(?:\s+"[^"\n]*")?\)|(!?)\[([^[\]\n]+)\]\[([^[\]\n]*)\]|\*\*([^*\n]+)\*\*|__([^_\n]+)__|~~([^~\n]+)~~|\*([^*\n]+)\*|_([^_\n]+)_|<((?:https?:\/\/|mailto:)[^<>\n]+)>|(https?:\/\/[^\s<>]+)|\\([\\\x60*_[\]{}()#+.!|>~-])/g;
38
+ let result = "", start = 0, match;
39
+ while ((match = token.exec(text))) {
40
+ result += escapeHtml(text.slice(start, match.index));
41
+ start = token.lastIndex;
42
+ if (match[1]) { result += "<code>" + escapeHtml(match[2]) + "</code>"; continue; }
43
+ if (match[3] !== undefined || match[6] !== undefined) {
44
+ const isReference = match[6] !== undefined;
45
+ const label = isReference ? match[7] : match[4];
46
+ const destination = isReference ? options.references.get((match[8] || label).toLowerCase())
47
+ : match[5].replace(/^<|>$/g, "");
48
+ const image = isReference ? match[6] : match[3];
49
+ if (!destination) result += escapeHtml(match[0]);
50
+ else if (image) result += options.image ? options.image(label, destination)
51
+ : '<span class="md-image-placeholder">' + escapeHtml(label + " [" + destination + "]") + "</span>";
52
+ else if (safeLink(destination)) result += '<a href="' + escapeHtml(destination)
53
+ + '" target="_blank" rel="noopener noreferrer">' + inline(label, options, depth + 1) + "</a>";
54
+ else result += escapeHtml(label + " (" + destination + ")");
55
+ continue;
56
+ }
57
+ const emphasis = match[9] || match[10] || match[11] || match[12] || match[13];
58
+ if (emphasis) {
59
+ const tag = match[9] || match[10] ? "strong" : match[11] ? "del" : "em";
60
+ result += "<" + tag + ">" + inline(emphasis, options, depth + 1) + "</" + tag + ">";
61
+ } else if (match[14] || match[15]) {
62
+ const url = match[14] || match[15];
63
+ result += '<a href="' + escapeHtml(url) + '" target="_blank" rel="noopener noreferrer">' + escapeHtml(url) + "</a>";
64
+ } else result += escapeHtml(match[16]);
65
+ }
66
+ return result + escapeHtml(text.slice(start));
67
+ }
68
+
69
+ const fence = /^ {0,3}(\x60{3,}|~{3,})(.*)$/;
70
+ const item = /^( *)([-+*•]|\d+[.)])\s+(.*)$/;
71
+ const rule = /^ {0,3}(?:(?:\*\s*){3,}|(?:-\s*){3,}|(?:_\s*){3,})$/;
72
+ function cells(line) {
73
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split(/(?<!\\)\|/).map(function (cell) { return cell.trim(); });
74
+ }
75
+ function tableDivider(line) {
76
+ return line.includes("|") && cells(line).every(function (cell) { return /^:?-{3,}:?$/.test(cell); });
77
+ }
78
+
79
+ export function renderMarkdown(text, supplied) {
80
+ const options = { ...supplied, references: new Map() };
81
+ const lines = String(text).replace(/\x00/g, "").replace(/\r\n?/g, "\n").split("\n");
82
+ let code = null;
83
+ lines.forEach(function (line, index) {
84
+ const mark = line.match(fence);
85
+ if (mark) {
86
+ if (!code) code = mark[1];
87
+ else if (mark[1][0] === code[0] && mark[1].length >= code.length && !mark[2].trim()) code = null;
88
+ }
89
+ if (code) return;
90
+ const ref = line.match(/^ {0,3}\[([^\]]+)\]:\s*(<[^>]+>|\S+)(?:\s+.*)?$/);
91
+ if (ref) { options.references.set(ref[1].toLowerCase(), ref[2].replace(/^<|>$/g, "")); lines[index] = ""; }
92
+ });
93
+ return blocks(lines, options, 0);
94
+ }
95
+
96
+ function blocks(lines, options, depth) {
97
+ if (depth > 8) return "<pre>" + escapeHtml(lines.join("\n")) + "</pre>";
98
+ const html = [];
99
+ for (let index = 0; index < lines.length;) {
100
+ const line = lines[index];
101
+ if (!line.trim()) { index++; continue; }
102
+ const mark = line.match(fence);
103
+ if (mark) {
104
+ const code = []; index++;
105
+ while (index < lines.length) {
106
+ const end = lines[index].match(fence);
107
+ if (end && end[1][0] === mark[1][0] && end[1].length >= mark[1].length && !end[2].trim()) { index++; break; }
108
+ code.push(lines[index++]);
109
+ }
110
+ html.push("<pre><code>" + escapeHtml(code.join("\n")) + "</code></pre>"); continue;
111
+ }
112
+ const heading = line.match(/^ {0,3}(#{1,6})\s+(.*)$/);
113
+ if (heading) {
114
+ const level = Math.min(heading[1].length + 3, 6);
115
+ html.push("<h" + level + ">" + inline(heading[2].replace(/\s+#+\s*$/, ""), options, 0) + "</h" + level + ">");
116
+ index++; continue;
117
+ }
118
+ if (rule.test(line)) { html.push("<hr>"); index++; continue; }
119
+ if (/^ {0,3}>/.test(line)) {
120
+ const quote = [];
121
+ while (index < lines.length && /^ {0,3}>/.test(lines[index])) quote.push(lines[index++].replace(/^ {0,3}> ?/, ""));
122
+ html.push("<blockquote>" + blocks(quote, options, depth + 1) + "</blockquote>"); continue;
123
+ }
124
+ if (index + 1 < lines.length && line.includes("|") && tableDivider(lines[index + 1])) {
125
+ const headers = cells(line);
126
+ if (headers.length <= 32) {
127
+ html.push('<div class="md-table" tabindex="0"><table><thead><tr>' + headers.map(function (value) {
128
+ return "<th>" + inline(value, options, 0) + "</th>";
129
+ }).join("") + "</tr></thead><tbody>");
130
+ index += 2;
131
+ while (index < lines.length && lines[index].includes("|") && lines[index].trim()) {
132
+ const row = cells(lines[index++]);
133
+ html.push("<tr>" + headers.map(function (_, col) { return "<td>" + inline(row[col] || "", options, 0) + "</td>"; }).join("") + "</tr>");
134
+ }
135
+ html.push("</tbody></table></div>"); continue;
136
+ }
137
+ }
138
+ const first = line.match(item);
139
+ if (first) {
140
+ const ordered = /^\d/.test(first[2]), indent = first[1].length;
141
+ const tag = ordered ? "ol" : "ul";
142
+ html.push("<" + tag + (ordered ? ' start="' + Math.min(parseInt(first[2], 10), 1000000) + '"' : "") + ">");
143
+ while (index < lines.length) {
144
+ const entry = lines[index].match(item);
145
+ if (!entry || entry[1].length !== indent || /^\d/.test(entry[2]) !== ordered) break;
146
+ const body = [entry[3]], contentIndent = entry[0].length - entry[3].length;
147
+ index++;
148
+ while (index < lines.length && lines[index].trim() && /^ */.exec(lines[index])[0].length > indent) {
149
+ const continuation = lines[index++];
150
+ body.push(continuation.slice(Math.min(contentIndent, /^ */.exec(continuation)[0].length)));
151
+ }
152
+ const task = body[0].match(/^\[([ xX])\]\s+(.*)$/);
153
+ if (task) body[0] = task[2];
154
+ html.push("<li>" + (task ? '<input type="checkbox" disabled aria-label="' + (task[1] === " " ? "Incomplete" : "Complete") + '"' + (task[1] === " " ? "" : " checked") + ">" : "")
155
+ + (body.length === 1 ? inline(body[0], options, 0) : blocks(body, options, depth + 1)) + "</li>");
156
+ }
157
+ html.push("</" + tag + ">"); continue;
158
+ }
159
+ const paragraph = [line]; index++;
160
+ while (index < lines.length && lines[index].trim() && !fence.test(lines[index]) && !item.test(lines[index])
161
+ && !/^ {0,3}(?:#{1,6}\s|>)/.test(lines[index]) && !rule.test(lines[index])
162
+ && !(index + 1 < lines.length && tableDivider(lines[index + 1]))) paragraph.push(lines[index++]);
163
+ html.push("<p>" + paragraph.map(function (value) { return inline(value, options, 0); }).join("<br>") + "</p>");
164
+ }
165
+ return html.join("");
166
+ }
167
+ `;
@@ -0,0 +1,28 @@
1
+ export const PREFS_SCRIPT = String.raw `
2
+ // Browser-local view preferences (theme, locale, layout). Storage may be
3
+ // unavailable (private mode, policy); reads use a fallback and mutations
4
+ // report failure instead of throwing.
5
+ export function readPreference(key, fallback) {
6
+ try { return localStorage.getItem(key) || fallback; } catch { return fallback; }
7
+ }
8
+
9
+ export function writePreference(key, value) {
10
+ try { localStorage.setItem(key, value); return true; } catch { return false; }
11
+ }
12
+
13
+ export function clearPreference(key) {
14
+ try { localStorage.removeItem(key); return true; } catch { return false; }
15
+ }
16
+
17
+ // Shared with session access consumers: no server configuration or live
18
+ // Session is changed by this browser-local preference.
19
+ export function readSessionAccessMode() {
20
+ const value = readPreference("yui.session.accessMode", "native");
21
+ return value === "structured" ? "structured" : "native";
22
+ }
23
+
24
+ export function writeSessionAccessMode(value) {
25
+ if (value !== "native" && value !== "structured") throw new Error("Invalid session access mode.");
26
+ return writePreference("yui.session.accessMode", value);
27
+ }
28
+ `;
@@ -0,0 +1,68 @@
1
+ import { DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, THEME_OPTIONS } from "../../shared/themes.js";
2
+ export const THEME_SCRIPT = String.raw `
3
+ // Theme preference. "system" follows prefers-color-scheme and resolves to the
4
+ // default dark or light theme; the resolved id is stamped on <html> so its
5
+ // token block applies. Options and swatches come from shared/themes.ts.
6
+ import { h, markSelected } from "/assets/js/lib/dom.js";
7
+ import { readPreference, writePreference } from "/assets/js/lib/prefs.js";
8
+
9
+ const THEMES = Object.freeze(${JSON.stringify(THEME_OPTIONS)});
10
+ const IDS = THEMES.map(function (theme) { return theme.id; });
11
+ const DEFAULT_DARK = ${JSON.stringify(DEFAULT_DARK_THEME.id)};
12
+ const DEFAULT_LIGHT = ${JSON.stringify(DEFAULT_LIGHT_THEME.id)};
13
+
14
+ function storedPreference() {
15
+ const saved = readPreference("yui.theme", "system");
16
+ return IDS.includes(saved) ? saved : "system";
17
+ }
18
+
19
+ function themeOption(theme, t, onPick) {
20
+ const swatch = h("span.theme-swatch", { "aria-hidden": "true" }, theme.swatch.map(function (color, index) {
21
+ const part = h("i");
22
+ part.style.background = color;
23
+ part.dataset.part = String(index);
24
+ return part;
25
+ }));
26
+ return h("button.theme-option", {
27
+ type: "button",
28
+ dataset: { themeOption: theme.id },
29
+ onclick: function () { onPick(theme.id); }
30
+ }, swatch, h("span", null, t("theme." + theme.id)));
31
+ }
32
+
33
+ export function createThemeController(container, t) {
34
+ const media = window.matchMedia("(prefers-color-scheme: light)");
35
+ let preference = storedPreference();
36
+ const listeners = new Set();
37
+
38
+ function resolved() {
39
+ return preference === "system" ? (media.matches ? DEFAULT_LIGHT : DEFAULT_DARK) : preference;
40
+ }
41
+ function apply() {
42
+ document.documentElement.dataset.theme = resolved();
43
+ if (container) markSelected(container.querySelectorAll("[data-theme-option]"), "aria-pressed", function (button) {
44
+ return button.dataset.themeOption === preference;
45
+ });
46
+ listeners.forEach(function (listener) { listener(resolved()); });
47
+ }
48
+ function render() {
49
+ if (!container) return;
50
+ container.replaceChildren.apply(container, THEMES.map(function (theme) { return themeOption(theme, t, set); }));
51
+ apply();
52
+ }
53
+ function set(next) {
54
+ if (!IDS.includes(next)) return;
55
+ preference = next;
56
+ writePreference("yui.theme", next);
57
+ apply();
58
+ }
59
+ media.addEventListener("change", function () { if (preference === "system") apply(); });
60
+ render();
61
+ return {
62
+ resolved: resolved,
63
+ render: render,
64
+ set: set,
65
+ subscribe: function (listener) { listeners.add(listener); }
66
+ };
67
+ }
68
+ `;
@@ -0,0 +1,101 @@
1
+ export const CONTAINERS_SCRIPT = String.raw `
2
+ // Panels of the single reading column. A card has a heading (icon tile,
3
+ // title, count and hint) with optional actions, then its body; a list card's
4
+ // body is a .row-stack whose rows are divided by hairlines, not nested boxes.
5
+ import { h, icon, markSelected } from "/assets/js/lib/dom.js";
6
+ import { fill } from "/assets/js/lib/format.js";
7
+ import { button, jsonBlock } from "/assets/js/ui/primitives.js";
8
+
9
+ export function card(options) {
10
+ const opts = options || {};
11
+ const element = h("section.card" + (opts.className ? "." + opts.className.split(" ").join(".") : ""));
12
+ if (opts.id) element.id = opts.id;
13
+ if (opts.title || opts.actions) element.append(cardHead(opts));
14
+ const body = h("div.card-body");
15
+ element.append(body);
16
+ element.body = body;
17
+ return element;
18
+ }
19
+
20
+ function cardHead(opts) {
21
+ const heading = h("div.card-heading", null,
22
+ h("h3.card-title", null, opts.icon ? h("span.card-icon", null, icon(opts.icon)) : null, h("span", null, opts.title),
23
+ opts.count !== undefined && opts.count !== null ? h("span.count", null, String(opts.count)) : null),
24
+ opts.hint ? h("p.card-hint", null, opts.hint) : null);
25
+ return h("header.card-head", null, heading, opts.actions ? h("div.card-actions", null, opts.actions) : null);
26
+ }
27
+
28
+ export function listCard(options) {
29
+ const element = card({ ...options, className: "list-card" + (options.className ? " " + options.className : "") });
30
+ element.list = h("div.row-stack");
31
+ element.body.append(element.list);
32
+ return element;
33
+ }
34
+
35
+ // Update a card heading count after a later read; null removes it.
36
+ export function setCardCount(element, value) {
37
+ const title = element.querySelector(".card-title");
38
+ let count = title && title.querySelector(".count");
39
+ if (value === undefined || value === null) { if (count) count.remove(); return; }
40
+ if (!title) return;
41
+ if (!count) { count = h("span.count"); title.append(count); }
42
+ count.textContent = String(value);
43
+ }
44
+
45
+ export function disclosure(title, viewKey, options) {
46
+ const opts = options || {};
47
+ const element = h("details.disclosure" + (opts.className ? "." + opts.className : ""));
48
+ if (viewKey) element.dataset.viewKey = viewKey;
49
+ const summary = h("summary", null, icon("chevron", "disclosure-chevron"), h("span.disclosure-title", null, title));
50
+ if (opts.meta) summary.append(h("span.disclosure-meta", null, opts.meta));
51
+ element.append(summary);
52
+ const body = h("div.disclosure-body");
53
+ element.append(body);
54
+ element.body = body;
55
+ return element;
56
+ }
57
+
58
+ // A top-level section that stays closed until asked (control, diagnostics…).
59
+ export function cardDisclosure(id, title, viewKey, meta) {
60
+ const element = disclosure(title, viewKey, { className: "card-disclosure", meta: meta });
61
+ element.id = id;
62
+ return element;
63
+ }
64
+
65
+ // The exact record behind a rich rendering, one click away.
66
+ export function rawDisclosure(t, value) {
67
+ const raw = disclosure(t("record.raw"), null);
68
+ raw.body.append(jsonBlock(value));
69
+ return raw;
70
+ }
71
+
72
+ // Local filter chips; selecting one redraws the list in place.
73
+ export function filterChips(ariaLabel, options, selected, onSelect) {
74
+ const row = h("div.chip-tabs", { role: "group", "aria-label": ariaLabel });
75
+ options.forEach(function (option) {
76
+ row.append(h("button.chip-tab", {
77
+ type: "button", "aria-pressed": String(option.value === selected), dataset: { filter: option.value },
78
+ onclick: function () {
79
+ markSelected(row.querySelectorAll(".chip-tab"), "aria-pressed", function (b) { return b.dataset.filter === option.value; });
80
+ onSelect(option.value);
81
+ }
82
+ }, option.label, option.count === undefined || option.count === null ? null : h("span.count", null, String(option.count))));
83
+ });
84
+ return row;
85
+ }
86
+
87
+ // Paged list footers: how much is loaded, and a button for the next page
88
+ // that disables itself until the caller redraws.
89
+ export function loadedNote(t, shown, total) {
90
+ return h("span.faint.small", null, fill(t("history.loaded"), { shown: shown, total: total }));
91
+ }
92
+
93
+ export function moreButton(t, onMore) {
94
+ const more = button(t("history.more"), { icon: "chevron", variant: "ghost" });
95
+ more.addEventListener("click", function () {
96
+ more.disabled = true;
97
+ onMore();
98
+ });
99
+ return more;
100
+ }
101
+ `;
@@ -0,0 +1,51 @@
1
+ export const CONTROLS_SCRIPT = String.raw `
2
+ // Selection controls: the segmented radio group and the scrollable tab row.
3
+ import { h, markSelected } from "/assets/js/lib/dom.js";
4
+
5
+ export function segmented(name, options, value, onChange) {
6
+ const group = h("div.seg.seg-sm", { role: "radiogroup" });
7
+ options.forEach(function (option) {
8
+ const item = h("button.seg-btn", {
9
+ type: "button", role: "radio", title: option.title || null,
10
+ "aria-checked": String(option.value === value),
11
+ dataset: { value: option.value },
12
+ onclick: function () {
13
+ markSelected(group.querySelectorAll(".seg-btn"), "aria-checked", function (other) { return other === item; });
14
+ onChange(option.value);
15
+ }
16
+ }, option.label);
17
+ group.append(item);
18
+ });
19
+ group.dataset.name = name;
20
+ return group;
21
+ }
22
+
23
+ // A tab row that keeps its horizontal scroll position across updates. When
24
+ // it is too narrow for every tab, the clipped edge fades out (data-fade), the
25
+ // wheel scrolls it sideways and revealTab() brings a tab into view.
26
+ export function scrollableTabRow(row) {
27
+ row.addEventListener("scroll", function () { syncTabsOverflow(row); }, { passive: true });
28
+ row.addEventListener("wheel", function (event) {
29
+ if (Math.abs(event.deltaY) <= Math.abs(event.deltaX) || row.scrollWidth <= row.clientWidth) return;
30
+ event.preventDefault();
31
+ row.scrollLeft += event.deltaY;
32
+ }, { passive: false });
33
+ new ResizeObserver(function () { syncTabsOverflow(row); }).observe(row);
34
+ return row;
35
+ }
36
+
37
+ export function syncTabsOverflow(row) {
38
+ const max = row.scrollWidth - row.clientWidth;
39
+ const before = row.scrollLeft > 1;
40
+ const after = row.scrollLeft < max - 1;
41
+ row.dataset.fade = before && after ? "both" : after ? "end" : before ? "start" : "none";
42
+ }
43
+
44
+ export function revealTab(row, tab) {
45
+ const margin = 28;
46
+ const left = tab.offsetLeft - margin;
47
+ const right = tab.offsetLeft + tab.offsetWidth + margin;
48
+ if (left < row.scrollLeft) row.scrollLeft = Math.max(0, left);
49
+ else if (right > row.scrollLeft + row.clientWidth) row.scrollLeft = right - row.clientWidth;
50
+ }
51
+ `;
@@ -0,0 +1,48 @@
1
+ export const FORMS_SCRIPT = String.raw `
2
+ // Write plumbing shared by every form. A submission carries a fresh
3
+ // requestId, marks its form unsent until a receipt settles, and reports an
4
+ // unknown outcome as unknown: nothing here ever replays a write.
5
+ import { h } from "/assets/js/lib/dom.js";
6
+
7
+ export function field(text, control, hint) {
8
+ return h("label.field", null, h("span", null, text), control, hint ? h("small.faint", null, hint) : null);
9
+ }
10
+
11
+ export function receiptLine(text, state) {
12
+ const line = h("p.receipt", { role: "status" }, text);
13
+ if (state) line.dataset.state = state;
14
+ return line;
15
+ }
16
+
17
+ export function setReceipt(line, text, state) {
18
+ line.textContent = text;
19
+ line.dataset.state = state || "";
20
+ }
21
+
22
+ // options: control (disabled while in flight), receipt, t, send(requestId),
23
+ // saved(result) → true to keep the control blocked, rejectedKey (prefix of
24
+ // a not-submitted error), unknownKey, and the optional form and afterWrite.
25
+ // A rejected submission re-enables the control; an unknown one keeps it
26
+ // disabled so the same write cannot be sent twice.
27
+ export async function submitWrite(options) {
28
+ const t = options.t;
29
+ options.control.disabled = true;
30
+ if (options.form) options.form.dataset.unsent = "true";
31
+ const requestId = crypto.randomUUID();
32
+ setReceipt(options.receipt, t("receipt.waiting") + " · " + requestId, "pending");
33
+ try {
34
+ const result = await options.send(requestId);
35
+ const blocked = !!options.saved(result);
36
+ if (options.form) options.form.dataset.unsent = "false";
37
+ options.control.disabled = blocked;
38
+ if (options.afterWrite) options.afterWrite();
39
+ } catch (error) {
40
+ if (error.disposition === "not-submitted") {
41
+ setReceipt(options.receipt, (options.rejectedKey ? t(options.rejectedKey) + " " : "") + error.message, "bad");
42
+ options.control.disabled = false;
43
+ return;
44
+ }
45
+ setReceipt(options.receipt, t(options.unknownKey) + " · " + requestId + " · " + error.message, "warn");
46
+ }
47
+ }
48
+ `;