@uniqbit/mate-core 0.18.5 → 1.0.0-canary.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 (209) hide show
  1. package/claude-plugin/hooks/companion-instructions.mjs +9 -0
  2. package/claude-plugin/hooks/hooks.json +12 -2
  3. package/package.json +10 -2
  4. package/src/brand/art.generated.ts +69 -0
  5. package/src/brand/index.ts +12 -0
  6. package/src/brand/palette.ts +19 -0
  7. package/src/brand/svg.generated.ts +8 -0
  8. package/src/brand/terminal.ts +83 -0
  9. package/src/brand/types.ts +33 -0
  10. package/src/cli/commands/artifact/artifact.ts +8 -0
  11. package/src/cli/commands/artifact/delivery/archive.ts +117 -0
  12. package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
  13. package/src/cli/commands/artifact/delivery/environment.ts +104 -0
  14. package/src/cli/commands/artifact/delivery/link.ts +154 -0
  15. package/src/cli/commands/artifact/delivery/output.ts +58 -0
  16. package/src/cli/commands/artifact/finish/command.ts +33 -3
  17. package/src/cli/commands/artifact/finish/engine.ts +47 -3
  18. package/src/cli/commands/artifact/finish/finisher.ts +10 -0
  19. package/src/cli/commands/artifact/finish/openspec.ts +177 -138
  20. package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
  21. package/src/cli/commands/artifact/pending/command.ts +31 -3
  22. package/src/cli/commands/artifact/pending/discovery.ts +21 -57
  23. package/src/cli/commands/cap/graphify.ts +52 -3
  24. package/src/cli/commands/cap/index-cmd.ts +8 -1
  25. package/src/cli/commands/cap/openspec.ts +13 -25
  26. package/src/cli/commands/companion/open.ts +56 -39
  27. package/src/cli/commands/companion/update.ts +75 -42
  28. package/src/cli/commands/doctor.ts +176 -3
  29. package/src/cli/commands/plugin/install.ts +9 -4
  30. package/src/cli/commands/plugin/plugin.ts +3 -2
  31. package/src/cli/commands/report/adapter.ts +11 -2
  32. package/src/cli/commands/report/contract.ts +21 -0
  33. package/src/cli/commands/report/highlight.ts +34 -4
  34. package/src/cli/commands/report/markdown.ts +110 -0
  35. package/src/cli/commands/report/palette.ts +118 -0
  36. package/src/cli/commands/report/renderer.ts +456 -153
  37. package/src/cli/commands/report/types.ts +16 -0
  38. package/src/cli/commands/setup.ts +7 -15
  39. package/src/cli/commands/studio/client-asset.ts +16 -0
  40. package/src/cli/commands/studio/openspec-cli.ts +10 -58
  41. package/src/cli/commands/studio/routes.ts +2 -0
  42. package/src/cli/commands/studio/selection.ts +19 -1
  43. package/src/cli/commands/studio/server.ts +42 -1
  44. package/src/cli/commands/studio/views/client.ts +6 -616
  45. package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
  46. package/src/cli/commands/studio/views/document.tsx +137 -69
  47. package/src/cli/commands/studio/views/markdown.tsx +250 -0
  48. package/src/cli/commands/studio/views/palette.ts +134 -0
  49. package/src/cli/commands/studio/views/reports/index.tsx +2 -7
  50. package/src/cli/commands/studio/views/skills/index.tsx +43 -62
  51. package/src/cli/commands/studio/views/specs/index.tsx +7 -15
  52. package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
  53. package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
  54. package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
  55. package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
  56. package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
  57. package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
  58. package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
  59. package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
  60. package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
  61. package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
  62. package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
  63. package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
  64. package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
  65. package/src/cli/commands/studio/views/styles.ts +217 -206
  66. package/src/cli/commands/studio/views/vault/state.ts +3 -5
  67. package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
  68. package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
  69. package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
  70. package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
  71. package/src/cli/commands/wrap.ts +5 -13
  72. package/src/cli/main.ts +4 -1
  73. package/src/cli/parse-flags.ts +25 -7
  74. package/src/cli/setup-selector.tsx +38 -162
  75. package/src/cli/usage.ts +4 -0
  76. package/src/hooks/companion-instructions.ts +190 -0
  77. package/src/hooks/hook-io.ts +63 -0
  78. package/src/hooks/session-banner.ts +21 -29
  79. package/src/hooks/session-guidance.ts +9 -17
  80. package/src/hooks/session-prepare.ts +115 -28
  81. package/src/hooks/validate-artifact-path.ts +82 -24
  82. package/src/index.ts +4 -0
  83. package/src/lib/change-layout.ts +154 -0
  84. package/src/lib/components/wizard-header.tsx +3 -1
  85. package/src/lib/context-mode-package.ts +24 -11
  86. package/src/lib/delivery/archive-change.ts +154 -0
  87. package/src/lib/delivery/branches.ts +108 -0
  88. package/src/lib/delivery/environment.ts +29 -0
  89. package/src/lib/delivery/forge.ts +279 -0
  90. package/src/lib/delivery/overlap.ts +202 -0
  91. package/src/lib/delivery/record-delivery.ts +196 -0
  92. package/src/lib/delivery/record.ts +343 -0
  93. package/src/lib/delivery/refresh.ts +174 -0
  94. package/src/lib/delivery/repository-access.ts +97 -0
  95. package/src/lib/delivery/stages.ts +111 -0
  96. package/src/lib/delivery/token.ts +58 -0
  97. package/src/lib/frontmatter.ts +18 -0
  98. package/src/lib/opencode-plugin-package.ts +5 -5
  99. package/src/lib/openspec-cli.ts +116 -0
  100. package/src/lib/openspec-scopes.ts +86 -0
  101. package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
  102. package/src/lib/orchestrator/adapters/claude.ts +48 -40
  103. package/src/lib/orchestrator/adapters/opencode.ts +2 -0
  104. package/src/lib/orchestrator/editor.ts +9 -9
  105. package/src/lib/orchestrator/openspec-store.ts +14 -54
  106. package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
  107. package/src/lib/orchestrator/projection-claude-links.ts +159 -0
  108. package/src/lib/orchestrator/projection-claude-skills.ts +19 -137
  109. package/src/lib/orchestrator/projection-entries.ts +45 -14
  110. package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
  111. package/src/lib/orchestrator/projection-types.ts +3 -0
  112. package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
  113. package/src/lib/orchestrator/session-prepare.ts +45 -5
  114. package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
  115. package/src/lib/orchestrator/types.ts +2 -2
  116. package/src/lib/orchestrator/wrap-pass.ts +22 -1
  117. package/src/lib/package-paths.ts +8 -0
  118. package/src/opencode/companion-hooks.ts +64 -5
  119. package/src/opencode/companion-policy.ts +37 -18
  120. package/src/opencode/projected-guidance.ts +4 -1
  121. package/src/opencode/session-prepare.ts +2 -1
  122. package/src/opencode/tui.tsx +4 -2
  123. package/src/runtime/artifact-refusal.ts +63 -0
  124. package/src/runtime/claude-delivery-plan.ts +89 -7
  125. package/src/runtime/commit-attribution-guard.ts +181 -0
  126. package/src/runtime/companion-guidance.ts +15 -3
  127. package/src/runtime/companion-instructions.ts +340 -0
  128. package/src/runtime/error-message.ts +4 -0
  129. package/src/runtime/index.ts +5 -0
  130. package/src/runtime/managed-block.ts +117 -0
  131. package/src/runtime/markdown-scan.ts +266 -0
  132. package/src/runtime/policy.ts +15 -1
  133. package/src/runtime/projected-flag.ts +7 -0
  134. package/src/runtime/projected-guidance.ts +1 -0
  135. package/src/runtime/runtime-document-manifest.ts +62 -11
  136. package/src/runtime/secret-guard.ts +148 -0
  137. package/src/runtime/secret-patterns.ts +164 -0
  138. package/src/runtime/secret-scan.ts +90 -0
  139. package/src/runtime/session-kind.ts +13 -0
  140. package/src/runtime/shell-heredoc.ts +81 -0
  141. package/src/runtime/wrap-state.ts +36 -2
  142. package/src/runtime/write-atomic.ts +64 -0
  143. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
  144. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
  145. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
  146. package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
  147. package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
  148. package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
  149. package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
  150. package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
  151. package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
  152. package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
  153. package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
  154. package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
  155. package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
  156. package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
  157. package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
  158. package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
  159. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
  160. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
  161. package/src/templates/mate-skills/{claude/mate-artifact-publish → agents/mate-publish-artifact}/SKILL.md +91 -23
  162. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  163. package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
  164. package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
  165. package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
  166. package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
  167. package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
  168. package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
  169. package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
  170. package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
  171. package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
  172. package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
  173. package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
  174. package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
  175. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
  176. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
  177. package/src/templates/mate-skills/{agents/mate-artifact-publish → claude/mate-publish-artifact}/SKILL.md +91 -23
  178. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  179. package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
  180. package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
  181. package/src/templates/mate-skills/openspec-references.ts +140 -0
  182. package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
  183. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
  184. package/src/tools/setup/capabilities/context-mode.ts +25 -5
  185. package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
  186. package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
  187. package/src/tools/setup/capabilities/graphify.ts +12 -0
  188. package/src/tools/setup/capabilities/openspec.ts +151 -270
  189. package/src/tools/setup/capabilities/tokensave.ts +36 -3
  190. package/src/tools/setup/context-services.ts +9 -17
  191. package/src/tools/setup/dynamic-plugins/install.ts +24 -28
  192. package/src/tools/setup/engine.ts +97 -21
  193. package/src/tools/setup/managed-output.ts +51 -0
  194. package/src/tools/setup/mate.ts +86 -11
  195. package/src/tools/setup/plugin.ts +35 -0
  196. package/src/tools/setup/plugins/guidance.ts +7 -4
  197. package/src/tools/setup/providers/agent-file-sections.ts +4 -2
  198. package/src/tools/setup/providers/claude-attribution.ts +65 -0
  199. package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
  200. package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
  201. package/src/tools/setup/providers/claude.ts +284 -8
  202. package/src/tools/setup/providers/opencode.ts +180 -1
  203. package/src/tools/setup/utils.ts +14 -4
  204. package/src/tools/setup.ts +126 -10
  205. package/wrappers/bin/graphify +52 -4
  206. package/wrappers/bin/openspec +33 -9
  207. package/wrappers/lib/graphify-gate.mjs +8 -0
  208. package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
  209. package/src/tools/setup/runtime-documents.ts +0 -291
