zidane-ui 9.1.6 → 9.1.9

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 (224) hide show
  1. package/dist/adapters/run-host.js +17 -20
  2. package/dist/adapters/server-files-host.js +3 -1
  3. package/dist/adapters/server-host.js +18 -11
  4. package/dist/adapters/server-machines-host.js +3 -1
  5. package/dist/adapters/server-session-host.js +2 -1
  6. package/dist/adapters/server-settings-accounts.js +2 -1
  7. package/dist/adapters/server-settings-host.js +3 -3
  8. package/dist/adapters/sse-host.js +9 -7
  9. package/dist/chat/SessionGroupHeader.js +2 -2
  10. package/dist/chat/SessionHostHeader.js +1 -1
  11. package/dist/chat/StatusDot.js +24 -20
  12. package/dist/chat/composer/Composer.d.ts +13 -8
  13. package/dist/chat/composer/Composer.js +14 -10
  14. package/dist/chat/composer/ComposerEditor.js +1 -1
  15. package/dist/chat/composer/HostComposer.d.ts +1 -1
  16. package/dist/chat/composer/HostComposer.js +7 -10
  17. package/dist/chat/composer/RunStatusChip.js +13 -1
  18. package/dist/chat/composer/attachment-ingest.d.ts +4 -1
  19. package/dist/chat/composer/attachment-ingest.js +27 -10
  20. package/dist/chat/composer/composer-bus.d.ts +9 -1
  21. package/dist/chat/composer/composer-bus.js +49 -6
  22. package/dist/chat/composer/index.d.ts +2 -2
  23. package/dist/chat/composer/index.js +2 -2
  24. package/dist/chat/composer/use-draft-target.d.ts +4 -0
  25. package/dist/chat/composer/use-draft-target.js +15 -4
  26. package/dist/chat/composer/use-send-prompt.d.ts +5 -1
  27. package/dist/chat/composer/use-send-prompt.js +2 -12
  28. package/dist/chat/headless/chip-labels.d.ts +2 -0
  29. package/dist/chat/headless/chip-labels.js +3 -1
  30. package/dist/chat/headless/composer/labels-editor.d.ts +2 -0
  31. package/dist/chat/headless/composer/labels-editor.js +2 -0
  32. package/dist/chat/headless/file-chip.d.ts +12 -13
  33. package/dist/chat/headless/file-chip.js +40 -2
  34. package/dist/chat/headless/fixture-host.js +14 -17
  35. package/dist/chat/headless/host.d.ts +24 -6
  36. package/dist/chat/headless/index.d.ts +4 -4
  37. package/dist/chat/headless/index.js +4 -4
  38. package/dist/chat/headless/labels.d.ts +16 -0
  39. package/dist/chat/headless/labels.js +15 -2
  40. package/dist/chat/headless/send-flow.d.ts +21 -4
  41. package/dist/chat/headless/send-flow.js +25 -6
  42. package/dist/chat/headless/session-list.d.ts +11 -0
  43. package/dist/chat/headless/sidebar/index.js +1 -1
  44. package/dist/chat/headless/sidebar/labels.d.ts +12 -1
  45. package/dist/chat/headless/transcript-host.d.ts +8 -1
  46. package/dist/chat/headless/transcript-labels.d.ts +2 -0
  47. package/dist/chat/index.d.ts +3 -3
  48. package/dist/chat/index.js +2 -2
  49. package/dist/chat/interactions/InboxPanel.js +2 -2
  50. package/dist/chat/interactions/QuestionField.js +2 -2
  51. package/dist/chat/rich-composer/RichComposer.d.ts +9 -2
  52. package/dist/chat/rich-composer/RichComposer.js +28 -7
  53. package/dist/chat/rich-composer/composer-extensions.d.ts +2 -0
  54. package/dist/chat/rich-composer/composer-extensions.js +2 -2
  55. package/dist/chat/rich-composer/composer-keys.js +1 -1
  56. package/dist/chat/rich-composer/use-composer-bus.js +2 -1
  57. package/dist/chat/rich-composer/use-composer-editor.d.ts +4 -1
  58. package/dist/chat/rich-composer/use-composer-editor.js +2 -1
  59. package/dist/chat/sidebar/SessionSidebar.js +1 -2
  60. package/dist/chat/sidebar/SidebarSearchRow.js +1 -1
  61. package/dist/chat/sidebar/useSidebarKeyboardNav.d.ts +0 -2
  62. package/dist/chat/sidebar/useSidebarKeyboardNav.js +7 -13
  63. package/dist/chat/testing/contract.d.ts +1 -1
  64. package/dist/chat/testing/contract.js +8 -7
  65. package/dist/chat/transcript/BadgeMenu.d.ts +12 -3
  66. package/dist/chat/transcript/BadgeMenu.js +20 -3
  67. package/dist/chat/transcript/CodemodeScript.js +147 -44
  68. package/dist/chat/transcript/EditDiffBlock.js +1 -4
  69. package/dist/chat/transcript/FileChip.js +60 -14
  70. package/dist/chat/transcript/LiveOutput.js +7 -3
  71. package/dist/chat/transcript/badges.js +2 -2
  72. package/dist/chat/transcript/index.d.ts +2 -2
  73. package/dist/chat/transcript/markdown-context.d.ts +4 -1
  74. package/dist/chat/transcript/rolling.d.ts +12 -0
  75. package/dist/chat/transcript/rolling.js +22 -0
  76. package/dist/chat/transcript/script-input.d.ts +1 -1
  77. package/dist/chat/transcript/shell-block.js +70 -60
  78. package/dist/chat/transcript/tool-row.js +21 -5
  79. package/dist/code/editor/FileTree.d.ts +3 -1
  80. package/dist/code/editor/FileTree.js +29 -1
  81. package/dist/components/Restricted.d.ts +25 -0
  82. package/dist/components/Restricted.js +32 -0
  83. package/dist/components/StepFrame.d.ts +1 -1
  84. package/dist/components/ZidaneMark.d.ts +28 -0
  85. package/dist/components/ZidaneMark.js +114 -0
  86. package/dist/components/palette/PalettePanel.d.ts +7 -1
  87. package/dist/components/palette/PalettePanel.js +37 -11
  88. package/dist/components/zidane-mark.d.ts +52 -0
  89. package/dist/components/zidane-mark.js +94 -0
  90. package/dist/core.css +59 -6
  91. package/dist/dist/{index-DqCio7Nd.d.ts → index-Bq87OcGy.d.ts} +19 -12
  92. package/dist/dist/index.d.ts +1 -1
  93. package/dist/extensions/ExtensionSettingsSection.d.ts +18 -0
  94. package/dist/extensions/ExtensionSettingsSection.js +227 -0
  95. package/dist/extensions/headless/host.d.ts +1 -1
  96. package/dist/extensions/headless/labels.d.ts +41 -1
  97. package/dist/extensions/headless/labels.js +57 -0
  98. package/dist/extensions/headless/select.js +1 -2
  99. package/dist/extensions/index.d.ts +2 -1
  100. package/dist/extensions/index.js +2 -1
  101. package/dist/files/FilesTab.js +2 -0
  102. package/dist/files/TreeColumn.d.ts +5 -1
  103. package/dist/files/TreeColumn.js +6 -0
  104. package/dist/files/headless/fixture-host.js +5 -3
  105. package/dist/files/headless/host.d.ts +24 -6
  106. package/dist/files/headless/index.d.ts +2 -2
  107. package/dist/files/headless/labels.d.ts +1 -0
  108. package/dist/files/headless/labels.js +1 -0
  109. package/dist/files/testing/contract.js +9 -2
  110. package/dist/files/useWorkspace.d.ts +14 -2
  111. package/dist/files/useWorkspace.js +87 -9
  112. package/dist/git/BranchMenu.js +1 -1
  113. package/dist/git/MoveSessionDialog.js +1 -1
  114. package/dist/git/RemoteFolderPicker.js +1 -1
  115. package/dist/git/WherePicker.js +1 -1
  116. package/dist/git/headless/index.js +1 -1
  117. package/dist/git/useEnvironmentChoices.js +1 -1
  118. package/dist/index.d.ts +4 -2
  119. package/dist/index.js +4 -2
  120. package/dist/keybindings/headless/catalog.js +1 -1
  121. package/dist/labels.d.ts +1 -0
  122. package/dist/labels.js +1 -0
  123. package/dist/machines/DaemonMenu.js +1 -1
  124. package/dist/machines/HostCapabilities.d.ts +23 -1
  125. package/dist/machines/HostCapabilities.js +149 -65
  126. package/dist/machines/HostSyncPanel.js +129 -123
  127. package/dist/machines/HostsSection.js +5 -1
  128. package/dist/machines/ServerCard.d.ts +4 -2
  129. package/dist/machines/ServerCard.js +222 -99
  130. package/dist/machines/ServerMachinesPanel.js +16 -13
  131. package/dist/machines/ServerUpdatePanel.js +31 -21
  132. package/dist/machines/TokensPanel.js +75 -53
  133. package/dist/machines/headless/fixture-host.d.ts +5 -0
  134. package/dist/machines/headless/fixture-host.js +13 -4
  135. package/dist/machines/headless/fixtures.js +1 -1
  136. package/dist/machines/headless/host.d.ts +22 -2
  137. package/dist/machines/headless/index.d.ts +2 -2
  138. package/dist/machines/headless/labels.d.ts +22 -1
  139. package/dist/machines/headless/labels.js +22 -2
  140. package/dist/machines/testing/contract.d.ts +3 -1
  141. package/dist/machines/testing/contract.js +9 -0
  142. package/dist/motion/size.d.ts +22 -1
  143. package/dist/motion/size.js +82 -4
  144. package/dist/onboarding/demos/delivery.d.ts +16 -0
  145. package/dist/onboarding/demos/delivery.js +132 -0
  146. package/dist/onboarding/demos/index.d.ts +2 -1
  147. package/dist/onboarding/demos/index.js +2 -1
  148. package/dist/onboarding/headless/labels.d.ts +8 -0
  149. package/dist/onboarding/headless/labels.js +7 -0
  150. package/dist/onboarding/index.d.ts +2 -1
  151. package/dist/onboarding/index.js +2 -1
  152. package/dist/onboarding/steps/ConversationStep.js +46 -4
  153. package/dist/palette/CommandPalette.js +3 -0
  154. package/dist/palette/PaletteDialog.js +7 -3
  155. package/dist/palette/usePaletteController.d.ts +10 -0
  156. package/dist/palette/usePaletteController.js +25 -3
  157. package/dist/server/sse-handler.js +6 -1
  158. package/dist/session/ArtifactsPanel.js +1 -1
  159. package/dist/session/SideChat.js +1 -2
  160. package/dist/session/UsagePanel.js +1 -1
  161. package/dist/session/headless/host.d.ts +3 -1
  162. package/dist/session/headless/index.js +2 -2
  163. package/dist/settings/SettingsRestricted.d.ts +25 -0
  164. package/dist/settings/SettingsRestricted.js +44 -0
  165. package/dist/settings/SettingsRowsPage.js +31 -37
  166. package/dist/settings/headless/catalog.js +7 -6
  167. package/dist/settings/headless/labels.d.ts +4 -2
  168. package/dist/settings/headless/labels.js +4 -2
  169. package/dist/settings/headless/layout.js +1 -1
  170. package/dist/settings/headless/providers.d.ts +2 -0
  171. package/dist/settings/headless/search.d.ts +1 -1
  172. package/dist/settings/index.d.ts +2 -2
  173. package/dist/settings/index.js +2 -2
  174. package/dist/settings/rows.d.ts +51 -8
  175. package/dist/settings/rows.js +81 -26
  176. package/dist/settings/sections/enabled-list/EnabledListSection.js +3 -2
  177. package/dist/settings/sections/environments/EnvironmentsSection.js +4 -3
  178. package/dist/settings/sections/extensions/ExtensionConfigRows.d.ts +2 -2
  179. package/dist/settings/sections/extensions/ExtensionConfigRows.js +22 -25
  180. package/dist/settings/sections/extensions/ExtensionOptionsSection.js +3 -2
  181. package/dist/settings/sections/extensions/options-labels.d.ts +2 -2
  182. package/dist/settings/sections/extensions/options-labels.js +2 -2
  183. package/dist/settings/sections/host-catalog/HostCatalogSection.d.ts +5 -3
  184. package/dist/settings/sections/host-catalog/HostCatalogSection.js +7 -4
  185. package/dist/settings/sections/instructions/InstructionsSection.js +24 -17
  186. package/dist/settings/sections/logs/LogsSection.d.ts +2 -1
  187. package/dist/settings/sections/logs/LogsSection.js +64 -56
  188. package/dist/settings/sections/mcp/McpSection.d.ts +1 -1
  189. package/dist/settings/sections/mcp/McpSection.js +18 -17
  190. package/dist/settings/sections/models/ModelRows.d.ts +3 -3
  191. package/dist/settings/sections/models/ModelRows.js +8 -7
  192. package/dist/settings/sections/performance/PerformanceSection.js +13 -4
  193. package/dist/settings/sections/performance/labels.d.ts +0 -1
  194. package/dist/settings/sections/performance/labels.js +0 -1
  195. package/dist/settings/sections/providers/CredentialSources.js +18 -8
  196. package/dist/settings/sections/providers/ProvidersSection.d.ts +2 -2
  197. package/dist/settings/sections/providers/ProvidersSection.js +20 -17
  198. package/dist/settings/sections/providers/ServerProvidersSection.d.ts +1 -1
  199. package/dist/settings/sections/providers/ServerProvidersSection.js +10 -14
  200. package/dist/settings/sections/shared/catalog.js +3 -2
  201. package/dist/settings/sections/skills/SkillList.js +3 -2
  202. package/dist/settings/testing/accounts-demo.js +1 -0
  203. package/dist/settings/testing/accounts.contract.js +2 -0
  204. package/dist/styles.css +160 -24
  205. package/dist/terminal/HostTerminalSurface.js +1 -1
  206. package/dist/terminal/headless/links.d.ts +19 -1
  207. package/dist/terminal/headless/links.js +26 -2
  208. package/dist/theme/css-vars.js +1 -0
  209. package/dist/theme/derive.d.ts +4 -0
  210. package/dist/theme/derive.js +2 -0
  211. package/dist/workspace/PanelContentView.js +1 -1
  212. package/dist/workspace/PanelHome.d.ts +3 -1
  213. package/dist/workspace/PanelHome.js +4 -3
  214. package/dist/workspace/SidePanel.d.ts +3 -1
  215. package/dist/workspace/SidePanel.js +3 -2
  216. package/dist/workspace/headless/open-target.d.ts +1 -1
  217. package/dist/workspace/headless/open-target.js +2 -2
  218. package/package.json +2 -2
  219. package/src/styles/chat.css +10 -10
  220. package/src/styles/motion.css +37 -0
  221. package/src/styles/primitives.css +11 -3
  222. package/src/styles/sidebar.css +12 -0
  223. package/src/styles/tokens.css +6 -0
  224. package/src/styles/transcript.css +69 -5
@@ -1,10 +1,10 @@
1
1
  import { BTN_DANGER, BTN_PRIMARY, BTN_SECONDARY } from "../../../components/button.js";
2
2
  import { ErrorNote, FormCard, FormSection, MetaChip, OpLine, RowText } from "../../../components/Form.js";
3
- import { constant, useReadable } from "../../../chat/headless/store.js";
4
3
  import { toSettingsError } from "../../headless/host.js";
5
4
  import { useSettingsHost } from "../../SettingsProvider.js";
6
5
  import { SettingsPanel, SettingsPanelBody } from "../../section.js";
7
6
  import { hasCredentialChoice } from "../../headless/providers.js";
7
+ import { SettingsRestricted } from "../../SettingsRestricted.js";
8
8
  import { useSettingsResource } from "../shared/catalog.js";
9
9
  import { CredentialSources } from "./CredentialSources.js";
10
10
  import { useAccountsSectionLabels } from "./ProvidersSection.js";
@@ -21,19 +21,16 @@ import { useMemo, useState } from "react";
21
21
  * is left alone. With more than one stored, they are its credential sources
22
22
  * (`CredentialSources`: the one in use picked, an OAuth account renamed).
23
23
  */
24
- const ALWAYS = constant(true);
25
24
  const YES = async () => true;