@@ -0,0 +1,258 @@
1
+ import { VAULT_TREE_STORAGE_KEY } from "./keys";
2
+ import { failure, fetchMarkup, isPlainClick, moveInto, navigate, wireNavigation } from "./page";
3
+
4
+ const FILTER_DELAY = 150;
5
+
6
+ let displayedGeneration: number | null = null;
7
+ let latestGeneration: number | null = null;
8
+ let refreshSequence = 0;
9
+ let filterSequence = 0;
10
+ let filterTimer: ReturnType<typeof setTimeout> | undefined;
11
+ let filterRequest: AbortController | null = null;
12
+
13
+ function showTree(shown: boolean): void {
14
+ const root = document.documentElement;
15
+ if (shown) root.removeAttribute("data-vault-tree");
16
+ else root.setAttribute("data-vault-tree", "hidden");
17
+ const toggle = document.getElementById("vault-tree-toggle");
18
+ if (toggle) {
19
+ toggle.textContent = shown ? "Hide files" : "Show files";
20
+ toggle.setAttribute("aria-expanded", shown ? "true" : "false");
21
+ }
22
+ try {
23
+ localStorage.setItem(VAULT_TREE_STORAGE_KEY, shown ? "shown" : "hidden");
24
+ } catch {
25
+ /* a blocked web store only costs the preference, never the page */
26
+ }
27
+ }
28
+
29
+ function readGeneration(slot: Element | null): number | null {
30
+ const value = slot ? slot.getAttribute("data-vault-generation") : null;
31
+ return value === null || value === "" ? null : Number(value);
32
+ }
33
+
34
+ /** The indicator clears only once the displayed listing is as new as the newest change seen. */
35
+ function showStale(): void {
36
+ const note = document.getElementById("vault-stale");
37
+ if (!note) return;
38
+ note.hidden = !(
39
+ latestGeneration !== null &&
40
+ displayedGeneration !== null &&
41
+ latestGeneration > displayedGeneration
42
+ );
43
+ }
44
+
45
+ function openedByViewer(): (string | null)[] {
46
+ return Array.from(document.querySelectorAll("details[data-vault-children-url][open]"), (folder) =>
47
+ folder.getAttribute("data-vault-dir"),
48
+ );
49
+ }
50
+
51
+ /** Swaps server-rendered parts into place; a refreshed tree drops loaded folders and reopens the viewer's. */
52
+ function swapVaultSlots(markup: string): void {
53
+ const reopen = openedByViewer();
54
+ const parsed = new DOMParser().parseFromString(markup, "text/html");
55
+ parsed.querySelectorAll("[data-vault-slot]").forEach((slot) => {
56
+ const name = slot.getAttribute("data-vault-slot");
57
+ const target = document.querySelector(`[data-vault-slot="${name}"]`);
58
+ if (!target) return;
59
+ const node = document.importNode(slot, true) as Element;
60
+ target.replaceWith(node);
61
+ wireNavigation(node);
62
+ if (name !== "tree") return;
63
+ displayedGeneration = readGeneration(node);
64
+ node
65
+ .querySelectorAll<HTMLDetailsElement>("details[data-vault-children-url]")
66
+ .forEach((folder) => {
67
+ if (reopen.includes(folder.getAttribute("data-vault-dir"))) folder.open = true;
68
+ });
69
+ });
70
+ showStale();
71
+ runFilter();
72
+ }
73
+
74
+ function failVaultSlots(message: string): void {
75
+ document.querySelectorAll("[data-vault-slot][aria-busy]").forEach((slot) => {
76
+ slot.removeAttribute("aria-busy");
77
+ slot.textContent = message;
78
+ });
79
+ }
80
+
81
+ function loadChildren(folder: Element): void {
82
+ const url = folder.getAttribute("data-vault-children-url");
83
+ const container = folder.querySelector(".vault-children");
84
+ if (
85
+ !url ||
86
+ !container ||
87
+ folder.hasAttribute("data-vault-loaded") ||
88
+ folder.hasAttribute("data-vault-loading")
89
+ ) {
90
+ return;
91
+ }
92
+ folder.setAttribute("data-vault-loading", "");
93
+ fetchMarkup(url)
94
+ .then((markup) => {
95
+ moveInto(container, markup);
96
+ folder.setAttribute("data-vault-loaded", "");
97
+ })
98
+ .catch((error) => {
99
+ container.textContent = `This folder could not be loaded: ${failure(error)}`;
100
+ })
101
+ .finally(() => {
102
+ folder.removeAttribute("data-vault-loading");
103
+ });
104
+ }
105
+
106
+ function runFilter(): void {
107
+ const filter = document.getElementById("vault-filter") as HTMLInputElement | null;
108
+ const results = document.getElementById("vault-filter-results");
109
+ const panel = document.getElementById("vault-tree-panel");
110
+ const layout = document.querySelector("[data-vault-layout]");
111
+ if (!filter || !results || !panel || !layout) return;
112
+ clearTimeout(filterTimer);
113
+ if (filterRequest) filterRequest.abort();
114
+ const needle = filter.value.trim();
115
+ filterSequence += 1;
116
+ if (!needle) {
117
+ results.hidden = true;
118
+ results.replaceChildren();
119
+ panel.removeAttribute("data-vault-filtering");
120
+ return;
121
+ }
122
+ const url = new URL(layout.getAttribute("data-vault-filter-url") || "", location.href);
123
+ url.searchParams.set("q", needle);
124
+ const sequence = filterSequence;
125
+ filterRequest = typeof AbortController !== "undefined" ? new AbortController() : null;
126
+ fetchMarkup(url.toString(), filterRequest ? filterRequest.signal : undefined)
127
+ .then((markup) => {
128
+ if (sequence !== filterSequence) return;
129
+ moveInto(results, markup);
130
+ results.hidden = false;
131
+ panel.setAttribute("data-vault-filtering", "");
132
+ })
133
+ .catch((error) => {
134
+ if (sequence !== filterSequence || (error && error.name === "AbortError")) return;
135
+ results.textContent = `The filter could not run: ${failure(error)}`;
136
+ results.hidden = false;
137
+ panel.setAttribute("data-vault-filtering", "");
138
+ });
139
+ }
140
+
141
+ /** Replaces the tree, listing and filter results in place; the editor and terminal are left alone. */
142
+ function refreshTree(): void {
143
+ const layout = document.querySelector("[data-vault-layout]");
144
+ const status = document.getElementById("vault-refresh-status");
145
+ const url = layout ? layout.getAttribute("data-vault-refresh-url") : null;
146
+ if (!url) return;
147
+ refreshSequence += 1;
148
+ const sequence = refreshSequence;
149
+ if (status) status.hidden = true;
150
+ fetchMarkup(url)
151
+ .then((markup) => {
152
+ if (sequence === refreshSequence) swapVaultSlots(markup);
153
+ })
154
+ .catch((error) => {
155
+ if (sequence !== refreshSequence || !status) return;
156
+ status.textContent = `The tree could not be refreshed: ${failure(error)}. Select Refresh tree to retry.`;
157
+ status.hidden = false;
158
+ });
159
+ }
160
+
161
+ function watchTreeChanges(layout: Element): void {
162
+ const url = layout.getAttribute("data-vault-changes-url");
163
+ if (!url || typeof EventSource === "undefined") return;
164
+ const changes = new EventSource(url);
165
+ changes.addEventListener("message", (message) => {
166
+ let event;
167
+ try {
168
+ event = JSON.parse(message.data);
169
+ } catch {
170
+ return;
171
+ }
172
+ if (!event || event.type !== "vault-tree-changed" || typeof event.generation !== "number") {
173
+ return;
174
+ }
175
+ if (latestGeneration === null || event.generation > latestGeneration) {
176
+ latestGeneration = event.generation;
177
+ }
178
+ showStale();
179
+ });
180
+ }
181
+
182
+ function wirePanel(panel: Element): void {
183
+ panel.addEventListener("click", (raw) => {
184
+ const event = raw as MouseEvent;
185
+ if (!isPlainClick(event)) return;
186
+ const target = event.target as Element | null;
187
+ const link =
188
+ target && target.closest ? target.closest<HTMLAnchorElement>("a[data-vault-entry]") : null;
189
+ if (!link) return;
190
+ event.preventDefault();
191
+ navigate(link.href, true);
192
+ });
193
+ panel.addEventListener(
194
+ "toggle",
195
+ (event) => {
196
+ const folder = event.target as HTMLDetailsElement | null;
197
+ if (
198
+ folder &&
199
+ folder.open &&
200
+ folder.matches &&
201
+ folder.matches("details[data-vault-children-url]")
202
+ ) {
203
+ loadChildren(folder);
204
+ }
205
+ },
206
+ true,
207
+ );
208
+ }
209
+
210
+ function wireTreeToggle(): void {
211
+ const toggle = document.getElementById("vault-tree-toggle");
212
+ if (!toggle) return;
213
+ toggle.hidden = false;
214
+ const hidden = document.documentElement.getAttribute("data-vault-tree") === "hidden";
215
+ toggle.textContent = hidden ? "Show files" : "Hide files";
216
+ toggle.setAttribute("aria-expanded", hidden ? "false" : "true");
217
+ toggle.addEventListener("click", () => {
218
+ showTree(document.documentElement.getAttribute("data-vault-tree") === "hidden");
219
+ });
220
+ }
221
+
222
+ export function wireVaultBrowser(): void {
223
+ displayedGeneration = null;
224
+ latestGeneration = null;
225
+ refreshSequence = 0;
226
+ filterSequence = 0;
227
+ filterTimer = undefined;
228
+ filterRequest = null;
229
+ const layout = document.querySelector("[data-vault-layout]");
230
+ if (!layout) return;
231
+ const panel = document.getElementById("vault-tree-panel");
232
+ displayedGeneration = readGeneration(document.querySelector('[data-vault-slot="tree"]'));
233
+ if (panel) wirePanel(panel);
234
+ const refresh = document.querySelector("form[data-vault-refresh]");
235
+ if (refresh) {
236
+ refresh.addEventListener("submit", (event) => {
237
+ event.preventDefault();
238
+ refreshTree();
239
+ });
240
+ }
241
+ const filter = document.getElementById("vault-filter");
242
+ if (filter) {
243
+ filter.hidden = false;
244
+ filter.addEventListener("input", () => {
245
+ clearTimeout(filterTimer);
246
+ filterTimer = setTimeout(runFilter, FILTER_DELAY);
247
+ });
248
+ }
249
+ wireTreeToggle();
250
+ watchTreeChanges(layout);
251
+ const deferred = layout.getAttribute("data-vault-view");
252
+ if (!deferred) return;
253
+ fetchMarkup(deferred)
254
+ .then(swapVaultSlots)
255
+ .catch((error) => {
256
+ failVaultSlots(`The files could not be listed: ${failure(error)}`);
257
+ });
258
+ }
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/studio-client/build.ts from views/studio-client. Do not edit.
2
+ export const STUDIO_CLIENT_BUNDLE = "(() => {\n // packages/mate-core/src/cli/commands/studio/views/studio-client/page.ts\n var toastTimer;\n function announce(message) {\n const toast = document.getElementById(\"studio-toast\");\n if (!toast)\n return;\n toast.textContent = message;\n toast.setAttribute(\"data-shown\", \"true\");\n clearTimeout(toastTimer);\n toastTimer = setTimeout(() => {\n toast.setAttribute(\"data-shown\", \"false\");\n }, 1400);\n }\n function copy(text, label) {\n if (typeof navigator === \"undefined\" || !navigator.clipboard || typeof navigator.clipboard.writeText !== \"function\") {\n announce(\"copying is blocked in this browser\");\n return Promise.resolve();\n }\n return Promise.resolve().then(() => navigator.clipboard.writeText(text)).then(() => {\n announce(`copied ${label}`);\n }, () => {\n announce(\"copying is blocked in this browser\");\n });\n }\n function failure(error) {\n const message = error?.message;\n return message ? message : String(error);\n }\n function fetchMarkup(url, signal) {\n return fetch(url, { headers: { accept: \"text/html\" }, signal }).then((response) => {\n if (!response.ok)\n throw new Error(`status ${response.status}`);\n return response.text();\n });\n }\n function moveInto(container, markup) {\n const parsed = new DOMParser().parseFromString(markup, \"text/html\");\n container.replaceChildren(...Array.from(parsed.body.childNodes, (child) => document.importNode(child, true)));\n }\n var savePromise = null;\n function pendingSave() {\n return savePromise;\n }\n function setPendingSave(save) {\n savePromise = save;\n }\n function editor() {\n return document.getElementById(\"vault-editor\");\n }\n function dirty() {\n const node = editor();\n return !!node && node.value !== (node.getAttribute(\"data-vault-original\") || \"\");\n }\n function askBeforeNavigation() {\n return !dirty() || window.confirm(\"Discard unsaved changes?\");\n }\n function guard() {\n return (savePromise ?? Promise.resolve(true)).then((allowed) => allowed && askBeforeNavigation());\n }\n var showInPlace = null;\n function setInPlaceNavigation(show) {\n showInPlace = show;\n }\n function navigate(url, inPlace = false) {\n guard().then((allowed) => {\n if (!allowed)\n return;\n if (inPlace && showInPlace && showInPlace(url))\n return;\n location.assign(url);\n });\n }\n function isPlainClick(event) {\n return !(event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);\n }\n function navigationUrl(form, submitter) {\n const target = new URL(form.action || location.href, location.href);\n new FormData(form).forEach((value, key) => {\n if (typeof value === \"string\")\n target.searchParams.append(key, value);\n });\n const named = submitter;\n if (named && named.name && named.value)\n target.searchParams.set(named.name, named.value);\n return target.toString();\n }\n function wireNavigation(scope) {\n (scope || document).querySelectorAll('form[method=\"get\"]:not([data-vault-refresh])').forEach((form) => {\n form.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n navigate(navigationUrl(form, event.submitter), form.hasAttribute(\"data-vault-open\"));\n });\n form.querySelectorAll(\"select\").forEach((select) => {\n select.addEventListener(\"change\", () => {\n navigate(navigationUrl(form));\n });\n });\n });\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/actions.ts\n var AGENT_ORDER = [\"claude\", \"opencode\"];\n function wireCopyControls() {\n document.querySelectorAll(\"[data-copy]\").forEach((node) => {\n node.addEventListener(\"click\", () => {\n const text = node.getAttribute(\"data-copy\");\n if (text)\n copy(text, node.getAttribute(\"data-copy-label\") || \"prompt\");\n });\n });\n }\n function copyPromptFor(node) {\n const viewed = document.documentElement.getAttribute(\"data-agent-viewed\");\n const order = viewed ? [viewed, ...AGENT_ORDER] : AGENT_ORDER;\n for (const agent of order) {\n const text = node.getAttribute(`data-prompt-${agent}`);\n if (text)\n return text;\n }\n return null;\n }\n function wireDelegatedActions() {\n if (typeof document.addEventListener !== \"function\")\n return;\n document.addEventListener(\"click\", (event) => {\n const target = event.target;\n if (!target || typeof target.closest !== \"function\")\n return;\n const run = target.closest(\"[data-studio-action]\");\n if (run) {\n const agents = (run.getAttribute(\"data-studio-agents\") || \"\").split(\" \").filter(Boolean);\n document.dispatchEvent(new CustomEvent(\"studio:terminal-action\", {\n detail: {\n action: run.getAttribute(\"data-studio-action\"),\n subject: run.getAttribute(\"data-studio-subject\"),\n agents\n }\n }));\n return;\n }\n const prompt = target.closest(\"[data-studio-copy-prompt]\");\n if (prompt) {\n const text = copyPromptFor(prompt);\n if (text)\n copy(text, \"prompt\");\n }\n });\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/keys.ts\n var THEME_STORAGE_KEY = \"mate-studio-theme\";\n var COMPANION_STORAGE_KEY = \"mate-studio-companion\";\n var VAULT_TREE_STORAGE_KEY = \"mate-studio-vault-tree\";\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/companion.ts\n function rememberCompanion() {\n const scope = document.querySelector(\"[data-companion]\");\n const current = scope && scope.getAttribute(\"data-companion\");\n if (!current)\n return;\n try {\n localStorage.setItem(COMPANION_STORAGE_KEY, current);\n } catch {}\n }\n\n // packages/mate-core/src/cli/commands/studio/routes.ts\n var REPORT_VIEW_PREFIX = \"/studio/reports/\";\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/theme.ts\n var THEME_CYCLE = [\"system\", \"dark\", \"light\"];\n function readTheme() {\n try {\n const stored = localStorage.getItem(THEME_STORAGE_KEY);\n return stored && THEME_CYCLE.includes(stored) ? stored : \"system\";\n } catch {\n return \"system\";\n }\n }\n function storeTheme(theme) {\n try {\n localStorage.setItem(THEME_STORAGE_KEY, theme);\n } catch {}\n }\n var theme = \"system\";\n var onThemeChange = null;\n function currentTheme() {\n return theme;\n }\n function setThemeListener(listener) {\n onThemeChange = listener;\n }\n function applyTheme(next) {\n theme = next;\n if (onThemeChange)\n onThemeChange();\n const root = document.documentElement;\n if (next === \"system\")\n root.removeAttribute(\"data-theme\");\n else\n root.setAttribute(\"data-theme\", next);\n const control = document.getElementById(\"studio-theme\");\n if (control) {\n control.textContent = `Theme: ${next}`;\n control.setAttribute(\"data-theme-state\", next);\n }\n }\n function wireTheme() {\n onThemeChange = null;\n applyTheme(readTheme());\n }\n function wireThemeToggle() {\n const toggle = document.getElementById(\"studio-theme\");\n if (!toggle)\n return;\n toggle.addEventListener(\"click\", () => {\n const next = THEME_CYCLE[(THEME_CYCLE.indexOf(theme) + 1) % THEME_CYCLE.length];\n applyTheme(next);\n storeTheme(next);\n });\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/reports.ts\n function reportHash() {\n const match = /(?:^|[#&])report=([0-9a-f]{32})/.exec(location.hash || \"\");\n return match ? match[1] : null;\n }\n function wireReports() {\n setThemeListener(null);\n const shell = document.querySelector(\"[data-reports-events-url]\");\n if (!shell || typeof EventSource === \"undefined\")\n return;\n const current = shell.getAttribute(\"data-companion\");\n const listUrl = shell.getAttribute(\"data-reports-url\");\n const viewUrl = shell.getAttribute(\"data-reports-view-url\");\n const list = document.getElementById(\"reports-list\");\n const frame = document.getElementById(\"reports-frame\");\n const open = document.getElementById(\"reports-open\");\n const empty = document.getElementById(\"reports-empty\");\n const template = document.getElementById(\"reports-item-template\");\n const badge = document.getElementById(\"reports-badge\");\n const toast = document.getElementById(\"reports-toast\");\n const toastTitle = document.getElementById(\"reports-toast-title\");\n const toastOpen = document.getElementById(\"reports-toast-open\");\n let shown = null;\n let toastTimeout;\n function show(id) {\n if (!frame)\n return;\n shown = id;\n const theme = currentTheme();\n const src = `${REPORT_VIEW_PREFIX}${id}${theme === \"system\" ? \"\" : `?scheme=${theme}`}`;\n frame.src = src;\n frame.hidden = false;\n if (open) {\n open.href = src;\n open.hidden = false;\n }\n if (list) {\n list.querySelectorAll(\"button[data-report-id]\").forEach((button) => {\n button.setAttribute(\"aria-pressed\", button.getAttribute(\"data-report-id\") === id ? \"true\" : \"false\");\n });\n }\n try {\n history.replaceState(null, \"\", `#report=${id}`);\n } catch {}\n }\n function render(reports) {\n const first = template && template.content.firstElementChild;\n if (!list || !template || !first)\n return;\n list.replaceChildren();\n if (empty)\n empty.hidden = reports.length > 0;\n for (const report of reports) {\n const item = first.cloneNode(true);\n const button = item.querySelector(\"button\");\n button.setAttribute(\"data-report-id\", report.id);\n button.setAttribute(\"aria-pressed\", report.id === shown ? \"true\" : \"false\");\n item.querySelector(\"[data-report-title]\").textContent = report.title;\n item.querySelector(\"[data-report-time]\").textContent = new Date(report.createdAt).toLocaleString();\n button.addEventListener(\"click\", () => show(report.id));\n list.append(item);\n }\n if (reports.length === 0 || reports.some((report) => report.id === shown))\n return;\n const named = reportHash();\n show(reports.some((report) => report.id === named) ? named : reports[0].id);\n }\n function refreshList() {\n if (!list || !listUrl)\n return;\n fetch(listUrl, { headers: { accept: \"application/json\" } }).then((response) => {\n if (!response.ok)\n throw new Error(`status ${response.status}`);\n return response.json();\n }).then((body) => render(body.reports || [])).catch(() => {});\n }\n function announceReport(event) {\n if (badge)\n badge.hidden = false;\n if (!toast || !toastTitle)\n return;\n toastTitle.textContent = `New report: ${event.title}`;\n if (toastOpen && viewUrl)\n toastOpen.href = `${viewUrl}#report=${event.id}`;\n toast.setAttribute(\"data-shown\", \"true\");\n clearTimeout(toastTimeout);\n toastTimeout = setTimeout(() => toast.setAttribute(\"data-shown\", \"false\"), 1e4);\n }\n setThemeListener(() => {\n if (shown)\n show(shown);\n });\n const events = new EventSource(shell.getAttribute(\"data-reports-events-url\"));\n events.addEventListener(\"open\", refreshList);\n events.addEventListener(\"message\", (message) => {\n let event;\n try {\n event = JSON.parse(message.data);\n } catch {\n return;\n }\n if (!event || event.type !== \"report-published\" || event.companion !== current)\n return;\n if (list)\n refreshList();\n else\n announceReport(event);\n });\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/panels.ts\n function wireSelectPanels({ root, select, panel, hidePanels }) {\n document.querySelectorAll(`[${root}]`).forEach((scope) => {\n const control = scope.querySelector(`[${select}]`);\n if (!control)\n return;\n const panels = scope.querySelectorAll(`[${panel}]`);\n function show() {\n panels.forEach((node) => {\n const active = node.getAttribute(panel) === control.value;\n node.setAttribute(\"data-active\", active ? \"true\" : \"false\");\n if (hidePanels)\n node.setAttribute(\"aria-hidden\", active ? \"false\" : \"true\");\n });\n }\n control.addEventListener(\"change\", show);\n show();\n });\n }\n function wireWorkflowSchemaSelect() {\n wireSelectPanels({\n root: \"data-workflow-schema-root\",\n select: \"data-workflow-schema-select\",\n panel: \"data-workflow-schema-panel\",\n hidePanels: false\n });\n }\n function wireSkillsSelect() {\n wireSelectPanels({\n root: \"data-skills-root\",\n select: \"data-skills-select\",\n panel: \"data-skill-panel\",\n hidePanels: true\n });\n }\n function markSelectedStage() {\n document.querySelectorAll(\"a[data-map-stage]\").forEach((link) => {\n if (link.getAttribute(\"href\") === location.hash)\n link.setAttribute(\"aria-current\", \"true\");\n else\n link.removeAttribute(\"aria-current\");\n });\n }\n function wireWorkflowStageSelection() {\n if (!document.querySelector(\"a[data-map-stage]\"))\n return;\n window.addEventListener(\"hashchange\", markSelectedStage);\n markSelectedStage();\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/vault-editor.ts\n function status(message) {\n const node = document.getElementById(\"vault-status\");\n if (node)\n node.textContent = message;\n }\n function setIncoming(event) {\n const box = document.getElementById(\"vault-incoming\");\n const content = document.getElementById(\"vault-incoming-content\");\n const recover = document.getElementById(\"vault-recover\");\n if (!box || !content || !recover)\n return;\n box.hidden = false;\n content.textContent = event.content || \"\";\n recover.setAttribute(\"data-vault-content\", event.content || \"\");\n recover.setAttribute(\"data-vault-token\", event.token || \"\");\n }\n function acceptIncoming(event) {\n const node = editor();\n if (!node)\n return;\n node.value = event.content || \"\";\n node.setAttribute(\"data-vault-original\", node.value);\n if (event.token)\n node.setAttribute(\"data-vault-token\", event.token);\n const box = document.getElementById(\"vault-incoming\");\n if (box)\n box.hidden = true;\n status(\"incoming version loaded into the editor\");\n }\n function save(node, hooks) {\n if (pendingSave())\n return;\n const companion = document.querySelector(\"[data-companion]\");\n const body = {\n companion: companion ? companion.getAttribute(\"data-companion\") : \"\",\n path: node.getAttribute(\"data-vault-path\"),\n content: node.value,\n token: node.getAttribute(\"data-vault-token\")\n };\n const request = fetch(\"/api/vault/save\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body)\n }).then((response) => response.json().then((result) => {\n if (!response.ok) {\n if (response.status === 409)\n setIncoming(result);\n status(result.reason || \"save refused\");\n return false;\n }\n node.setAttribute(\"data-vault-token\", result.token || \"\");\n node.setAttribute(\"data-vault-original\", node.value);\n status(\"saved\");\n hooks.saved();\n return true;\n })).catch(() => {\n status(\"save failed\");\n return false;\n }).finally(() => {\n setPendingSave(null);\n });\n setPendingSave(request);\n }\n var stream = null;\n function followStream(eventsUrl, node, hooks) {\n const events = new EventSource(eventsUrl);\n stream = events;\n events.addEventListener(\"message\", (message) => {\n let event;\n try {\n event = JSON.parse(message.data);\n } catch {\n return;\n }\n if (event.kind === \"removed\") {\n status(\"this file no longer exists on disk\");\n return;\n }\n if (!node) {\n hooks.changed();\n return;\n }\n if (event.kind === \"overwritten\") {\n setIncoming(event);\n status(\"a version was overwritten; its content is available below\");\n return;\n }\n if (dirty()) {\n setIncoming(event);\n status(\"the file changed on disk; unsaved content is still in the editor\");\n return;\n }\n node.value = event.content || \"\";\n node.setAttribute(\"data-vault-original\", node.value);\n if (event.token)\n node.setAttribute(\"data-vault-token\", event.token);\n status(\"updated from disk\");\n });\n }\n function unmountViewer() {\n if (stream) {\n if (typeof stream.close === \"function\")\n stream.close();\n stream = null;\n }\n setPendingSave(null);\n }\n function mountViewer(hooks) {\n unmountViewer();\n const root = document.querySelector(\"[data-vault-root]\");\n if (!root)\n return;\n const node = editor();\n if (node) {\n node.setAttribute(\"data-vault-original\", node.value);\n const saveButton = document.getElementById(\"vault-save\");\n if (saveButton)\n saveButton.addEventListener(\"click\", () => save(node, hooks));\n const recover = document.getElementById(\"vault-recover\");\n if (recover) {\n recover.addEventListener(\"click\", () => {\n acceptIncoming({\n content: recover.getAttribute(\"data-vault-content\") || \"\",\n token: recover.getAttribute(\"data-vault-token\") || \"\"\n });\n });\n }\n }\n const eventsUrl = root.getAttribute(\"data-vault-events\");\n if (eventsUrl && typeof EventSource !== \"undefined\")\n followStream(eventsUrl, node, hooks);\n }\n function wireUnloadGuard() {\n if (typeof window === \"undefined\")\n return;\n window.addEventListener(\"beforeunload\", (event) => {\n if (!dirty())\n return;\n event.preventDefault();\n event.returnValue = \"\";\n });\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/vault-pane.ts\n function entry(offset) {\n return { vaultScope: scope(), vaultScroll: offset };\n }\n var swapSequence = 0;\n var shownUrl = \"\";\n function layout() {\n return document.querySelector(\"[data-vault-layout]\");\n }\n function scrollOffset() {\n return typeof window !== \"undefined\" && typeof window.scrollY === \"number\" ? window.scrollY : 0;\n }\n function scrollTo(offset) {\n if (typeof window !== \"undefined\" && typeof window.scrollTo === \"function\") {\n window.scrollTo(0, offset);\n }\n }\n function paneAddress(url) {\n const route = layout()?.getAttribute(\"data-vault-pane-route\");\n if (!route)\n return null;\n try {\n const target = new URL(url, location.href);\n const current = new URL(location.href);\n if (target.origin !== current.origin || target.pathname !== current.pathname)\n return null;\n const pane = new URL(route, location.href);\n pane.search = target.search;\n return pane.toString();\n } catch {\n return null;\n }\n }\n function scope() {\n return layout()?.getAttribute(\"data-vault-scope\") ?? \"\";\n }\n function markShown() {\n const root = document.querySelector(\"[data-vault-root]\");\n const path = root ? root.getAttribute(\"data-vault-path\") : null;\n document.querySelectorAll(\"a[data-vault-entry]\").forEach((link) => {\n if (path && link.getAttribute(\"data-vault-entry\") === path) {\n link.setAttribute(\"aria-current\", \"page\");\n } else {\n link.removeAttribute(\"aria-current\");\n }\n });\n }\n function applyPane(markup, offset) {\n const parsed = new DOMParser().parseFromString(markup, \"text/html\");\n const crumb = parsed.querySelector(\"[data-vault-crumb]\");\n const pane = parsed.querySelector(\"[data-vault-pane]\");\n const shownCrumb = document.querySelector(\"[data-vault-crumb]\");\n const shownPane = document.querySelector(\"[data-vault-pane]\");\n if (!crumb || !pane || !shownCrumb || !shownPane)\n throw new Error(\"no pane in the answer\");\n unmountViewer();\n const nextCrumb = document.importNode(crumb, true);\n const nextPane = document.importNode(pane, true);\n shownCrumb.replaceWith(nextCrumb);\n shownPane.replaceWith(nextPane);\n wireNavigation(nextCrumb);\n wireNavigation(nextPane);\n mountViewer(hooks);\n markShown();\n scrollTo(offset);\n }\n function swap(url, move, offset, fallback) {\n const address = paneAddress(url);\n if (!address) {\n fallback();\n return;\n }\n swapSequence += 1;\n const mine = swapSequence;\n fetchMarkup(address).then((markup) => {\n if (mine !== swapSequence)\n return;\n applyPane(markup, offset);\n const target = new URL(url, location.href).toString();\n if (move === \"push\") {\n history.replaceState({ ...history.state, vaultScroll: offset }, \"\");\n history.pushState(entry(offset), \"\", target);\n } else if (move === \"replace\") {\n history.replaceState(entry(offset), \"\", target);\n }\n shownUrl = target;\n }).catch(() => {\n if (mine === swapSequence)\n fallback();\n });\n }\n function showFile(url) {\n if (!paneAddress(url))\n return false;\n swap(url, \"push\", scrollOffset(), () => location.assign(url));\n return true;\n }\n var hooks = {\n saved() {\n const root = document.querySelector(\"[data-vault-root]\");\n const read = root ? root.getAttribute(\"data-vault-read-href\") : null;\n if (!read)\n return;\n const target = new URL(read, location.href).toString();\n const offset = scrollOffset();\n history.replaceState(entry(offset), \"\", target);\n shownUrl = target;\n swap(target, \"none\", offset, () => location.assign(target));\n status(\"saved\");\n },\n changed() {\n const target = shownUrl || location.href;\n swap(target, \"none\", scrollOffset(), () => {});\n }\n };\n function wireLinks(main) {\n main.addEventListener(\"click\", (raw) => {\n const event = raw;\n if (!isPlainClick(event))\n return;\n const target = event.target;\n const link = target && target.closest ? target.closest(\"a[data-vault-entry], a[data-vault-mode-link]\") : null;\n if (!link)\n return;\n event.preventDefault();\n navigate(link.href, true);\n });\n }\n function wireHistory() {\n if (typeof window === \"undefined\")\n return;\n window.addEventListener(\"popstate\", () => {\n const next = location.href;\n const previous = shownUrl;\n const state = history.state;\n const saved = state?.vaultScroll ?? 0;\n const swappable = !!state && state.vaultScope === scope();\n guard().then((allowed) => {\n if (!allowed) {\n history.pushState(entry(scrollOffset()), \"\", previous);\n return;\n }\n if (swappable)\n swap(next, \"none\", saved, () => location.reload());\n else\n location.reload();\n });\n });\n }\n function wireVaultPane() {\n const shown = layout();\n if (!shown)\n return;\n shownUrl = location.href;\n if (document.querySelector(\"[data-vault-root]\")) {\n history.replaceState({ ...history.state, ...entry(scrollOffset()) }, \"\");\n }\n if (typeof history !== \"undefined\" && \"scrollRestoration\" in history) {\n history.scrollRestoration = \"manual\";\n }\n setInPlaceNavigation(showFile);\n const main = document.querySelector(\"[data-vault-main]\");\n if (main)\n wireLinks(main);\n wireHistory();\n mountViewer(hooks);\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/vault-tree.ts\n var FILTER_DELAY = 150;\n var displayedGeneration = null;\n var latestGeneration = null;\n var refreshSequence = 0;\n var filterSequence = 0;\n var filterTimer;\n var filterRequest = null;\n function showTree(shown) {\n const root = document.documentElement;\n if (shown)\n root.removeAttribute(\"data-vault-tree\");\n else\n root.setAttribute(\"data-vault-tree\", \"hidden\");\n const toggle = document.getElementById(\"vault-tree-toggle\");\n if (toggle) {\n toggle.textContent = shown ? \"Hide files\" : \"Show files\";\n toggle.setAttribute(\"aria-expanded\", shown ? \"true\" : \"false\");\n }\n try {\n localStorage.setItem(VAULT_TREE_STORAGE_KEY, shown ? \"shown\" : \"hidden\");\n } catch {}\n }\n function readGeneration(slot) {\n const value = slot ? slot.getAttribute(\"data-vault-generation\") : null;\n return value === null || value === \"\" ? null : Number(value);\n }\n function showStale() {\n const note = document.getElementById(\"vault-stale\");\n if (!note)\n return;\n note.hidden = !(latestGeneration !== null && displayedGeneration !== null && latestGeneration > displayedGeneration);\n }\n function openedByViewer() {\n return Array.from(document.querySelectorAll(\"details[data-vault-children-url][open]\"), (folder) => folder.getAttribute(\"data-vault-dir\"));\n }\n function swapVaultSlots(markup) {\n const reopen = openedByViewer();\n const parsed = new DOMParser().parseFromString(markup, \"text/html\");\n parsed.querySelectorAll(\"[data-vault-slot]\").forEach((slot) => {\n const name = slot.getAttribute(\"data-vault-slot\");\n const target = document.querySelector(`[data-vault-slot=\"${name}\"]`);\n if (!target)\n return;\n const node = document.importNode(slot, true);\n target.replaceWith(node);\n wireNavigation(node);\n if (name !== \"tree\")\n return;\n displayedGeneration = readGeneration(node);\n node.querySelectorAll(\"details[data-vault-children-url]\").forEach((folder) => {\n if (reopen.includes(folder.getAttribute(\"data-vault-dir\")))\n folder.open = true;\n });\n });\n showStale();\n runFilter();\n }\n function failVaultSlots(message) {\n document.querySelectorAll(\"[data-vault-slot][aria-busy]\").forEach((slot) => {\n slot.removeAttribute(\"aria-busy\");\n slot.textContent = message;\n });\n }\n function loadChildren(folder) {\n const url = folder.getAttribute(\"data-vault-children-url\");\n const container = folder.querySelector(\".vault-children\");\n if (!url || !container || folder.hasAttribute(\"data-vault-loaded\") || folder.hasAttribute(\"data-vault-loading\")) {\n return;\n }\n folder.setAttribute(\"data-vault-loading\", \"\");\n fetchMarkup(url).then((markup) => {\n moveInto(container, markup);\n folder.setAttribute(\"data-vault-loaded\", \"\");\n }).catch((error) => {\n container.textContent = `This folder could not be loaded: ${failure(error)}`;\n }).finally(() => {\n folder.removeAttribute(\"data-vault-loading\");\n });\n }\n function runFilter() {\n const filter = document.getElementById(\"vault-filter\");\n const results = document.getElementById(\"vault-filter-results\");\n const panel = document.getElementById(\"vault-tree-panel\");\n const layout = document.querySelector(\"[data-vault-layout]\");\n if (!filter || !results || !panel || !layout)\n return;\n clearTimeout(filterTimer);\n if (filterRequest)\n filterRequest.abort();\n const needle = filter.value.trim();\n filterSequence += 1;\n if (!needle) {\n results.hidden = true;\n results.replaceChildren();\n panel.removeAttribute(\"data-vault-filtering\");\n return;\n }\n const url = new URL(layout.getAttribute(\"data-vault-filter-url\") || \"\", location.href);\n url.searchParams.set(\"q\", needle);\n const sequence = filterSequence;\n filterRequest = typeof AbortController !== \"undefined\" ? new AbortController : null;\n fetchMarkup(url.toString(), filterRequest ? filterRequest.signal : undefined).then((markup) => {\n if (sequence !== filterSequence)\n return;\n moveInto(results, markup);\n results.hidden = false;\n panel.setAttribute(\"data-vault-filtering\", \"\");\n }).catch((error) => {\n if (sequence !== filterSequence || error && error.name === \"AbortError\")\n return;\n results.textContent = `The filter could not run: ${failure(error)}`;\n results.hidden = false;\n panel.setAttribute(\"data-vault-filtering\", \"\");\n });\n }\n function refreshTree() {\n const layout = document.querySelector(\"[data-vault-layout]\");\n const status = document.getElementById(\"vault-refresh-status\");\n const url = layout ? layout.getAttribute(\"data-vault-refresh-url\") : null;\n if (!url)\n return;\n refreshSequence += 1;\n const sequence = refreshSequence;\n if (status)\n status.hidden = true;\n fetchMarkup(url).then((markup) => {\n if (sequence === refreshSequence)\n swapVaultSlots(markup);\n }).catch((error) => {\n if (sequence !== refreshSequence || !status)\n return;\n status.textContent = `The tree could not be refreshed: ${failure(error)}. Select Refresh tree to retry.`;\n status.hidden = false;\n });\n }\n function watchTreeChanges(layout) {\n const url = layout.getAttribute(\"data-vault-changes-url\");\n if (!url || typeof EventSource === \"undefined\")\n return;\n const changes = new EventSource(url);\n changes.addEventListener(\"message\", (message) => {\n let event;\n try {\n event = JSON.parse(message.data);\n } catch {\n return;\n }\n if (!event || event.type !== \"vault-tree-changed\" || typeof event.generation !== \"number\") {\n return;\n }\n if (latestGeneration === null || event.generation > latestGeneration) {\n latestGeneration = event.generation;\n }\n showStale();\n });\n }\n function wirePanel(panel) {\n panel.addEventListener(\"click\", (raw) => {\n const event = raw;\n if (!isPlainClick(event))\n return;\n const target = event.target;\n const link = target && target.closest ? target.closest(\"a[data-vault-entry]\") : null;\n if (!link)\n return;\n event.preventDefault();\n navigate(link.href, true);\n });\n panel.addEventListener(\"toggle\", (event) => {\n const folder = event.target;\n if (folder && folder.open && folder.matches && folder.matches(\"details[data-vault-children-url]\")) {\n loadChildren(folder);\n }\n }, true);\n }\n function wireTreeToggle() {\n const toggle = document.getElementById(\"vault-tree-toggle\");\n if (!toggle)\n return;\n toggle.hidden = false;\n const hidden = document.documentElement.getAttribute(\"data-vault-tree\") === \"hidden\";\n toggle.textContent = hidden ? \"Show files\" : \"Hide files\";\n toggle.setAttribute(\"aria-expanded\", hidden ? \"false\" : \"true\");\n toggle.addEventListener(\"click\", () => {\n showTree(document.documentElement.getAttribute(\"data-vault-tree\") === \"hidden\");\n });\n }\n function wireVaultBrowser() {\n displayedGeneration = null;\n latestGeneration = null;\n refreshSequence = 0;\n filterSequence = 0;\n filterTimer = undefined;\n filterRequest = null;\n const layout = document.querySelector(\"[data-vault-layout]\");\n if (!layout)\n return;\n const panel = document.getElementById(\"vault-tree-panel\");\n displayedGeneration = readGeneration(document.querySelector('[data-vault-slot=\"tree\"]'));\n if (panel)\n wirePanel(panel);\n const refresh = document.querySelector(\"form[data-vault-refresh]\");\n if (refresh) {\n refresh.addEventListener(\"submit\", (event) => {\n event.preventDefault();\n refreshTree();\n });\n }\n const filter = document.getElementById(\"vault-filter\");\n if (filter) {\n filter.hidden = false;\n filter.addEventListener(\"input\", () => {\n clearTimeout(filterTimer);\n filterTimer = setTimeout(runFilter, FILTER_DELAY);\n });\n }\n wireTreeToggle();\n watchTreeChanges(layout);\n const deferred = layout.getAttribute(\"data-vault-view\");\n if (!deferred)\n return;\n fetchMarkup(deferred).then(swapVaultSlots).catch((error) => {\n failVaultSlots(`The files could not be listed: ${failure(error)}`);\n });\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/index.ts\n function startStudioClient() {\n wireTheme();\n rememberCompanion();\n wireWorkflowSchemaSelect();\n wireWorkflowStageSelection();\n wireSkillsSelect();\n wireThemeToggle();\n wireCopyControls();\n wireDelegatedActions();\n wireUnloadGuard();\n wireNavigation();\n wireVaultPane();\n wireVaultBrowser();\n wireReports();\n }\n\n // packages/mate-core/src/cli/commands/studio/views/studio-client/main.ts\n startStudioClient();\n})();\n";