26
25
  /**
27
26
  * Renders the providers of the nearest `SettingsProvider`'s host (`providers`) as a server's cards: Set API
28
27
  * key needs `setApiKey`, Sign in `signIn`, Remove `remove`; a connection that may not write (`writable`)
29
- * shows none of them.
28
+ * sees the cards restricted ("Admin only").
30
29
  *
31
30
  * @param props - The `hostName`, an `intro`, `className` and `style`.
32
31
  */
33
32
  function ServerProvidersSection({ hostName, intro, className, style }) {
34
- const host = useSettingsHost();
35
- const providers = host.providers;
36
- const writable = useReadable(host.writable ?? ALWAYS);
33
+ const providers = useSettingsHost().providers;
37
34
  const l = useAccountsSectionLabels();
38
35
  const resource = useMemo(() => providers?.list("") ?? null, [providers]);
39
36
  const load = useSettingsResource(resource);
@@ -49,21 +46,20 @@ function ServerProvidersSection({ hostName, intro, className, style }) {
49
46
  kind: "busy",
50
47
  children: l.accountsServerReading(hostName)
51
48
  }),
52
- list && providers && /* @__PURE__ */ jsx(FormSection, {
49
+ list && providers && /* @__PURE__ */ jsx(SettingsRestricted, { children: /* @__PURE__ */ jsx(FormSection, {
53
50
  layout: "cards",
54
51
  children: list.map((provider) => /* @__PURE__ */ jsx(ProviderCard, {
55
52
  host: providers,
56
53
  hostName,
57
54
  provider,
58
- writable,
59
55
  labels: l
60
56
  }, provider.key))
61
- })
57
+ }) })
62
58
  ]
63
59
  })] })
64
60
  });
65
61
  }
66
- function ProviderCard({ host, hostName, provider, writable, labels: l }) {
62
+ function ProviderCard({ host, hostName, provider, labels: l }) {
67
63
  const [editing, setEditing] = useState(false);
68
64
  const [signingIn, setSigningIn] = useState(false);
69
65
  const [key, setKey] = useState("");
@@ -107,13 +103,13 @@ function ProviderCard({ host, hostName, provider, writable, labels: l }) {
107
103
  children: provider.methods.map((method) => /* @__PURE__ */ jsx(MetaChip, { children: method.detail }, `${method.source}:${method.key ?? method.detail}`))
108
104
  }) : void 0
109
105
  }),
110
- writable && signIn && provider.oauth && !signingIn && /* @__PURE__ */ jsx("button", {
106
+ signIn && provider.oauth && !signingIn && /* @__PURE__ */ jsx("button", {
111
107
  type: "button",
112
108
  className: BTN_SECONDARY,
113
109
  onClick: () => setSigningIn(true),
114
110
  children: l.accountsSignIn
115
111
  }),
116
- writable && setApiKey && !editing && /* @__PURE__ */ jsx("button", {
112
+ setApiKey && !editing && /* @__PURE__ */ jsx("button", {
117
113
  type: "button",
118
114
  className: BTN_SECONDARY,
119
115
  onClick: () => setEditing(true),
@@ -162,7 +158,7 @@ function ProviderCard({ host, hostName, provider, writable, labels: l }) {
162
158
  children: /* @__PURE__ */ jsx(CredentialSources, {
163
159
  provider,
164
160
  cwd: "",
165
- host: writable ? host : { list: host.list },
161
+ host,
166
162
  labels: l,
167
163
  confirm: YES,
168
164
  onError: setError
@@ -185,7 +181,7 @@ function ProviderCard({ host, hostName, provider, writable, labels: l }) {
185
181
  }),
186
182
  /* @__PURE__ */ jsx(MetaChip, { children: method.detail })
187
183
  ]
188
- }), writable && remove && /* @__PURE__ */ jsx("button", {
184
+ }), remove && /* @__PURE__ */ jsx("button", {
189
185
  type: "button",
190
186
  className: BTN_DANGER,
191
187
  disabled: busy,
@@ -5,6 +5,7 @@ import { Checkbox } from "../../../components/Form.js";
5
5
  import { toSettingsError } from "../../headless/host.js";
6
6
  import { useSettingsDocument, useSettingsHost, useSettingsWritable } from "../../SettingsProvider.js";
7
7
  import { useSectionLabels } from "../use-section-labels.js";
8
+ import { SettingsRestricted } from "../../SettingsRestricted.js";
8
9
  import { enabledNames, toggleEnabled } from "../../headless/enabled.js";
9
10
  import { DEFAULT_CATALOG_SHARED_LABELS } from "./labels.js";
10
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -130,7 +131,7 @@ function EnabledList({ catalog, keyOf, labelOf, detailOf, leadOf, badgeOf, statu
130
131
  return /* @__PURE__ */ jsxs("div", {
131
132
  className,
132
133
  style,
133
- children: [preamble, catalog.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { title: emptyMessage }) : /* @__PURE__ */ jsx("ul", {
134
+ children: [preamble, catalog.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { title: emptyMessage }) : /* @__PURE__ */ jsx(SettingsRestricted, { children: /* @__PURE__ */ jsx("ul", {
134
135
  className: "zg-tray rounded-zg-lg flex flex-col list-none m-0 p-1",
135
136
  children: catalog.map((entry) => {
136
137
  const key = keyOf(entry);
@@ -177,7 +178,7 @@ function EnabledList({ catalog, keyOf, labelOf, detailOf, leadOf, badgeOf, statu
177
178
  }) : null]
178
179
  }, key);
179
180
  })
180
- })]
181
+ }) })]
181
182
  });
182
183
  }
183
184
  //#endregion
@@ -6,6 +6,7 @@ import { Checkbox, MetaChip } from "../../../components/Form.js";
6
6
  import { LoadError } from "../../../components/LoadError.js";
7
7
  import { useSettingsHost } from "../../SettingsProvider.js";
8
8
  import { useSectionLabels } from "../use-section-labels.js";
9
+ import { SettingsRestricted } from "../../SettingsRestricted.js";
9
10
  import { OpenFileButton, RevealPathButton, useCatalogSharedLabels, useEnabledList, useSettingsResource } from "../shared/catalog.js";
10
11
  import { useCodeDocument } from "../../../code/editor/useCodeDocument.js";
11
12
  import { formatTokens } from "../../../session/headless/usage-format.js";
@@ -121,7 +122,7 @@ function SkillList({ cwd, catalog, actionOf, onSaved, emptyMessage }) {
121
122
  weights
122
123
  }),
123
124
  shown.length === 0 && /* @__PURE__ */ jsx(EmptyState, { title: inScope.length === 0 ? l.skillsSectionNoneInScope(filter) : l.skillsSectionNoMatch(query.trim()) }),
124
- /* @__PURE__ */ jsx("ul", {
125
+ /* @__PURE__ */ jsx(SettingsRestricted, { children: /* @__PURE__ */ jsx("ul", {
125
126
  className: "flex flex-col gap-2 list-none m-0 p-0",
126
127
  children: shown.map((entry) => /* @__PURE__ */ jsx(SkillCard, {
127
128
  cwd,
@@ -139,7 +140,7 @@ function SkillList({ cwd, catalog, actionOf, onSaved, emptyMessage }) {
139
140
  action: actionOf?.(entry),
140
141
  onSaved
141
142
  }, entry.name))
142
- })
143
+ }) })
143
144
  ]
144
145
  });
145
146
  }
@@ -16,6 +16,7 @@ const DEMO_PROVIDERS = [
16
16
  source: "oauth",
17
17
  key: "anthropic:oauth:work",
18
18
  label: "work",
19
+ email: "ada@example.com",
19
20
  detail: "in 12 days",
20
21
  active: true
21
22
  }, {
@@ -46,6 +46,7 @@ const PROVIDERS = [{
46
46
  source: "oauth",
47
47
  key: "anthropic:oauth:work",
48
48
  label: "work",
49
+ email: "work@example.com",
49
50
  detail: "expires in 12 days",
50
51
  active: true
51
52
  }, {
@@ -143,6 +144,7 @@ function describeSettingsAccountsContract(name, makeHarness, capabilities) {
143
144
  const anthropic = list.find((p) => p.key === "anthropic");
144
145
  expect(anthropic.available).toBe(true);
145
146
  expect(anthropic.methods.find((m) => m.active)?.key).toBe("anthropic:oauth:work");
147
+ expect(anthropic.methods.find((m) => m.active)?.email).toBe("work@example.com");
146
148
  expect(list.find((p) => p.key === "openai").available).toBe(false);
147
149
  });
148
150
  it.skipIf(!has("providers.setApiKey"))("setApiKey stores the key and the list shows it", async () => {
package/dist/styles.css CHANGED
@@ -303,7 +303,6 @@
303
303
  .sticky{position:sticky;}
304
304
  .static{position:static;}
305
305
  .-inset-\[2px\]{inset:-2px;}
306
- .-inset-\[3px\]{inset:-3px;}
307
306
  .-inset-px{inset:-1px;}
308
307
  .inset-0{inset:0;}
309
308
  .-inset-x-1{left:-0.25rem;right:-0.25rem;}
@@ -401,6 +400,11 @@
401
400
  .m\[2\]{margin:2;}
402
401
  .m\[2\]\!{margin:2 !important;}
403
402
  .m\[3\]{margin:3;}
403
+ .m\[4\]{margin:4;}
404
+ .m\[5\]{margin:5;}
405
+ .m\[6\]{margin:6;}
406
+ .m\[8\]{margin:8;}
407
+ .m\[9\]{margin:9;}
404
408
  .m23\.6{margin:5.9rem;}
405
409
  .m4\.7144{margin:1.1786rem;}
406
410
  .ma{margin:auto;}
@@ -412,6 +416,7 @@
412
416
  .-my-1{margin-top:-0.25rem;margin-bottom:-0.25rem;}
413
417
  .-my-px{margin-top:-1px;margin-bottom:-1px;}
414
418
  .mx{margin-left:1rem;margin-right:1rem;}
419
+ .mx-0\.5{margin-left:0.125rem;margin-right:0.125rem;}
415
420
  .mx-1{margin-left:0.25rem;margin-right:0.25rem;}
416
421
  .mx-1\.5{margin-left:0.375rem;margin-right:0.375rem;}
417
422
  .mx-2{margin-left:0.5rem;margin-right:0.5rem;}
@@ -474,7 +479,6 @@
474
479
  .mt-\[5px\]{margin-top:5px;}
475
480
  .mt-\[6px\]{margin-top:6px;}
476
481
  .mt-\[7px\]{margin-top:7px;}
477
- .mt-0{margin-top:0;}
478
482
  .mt-0\.5{margin-top:0.125rem;}
479
483
  .mt-1{margin-top:0.25rem;}
480
484
  .mt-1\.5{margin-top:0.375rem;}
@@ -504,6 +508,7 @@
504
508
  .\!w-40{width:10rem !important;}
505
509
  .\!w-64{width:16rem !important;}
506
510
  .\!w-auto{width:auto !important;}
511
+ .h-\[108px\]{height:108px;}
507
512
  .h-\[14px\]{height:14px;}
508
513
  .h-\[18px\]{height:18px;}
509
514
  .h-\[1em\]{height:1em;}
@@ -567,6 +572,7 @@
567
572
  .max-h-\[min\(70vh\,560px\)\]{max-height:min(70vh,560px);}
568
573
  .max-h-16{max-height:4rem;}
569
574
  .max-h-28{max-height:7rem;}
575
+ .max-h-40{max-height:10rem;}
570
576
  .max-h-60{max-height:15rem;}
571
577
  .max-h-64{max-height:16rem;}
572
578
  .max-h-72{max-height:18rem;}
@@ -812,7 +818,6 @@
812
818
  .gap-8{gap:2rem;}
813
819
  .gap-px{gap:1px;}
814
820
  .gap-x-1{column-gap:0.25rem;}
815
- .gap-x-1\.5{column-gap:0.375rem;}
816
821
  .gap-x-2{column-gap:0.5rem;}
817
822
  .gap-x-2\.5{column-gap:0.625rem;}
818
823
  .gap-x-3{column-gap:0.75rem;}
@@ -831,6 +836,9 @@
831
836
  .space-y-4>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1rem * var(--un-space-y-reverse));}
832
837
  .space-y-5>:not([hidden])~:not([hidden]){--un-space-y-reverse:0;margin-top:calc(1.25rem * calc(1 - var(--un-space-y-reverse)));margin-bottom:calc(1.25rem * var(--un-space-y-reverse));}
833
838
  .divide-y>:not([hidden])~:not([hidden]){--un-divide-y-reverse:0;border-top-width:calc(1px * calc(1 - var(--un-divide-y-reverse)));border-bottom-width:calc(1px * var(--un-divide-y-reverse));}
839
+ .border-\[var\(--zg-border\)\],
840
+ .border-zg-border,
841
+ .divide-\[var\(--zg-border\)\]>:not([hidden])~:not([hidden]){border-color:var(--zg-border);}
834
842
  .\[\&\+\&\]\:border-\[var\(--zg-hairline\)\]+.\[\&\+\&\]\:border-\[var\(--zg-hairline\)\],
835
843
  .border-\[var\(--zg-hairline\)\],
836
844
  .divide-\[var\(--zg-hairline\)\]>:not([hidden])~:not([hidden]){border-color:var(--zg-hairline);}
@@ -867,8 +875,6 @@
867
875
  .border-s-2{border-inline-start-width:2px;}
868
876
  .border-\[var\(--zg-accent\)\]{border-color:var(--zg-accent);}
869
877
  .border-\[var\(--zg-border-strong\)\]{border-color:var(--zg-border-strong);}
870
- .border-\[var\(--zg-border\)\],
871
- .border-zg-border{border-color:var(--zg-border);}
872
878
  .border-\[var\(--zg-text-muted\)\]{border-color:var(--zg-text-muted);}
873
879
  .border-transparent{border-color:transparent;}
874
880
  .border-zg-danger{border-color:var(--zg-danger);}
@@ -1027,6 +1033,7 @@
1027
1033
  .pb-2{padding-bottom:0.5rem;}
1028
1034
  .pb-2\.5{padding-bottom:0.625rem;}
1029
1035
  .pb-3{padding-bottom:0.75rem;}
1036
+ .pb-3\.5{padding-bottom:0.875rem;}
1030
1037
  .pb-4{padding-bottom:1rem;}
1031
1038
  .pb-6{padding-bottom:1.5rem;}
1032
1039
  .pb-8{padding-bottom:2rem;}
@@ -1134,10 +1141,12 @@
1134
1141
  .text-\[var\(--zg-accent\)\],
1135
1142
  .text-zg-accent{color:var(--zg-accent);}
1136
1143
  .text-\[var\(--zg-bg\)\]{color:var(--zg-bg);}
1144
+ .text-\[var\(--zg-choice-fg\,var\(--zg-text-muted\)\)\]{color:var(--zg-choice-fg,var(--zg-text-muted));}
1137
1145
  .text-\[var\(--zg-code-fg\)\]{color:var(--zg-code-fg);}
1138
1146
  .text-\[var\(--zg-danger\)\],
1139
1147
  .text-zg-danger{color:var(--zg-danger);}
1140
1148
  .text-\[var\(--zg-error\)\]{color:var(--zg-error);}
1149
+ .text-\[var\(--zg-sidebar-label-fg\,var\(--zg-text-muted\)\)\]{color:var(--zg-sidebar-label-fg,var(--zg-text-muted));}
1141
1150
  .text-\[var\(--zg-success\)\],
1142
1151
  .text-zg-success{color:var(--zg-success);}
1143
1152
  .text-\[var\(--zg-text-muted\)\],
@@ -1162,6 +1171,7 @@
1162
1171
  .enabled\:hover\:text-\[var\(--zg-text\)\]:hover:enabled{color:var(--zg-text);}
1163
1172
  .disabled\:hover\:text-\[var\(--zg-text-muted\)\]:hover:disabled{color:var(--zg-text-muted);}
1164
1173
  .disabled\:text-\[var\(--zg-text-muted\)\]:disabled{color:var(--zg-text-muted);}
1174
+ .placeholder\:text-\[var\(--zg-sidebar-label-fg\,var\(--zg-text-muted\)\)\]::placeholder{color:var(--zg-sidebar-label-fg,var(--zg-text-muted));}
1165
1175
  .placeholder\:text-\[var\(--zg-text-muted\)\]::placeholder{color:var(--zg-text-muted);}
1166
1176
  .placeholder\:text-\[var\(--zg-text-subtle\)\]::placeholder{color:var(--zg-text-subtle);}
1167
1177
  .text-inherit{color:inherit;}
@@ -1188,7 +1198,6 @@
1188
1198
  .tracking-\[0\.12em\]{letter-spacing:0.12em;}
1189
1199
  .tracking-\[inherit\]{letter-spacing:inherit;}
1190
1200
  .tracking-normal{letter-spacing:0em;}
1191
- .tracking-wide{letter-spacing:0.025em;}
1192
1201
  .font-\[inherit\]{font-family:inherit;}
1193
1202
  .font-mono{font-family:var(--zg-font-mono);}
1194
1203
  .font-sans{font-family:var(--zg-font-ui);}
@@ -1368,6 +1377,7 @@
1368
1377
  --zg-ring: #26a9f1;
1369
1378
 
1370
1379
  --zg-text-raw: #e5e8ec;
1380
+ --zg-text-strong-raw: #ffffff;
1371
1381
  --zg-text-muted-raw: #9499a0;
1372
1382
  --zg-text-subtle-raw: #5f636a;
1373
1383
 
@@ -1634,6 +1644,11 @@ html[data-motion='off'] {
1634
1644
  color-mix(in oklab, var(--zg-text-raw) var(--zg-contrast-base), var(--zg-bg)),
1635
1645
  var(--zg-contrast-target) var(--zg-contrast-boost)
1636
1646
  );
1647
+ --zg-text-strong: color-mix(
1648
+ in oklab,
1649
+ color-mix(in oklab, var(--zg-text-strong-raw, var(--zg-text-raw)) var(--zg-contrast-base), var(--zg-bg)),
1650
+ var(--zg-contrast-target) var(--zg-contrast-boost)
1651
+ );
1637
1652
  --zg-text-muted: color-mix(
1638
1653
  in oklab,
1639
1654
  color-mix(in oklab, var(--zg-text-muted-raw) var(--zg-contrast-base), var(--zg-bg)),
@@ -1775,6 +1790,16 @@ html[data-motion='off'] {
1775
1790
  scale: 0.985;
1776
1791
  }
1777
1792
 
1793
+ /* Restricted (`Restricted.tsx`): what a connection may read but not change,
1794
+ dimmed by tone and opacity together; its tooltip says why. */
1795
+ .zg-restricted {
1796
+ cursor: not-allowed;
1797
+ }
1798
+ .zg-restricted > [inert] {
1799
+ opacity: 0.55;
1800
+ filter: saturate(0.6);
1801
+ }
1802
+
1778
1803
  /* Tooltip (`Tooltip.tsx`): visibility, the fade + 2px rise and the show
1779
1804
  delay are driven inline by the component (one timing model for inline and
1780
1805
  portalled bubbles). This is the hidden state before it renders. */
@@ -2005,12 +2030,10 @@ html[data-motion='off'] {
2005
2030
  background: var(--zg-row-hover);
2006
2031
  }
2007
2032
 
2008
- /* The one uppercase section label. */
2033
+ /* The section label: sentence case, as written. */
2009
2034
  .zg-section-label {
2010
2035
  font-size: var(--zg-font-size-badge);
2011
2036
  font-weight: 600;
2012
- letter-spacing: 0.08em;
2013
- text-transform: uppercase;
2014
2037
  /* A real label, read like any text: the muted tone (subtle is for decoration). */
2015
2038
  color: var(--zg-text-muted);
2016
2039
  }
@@ -2414,6 +2437,16 @@ html[data-motion='reduced'] .zg-shimmer {
2414
2437
  white-space: nowrap;
2415
2438
  }
2416
2439
 
2440
+ /* SmoothHeight: eases between two contents' heights while `data-easing`, then lets go. */
2441
+ .zg-smooth-height[data-easing] {
2442
+ overflow: hidden;
2443
+ transition: height var(--zg-motion-settle);
2444
+ }
2445
+ /* The content's own margins stay inside what is measured. */
2446
+ .zg-smooth-height-inner {
2447
+ display: flow-root;
2448
+ }
2449
+
2417
2450
  /* HeldWidth: a trailing slot held to its widest content; content sits on
2418
2451
  the right edge and is revealed from the left as the slot grows. */
2419
2452
  .zg-held-width {
@@ -2836,6 +2869,33 @@ html[data-motion='reduced'] :is(.animate-spin, .zg-spin-ring, .zg-sendstop-ring)
2836
2869
  `running`, Timeline `tool-running`). Applied to an absolutely positioned
2837
2870
  ring sibling, never to the dot itself. */
2838
2871
 
2872
+ /* The zidane mark spinning in and out (`ZidaneMark`): a quarter turn the way
2873
+ its bands flow, from `--zg-mark-from` of its size (the dot it grows out
2874
+ of in `StatusDot`). `.zg-mark-in` plays on mount; `.zg-mark-swap` on a
2875
+ `Presence` plays both ways. Turn and scale ride `snap` in and `exit`
2876
+ out, opacity `fade` / `fade-out`. */
2877
+ .zg-mark-in,
2878
+ .zg-presence.zg-mark-swap[data-state='enter'] {
2879
+ animation:
2880
+ zg-mark-in var(--zg-motion-snap) both,
2881
+ zg-fade-in var(--zg-motion-fade) both;
2882
+ }
2883
+ .zg-presence.zg-mark-swap[data-state='exit'] {
2884
+ animation:
2885
+ zg-mark-out var(--zg-motion-exit) both,
2886
+ zg-fade-out var(--zg-motion-fade-out) both;
2887
+ }
2888
+ @keyframes zg-mark-in {
2889
+ from {
2890
+ transform: scale(var(--zg-mark-from, 0.6)) rotate(-90deg);
2891
+ }
2892
+ }
2893
+ @keyframes zg-mark-out {
2894
+ to {
2895
+ transform: scale(var(--zg-mark-from, 0.6)) rotate(45deg);
2896
+ }
2897
+ }
2898
+
2839
2899
  @keyframes zg-orbit {
2840
2900
  to {
2841
2901
  transform: rotate(360deg);
@@ -3682,12 +3742,10 @@ html[data-motion='reduced'] .zg-seg > span:nth-child(-n + 3) {
3682
3742
  contain-intrinsic-size: auto 28px;
3683
3743
  }
3684
3744
 
3745
+ /* Active: a flat wash a step above hover, no raised chip (it read as a button). Hover keeps it. */
3685
3746
  .zg-session-row[data-active='true']::before,
3686
- .zg-session-row[data-tentative-active='true']::before,
3687
- .zg-session-row[data-active='true']:hover::before,
3688
- .zg-session-row[data-tentative-active='true']:hover::before {
3689
- background: var(--zg-raised);
3690
- box-shadow: var(--zg-shadow-raised);
3747
+ .zg-session-row[data-tentative-active='true']::before {
3748
+ background: var(--zg-row-active);
3691
3749
  }
3692
3750
 
3693
3751
  .zg-session-row:hover:not([data-active='true']):not([data-tentative-active='true'])::before {
@@ -3702,11 +3760,6 @@ html[data-motion='reduced'] .zg-seg > span:nth-child(-n + 3) {
3702
3760
  box-shadow: var(--zg-shadow-well);
3703
3761
  }
3704
3762
 
3705
- .zg-session-row[data-active='true'] .zg-session-title,
3706
- .zg-session-row[data-tentative-active='true'] .zg-session-title {
3707
- font-weight: 500;
3708
- }
3709
-
3710
3763
  /* The title's color; its fade lives on the clip around it (`.zg-title-clip`). */
3711
3764
  .zg-session-title {
3712
3765
  color: var(--zg-text-muted);
@@ -4046,6 +4099,13 @@ html[data-motion='reduced'] .zg-orb-ripple {
4046
4099
  transform-origin: var(--radix-dropdown-menu-content-transform-origin);
4047
4100
  }
4048
4101
 
4102
+ /* Dark appearance: primary text at the theme's strongest, as in the sidebar. */
4103
+ :root.dark .zg-inbox {
4104
+ --zg-text: var(--zg-text-strong);
4105
+ /* A question's unpicked choices (QuestionField), muted elsewhere. */
4106
+ --zg-choice-fg: var(--zg-text-strong);
4107
+ }
4108
+
4049
4109
  /* Inbox card resolved: lifts away on `exit` and fades on `fade-out`, then
4050
4110
  unmounts (InboxPanel waits `readExitMs`). */
4051
4111
  .zg-inbox-card[data-state='leaving'] {
@@ -4088,6 +4148,18 @@ html[data-motion='reduced'] .zg-orb-ripple {
4088
4148
  width: 0;
4089
4149
  }
4090
4150
 
4151
+ /* Dark appearance: primary text at the theme's strongest (pure white unless the theme names its text); muted and
4152
+ subtle keep the theme's gray. */
4153
+ :root.dark .zg-sidebar {
4154
+ --zg-text: var(--zg-text-strong);
4155
+ /* Project names, the New session label, the search placeholder; muted elsewhere. */
4156
+ --zg-sidebar-label-fg: var(--zg-text-strong);
4157
+ }
4158
+ /* Every session title, not only the active one (`.zg-session-title` is muted in chat.css). */
4159
+ :root.dark .zg-sidebar .zg-session-title {
4160
+ color: var(--zg-text);
4161
+ }
4162
+
4091
4163
  /* Content sheet: full panel width, pinned to the aside's right edge so it
4092
4164
  travels with it. A light opacity fade rides along (no blur, no
4093
4165
  re-layout: nothing inside changes position relative to the sheet). */
@@ -4895,7 +4967,8 @@ a.zg-badge:hover,
4895
4967
  script has not reached are dim; the running one carries the accent and the
4896
4968
  running sweep; a call that ends washes its line once; a failed line is red,
4897
4969
  the script's own error under it. A running script past 15 lines scrolls to
4898
- follow its running line. Motion stops under reduced / off. */
4970
+ follow its running line, and keeps that window once it ends. Motion stops
4971
+ under reduced / off. */
4899
4972
  /* The frame's hairline, drawn again over the lines: the sticky gutter and the
4900
4973
  tinted lines are opaque, and covered it where they meet the frame's edge. */
4901
4974
  .zg-script-frame::after {
@@ -4921,13 +4994,15 @@ a.zg-badge:hover,
4921
4994
  .zg-code-scroll[data-wrap] .zg-script-lines {
4922
4995
  width: auto;
4923
4996
  }
4924
- .zg-script-lines[data-follow] {
4925
- max-height: calc(15 * 1.5em + 1rem);
4997
+ /* A long script's window: the frame's own scroller takes it, so one element
4998
+ scrolls both ways and the sticky gutter stays put sideways too. */
4999
+ .zg-code-scroll:has(> .zg-script-lines[data-window]) {
5000
+ max-height: calc(15 * 1.5 * var(--zg-font-size-code) + 1rem);
4926
5001
  overflow-y: auto;
4927
5002
  scroll-behavior: smooth;
4928
5003
  }
4929
- html[data-motion='off'] .zg-script-lines,
4930
- html[data-motion='reduced'] .zg-script-lines {
5004
+ html[data-motion='off'] .zg-code-scroll,
5005
+ html[data-motion='reduced'] .zg-code-scroll {
4931
5006
  scroll-behavior: auto;
4932
5007
  }
4933
5008
  .zg-script-line {
@@ -4978,6 +5053,61 @@ html[data-motion='reduced'] .zg-script-lines {
4978
5053
  .zg-script-line[data-state='idle'] .zg-script-number {
4979
5054
  opacity: 0.4;
4980
5055
  }
5056
+ /* In a loop that went round again: ran in the round before, not yet in this one. */
5057
+ .zg-script-line[data-state='previous'] .zg-script-code {
5058
+ opacity: 0.6;
5059
+ }
5060
+ .zg-script-line[data-state='previous'] .zg-script-mark {
5061
+ opacity: 0.35;
5062
+ }
5063
+ /* A `sleep`: the running tint, softer, and its mark breathing. */
5064
+ .zg-script-line[data-state='waiting'] {
5065
+ --zg-script-tint: color-mix(in srgb, var(--zg-accent-wash) 40%, transparent);
5066
+ --zg-script-bar: color-mix(in srgb, var(--zg-accent) 60%, transparent);
5067
+ }
5068
+ .zg-script-line[data-state='waiting'] .zg-script-gutter {
5069
+ color: var(--zg-accent);
5070
+ }
5071
+ .zg-script-line[data-state='waiting'] .zg-script-mark {
5072
+ animation: zg-script-breathe 2s ease-in-out infinite;
5073
+ }
5074
+ html[data-motion='off'] .zg-script-line .zg-script-mark,
5075
+ html[data-motion='reduced'] .zg-script-line .zg-script-mark {
5076
+ animation: none;
5077
+ }
5078
+
5079
+ /* Loops: the first line's mark, and while the script is in a loop, its line
5080
+ numbers in the running color. */
5081
+ .zg-script-loop {
5082
+ color: var(--zg-text-subtle);
5083
+ transition: color var(--zg-motion-fade);
5084
+ }
5085
+ .zg-script-loop[data-active] {
5086
+ color: var(--zg-accent);
5087
+ }
5088
+ .zg-script-line[data-in-loop] .zg-script-number {
5089
+ color: color-mix(in srgb, var(--zg-accent) 75%, var(--zg-text-subtle));
5090
+ }
5091
+
5092
+ /* After a line's code: a loop's round, a line's call count, a wait's time left. */
5093
+ .zg-script-note {
5094
+ display: inline-flex;
5095
+ align-items: center;
5096
+ gap: 0.25rem;
5097
+ margin-inline-start: 0.625ch;
5098
+ border-radius: var(--zg-radius-xs);
5099
+ font-family: var(--zg-font-ui);
5100
+ font-size: 0.85em;
5101
+ color: var(--zg-text-subtle);
5102
+ font-variant-numeric: tabular-nums;
5103
+ vertical-align: 0.05em;
5104
+ transition: color var(--zg-motion-fade);
5105
+ }
5106
+ .zg-script-note[data-active] {
5107
+ padding-inline: 0.375rem;
5108
+ color: var(--zg-accent);
5109
+ background: color-mix(in srgb, var(--zg-accent-wash) 60%, transparent);
5110
+ }
4981
5111
  /* A line's tint and bar come from its state. The gutter stays opaque (code
4982
5112
  scrolled sideways passes under it): the tint goes on a layer over its own
4983
5113
  background, and the bar sits on it, so both stay in place while scrolling. */
@@ -5032,6 +5162,12 @@ html[data-motion='reduced'] .zg-script-line::after {
5032
5162
  animation: none;
5033
5163
  }
5034
5164
 
5165
+ @keyframes zg-script-breathe {
5166
+ 50% {
5167
+ opacity: 0.4;
5168
+ }
5169
+ }
5170
+
5035
5171
  @keyframes zg-script-settle {
5036
5172
  from {
5037
5173
  background-color: color-mix(in srgb, var(--zg-success-soft) 80%, transparent);
@@ -6,9 +6,9 @@ import { matchesBinding } from "../keybindings/match.js";
6
6
  import { useKeybindingsHost } from "../keybindings/KeybindingsProvider.js";
7
7
  import { useSymbolsFontUrl, useTerminalActions, useTerminalStore } from "./TerminalProvider.js";
8
8
  import { useLinkRouter } from "../extensions/links.js";
9
+ import { detectLinks, linkTarget, resolveTerminalPath, serverOnlyLink } from "./headless/links.js";
9
10
  import { loadGhostty, openTerminal } from "./ghostty.js";
10
11
  import { terminalFont } from "./grid-size.js";
11
- import { detectLinks, linkTarget, resolveTerminalPath, serverOnlyLink } from "./headless/links.js";
12
12
  import { planChunk, planReset, shouldAck } from "./headless/output-cursor.js";
13
13
  import { feedSlices, yieldToEventLoop } from "./headless/replay.js";
14
14
  import { macTerminalKey } from "./keys.js";
@@ -14,6 +14,24 @@ type TerminalLink = {
14
14
  line?: number;
15
15
  column?: number;
16
16
  };
17
+ /** Where in a file a path points: 1-based. */
18
+ interface PathPosition {
19
+ /** The line. */
20
+ line: number;
21
+ /** The column. */
22
+ column?: number;
23
+ /** The last line of a range. */
24
+ endLine?: number;
25
+ }
26
+ /**
27
+ * A path written with a position at its end, split into both, as the terminal's links read one (plus a range's
28
+ * end and GitHub's `#L12`). `null` when the text ends in no position, or in line 0.
29
+ *
30
+ * @param text - The text, trimmed.
31
+ */
32
+ declare function splitPathPosition(text: string): ({
33
+ path: string;
34
+ } & PathPosition) | null;
17
35
  /**
18
36
  * Links on one line. URLs with a scheme are only used to keep paths out of
19
37
  * them (`https://x.dev/a/b.ts` is not a path): the library links those
@@ -54,4 +72,4 @@ declare function resolveTerminalPath(path: string, cwd: string, home?: string):
54
72
  */
55
73
  declare function serverOnlyLink(url: string, serverName: string | null, describe: (serverName: string) => string): string | null;
56
74
  //#endregion
57
- export { LinkTarget, TerminalLink, detectLinks, linkTarget, resolveTerminalPath, serverOnlyLink };
75
+ export { LinkTarget, PathPosition, TerminalLink, detectLinks, linkTarget, resolveTerminalPath, serverOnlyLink, splitPathPosition };
@@ -13,7 +13,31 @@ function basename(path) {
13
13
  }
14
14
  const URL_RE = /\bhttps?:\/\/[^\s"'<>`]+/g;
15
15
  const LOCAL_RE = /(?<![\w./:@-])(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]):\d{2,5}(?:\/[^\s"'<>`]*)?/g;
16
- const PATH_RE = /(?<![\w./@+~-])((?:\.{1,2}\/|~\/|\/)?(?:[\w.@+-]+\/)*[\w.@+-]+)(?::(\d+)(?::(\d+))?|\((\d+),(\d+)\))?/g;
16
+ /** A position after a path: `:12`, `:12:5` or `(12,5)` (tsc). Groups: line, column, tsc line, tsc column. */
17
+ const POSITION = String.raw`(?::(\d+)(?::(\d+))?|\((\d+),(\d+)\))`;
18
+ const PATH_RE = new RegExp(String.raw`(?<![\w./@+~-])((?:\.{1,2}\/|~\/|\/)?(?:[\w.@+-]+\/)*[\w.@+-]+)${POSITION}?`, "g");
19
+ /** A whole text ending in a position: the terminal's, a range's end (`:12-20`), or GitHub's `#L12`, `#L12C5`, `#L12-L20`. */
20
+ const ENDS_IN_POSITION = new RegExp(String.raw`^(.+?)(?:${POSITION}(?:-(\d+))?|#L(\d+)(?:C(\d+))?(?:-L?(\d+))?)$`);
21
+ /**
22
+ * A path written with a position at its end, split into both, as the terminal's links read one (plus a range's
23
+ * end and GitHub's `#L12`). `null` when the text ends in no position, or in line 0.
24
+ *
25
+ * @param text - The text, trimmed.
26
+ */
27
+ function splitPathPosition(text) {
28
+ const m = ENDS_IN_POSITION.exec(text);
29
+ if (!m) return null;
30
+ const line = Number(m[2] ?? m[4] ?? m[7]);
31
+ if (!(line >= 1)) return null;
32
+ const column = m[3] ?? m[5] ?? m[8];
33
+ const endLine = m[6] ?? m[9];
34
+ return {
35
+ path: m[1],
36
+ line,
37
+ ...column ? { column: Number(column) } : {},
38
+ ...endLine && Number(endLine) > line ? { endLine: Number(endLine) } : {}
39
+ };
40
+ }
17
41
  /** Extensions that make a bare name (no slash, no position) read as a file. */
18
42
  const FILE_EXTENSIONS = new Set("ts tsx js jsx mjs cjs mts cts json jsonc md mdx py rs go rb java kt kts swift c h cc cpp hpp cs css scss sass less html htm vue svelte astro yml yaml toml lock sh bash zsh fish txt sql graphql gql proto xml ini env conf cfg php ex exs erl hs ml zig nim lua dart scala clj r jl tf".split(" "));
19
43
  /** Trailing characters that end a sentence rather than an address. */
@@ -197,4 +221,4 @@ function serverOnlyLink(url, serverName, describe) {
197
221
  return LOOPBACK_HOSTS.has(host) || host.endsWith(".localhost") ? describe(serverName) : null;
198
222
  }
199
223
  //#endregion
200
- export { detectLinks, linkTarget, resolveTerminalPath, serverOnlyLink };
224
+ export { detectLinks, linkTarget, resolveTerminalPath, serverOnlyLink, splitPathPosition };
@@ -18,6 +18,7 @@ function paletteToCssVars(p) {
18
18
  "--zg-border-strong": p.borderStrong,
19
19
  "--zg-ring": p.ring,
20
20
  "--zg-text-raw": p.text,
21
+ "--zg-text-strong-raw": p.textStrong,
21
22
  "--zg-text-muted-raw": p.textMuted,
22
23
  "--zg-text-subtle-raw": p.textSubtle,
23
24
  "--zg-accent": p.accent,
@@ -14,6 +14,8 @@ interface ThemeSeed {
14
14
  appearance?: Appearance;
15
15
  /** Body text. Derived from `canvas` with a faint tint when omitted. */
16
16
  text?: string;
17
+ /** Primary text where it reads strongest (the sidebar and the inbox in dark). Defaults to white in dark, `text` in light. */
18
+ textStrong?: string;
17
19
  /** Secondary text. Pushed until it clears its contrast floor. */
18
20
  textMuted?: string;
19
21
  /** Tertiary text, such as hints. Pushed until it clears its contrast floor. */
@@ -189,6 +191,8 @@ interface ResolvedPalette {
189
191
  ring: string;
190
192
  /** Body text. */
191
193
  text: string;
194
+ /** Primary text where it reads strongest (the sidebar and the inbox in dark). */
195
+ textStrong: string;
192
196
  /** Secondary text. */
193
197
  textMuted: string;
194
198
  /** Tertiary text, such as hints. */
@@ -184,6 +184,7 @@ function derivePalette(seed) {
184
184
  const hoverWash = dark ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.04)";
185
185
  const textBase = seed.text ?? tintedNeutral(dark ? .93 : .24, tintSource, tint * .5);
186
186
  const text = ensureContrast(textBase, surface2, CONTRAST.text);
187
+ const textStrong = ensureContrast(seed.textStrong ?? (dark ? "#ffffff" : text), surface2, CONTRAST.text);
187
188
  const textMuted = ensureContrast(seed.textMuted ?? mix(text, bg, .42), surface2, CONTRAST.textMuted);
188
189
  const textSubtle = ensureContrast(seed.textSubtle ?? mix(text, bg, .62), bg, CONTRAST.textSubtle);
189
190
  const accent = ensureContrast(seed.accent, bg, 3);
@@ -256,6 +257,7 @@ function derivePalette(seed) {
256
257
  text,
257
258
  textMuted,
258
259
  textSubtle,
260
+ textStrong,
259
261
  accentFg,
260
262
  accentFill,
261
263
  accentFillHover,