pinokiod 7.1.73 → 7.1.74

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.
@@ -13,13 +13,13 @@ class Script {
13
13
  return null
14
14
  }
15
15
  currentSessionId(req) {
16
- if (req && typeof req.id === "string" && req.id.trim()) {
17
- return req.id
18
- }
19
16
  const parent = this.currentParent(req)
20
17
  if (parent && typeof parent.id === "string" && parent.id.trim()) {
21
18
  return parent.id
22
19
  }
20
+ if (req && typeof req.id === "string" && req.id.trim()) {
21
+ return req.id
22
+ }
23
23
  return undefined
24
24
  }
25
25
  currentCaller(req) {
@@ -192,14 +192,16 @@ class Script {
192
192
  input: req.params.params,
193
193
  client: this.currentClient(req),
194
194
  }
195
- if (req.id) {
196
- request.id = req.id
197
- }
198
195
  const caller = this.currentCaller(req)
199
196
  if (caller) {
200
197
  request.caller = caller
201
- } else if (req.parent && req.parent.path) {
202
- request.caller = req.parent.path
198
+ } else {
199
+ const parent = this.currentParent(req)
200
+ if (parent && typeof parent.id === "string" && parent.id.trim()) {
201
+ request.caller = parent.id
202
+ } else if (req.parent && req.parent.path) {
203
+ request.caller = req.parent.path
204
+ }
203
205
  }
204
206
  const origin = this.currentOrigin(req)
205
207
  if (origin) {
@@ -2,15 +2,35 @@ const path = require('path')
2
2
  const fs = require('fs')
3
3
  const os = require('os')
4
4
  class Shell {
5
- applyBluefairyDefault(req = {}) {
6
- if (!req.params || Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
5
+ async applyBluefairyDefault(req = {}, kernel) {
6
+ if (!req.params) {
7
+ return
8
+ }
9
+ if (Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
10
+ return
11
+ }
12
+ const preferences = kernel && kernel.appPreferences
13
+ if (
14
+ !preferences
15
+ || !kernel
16
+ || !kernel.api
17
+ || typeof kernel.api.resolvePath !== "function"
18
+ || typeof preferences.resolveAppIdFromPath !== "function"
19
+ || typeof preferences.getPreference !== "function"
20
+ ) {
21
+ req.params.bluefairy = "off"
7
22
  return
8
23
  }
9
- if (req.parent && Object.prototype.hasOwnProperty.call(req.parent, "protection_enabled")) {
10
- req.params.bluefairy = req.parent.protection_enabled === false ? "off" : "on"
11
- } else {
24
+ const cwd = req.cwd || kernel.homedir
25
+ const requestedPath = req.params.path || "."
26
+ const resolvedPath = kernel.api.resolvePath(cwd, requestedPath)
27
+ const appId = preferences.resolveAppIdFromPath(resolvedPath)
28
+ if (!appId) {
12
29
  req.params.bluefairy = "off"
30
+ return
13
31
  }
32
+ const preference = await preferences.getPreference(appId)
33
+ req.params.bluefairy = preference && preference.protection_enabled === true ? "on" : "off"
14
34
  }
15
35
  async start(req, ondata, kernel) {
16
36
  /*
@@ -50,7 +70,9 @@ class Shell {
50
70
  if (req.params) {
51
71
  req.params.$parent = req.parent
52
72
  }
53
- this.applyBluefairyDefault(req)
73
+ if (!Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
74
+ req.params.bluefairy = "off"
75
+ }
54
76
 
55
77
  // // create a persistent session
56
78
  // req.params.persistent = true
@@ -136,10 +158,13 @@ class Shell {
136
158
  if (!req.params) {
137
159
  req.params = {}
138
160
  }
161
+ if (!req.params.path) {
162
+ req.params.path = req.cwd
163
+ }
139
164
  if (req.params) {
140
165
  req.params.$parent = req.parent
141
166
  }
142
- this.applyBluefairyDefault(req)
167
+ await this.applyBluefairyDefault(req, kernel)
143
168
  let options = {}
144
169
  if (req.cwd) options.cwd = req.cwd
145
170
  if (req.parent && req.parent.id) {
@@ -4,7 +4,7 @@ const semver = require('semver')
4
4
 
5
5
  class Bluefairy {
6
6
  description = "Installs Bluefairy, a standalone package freshness guard."
7
- version = ">=0.0.28"
7
+ version = ">=0.0.31"
8
8
 
9
9
  packageName() {
10
10
  return "bluefairy"
@@ -37,6 +37,10 @@ class Bluefairy {
37
37
  return this.kernel.bin.path("bluefairy")
38
38
  }
39
39
 
40
+ packageInstallPath() {
41
+ return path.resolve(this.moduleRoot(), this.packageName())
42
+ }
43
+
40
44
  env() {
41
45
  return {
42
46
  BLUEFAIRY_HOME: this.runtimeHome()
@@ -63,6 +67,20 @@ class Bluefairy {
63
67
  ]
64
68
  }
65
69
 
70
+ binLauncherArtifacts() {
71
+ const binDir = this.npmBinDir()
72
+ if (this.kernel.platform === "win32") {
73
+ return [
74
+ path.resolve(binDir, "bluefairy"),
75
+ path.resolve(binDir, "bluefairy.cmd"),
76
+ path.resolve(binDir, "bluefairy.ps1"),
77
+ ]
78
+ }
79
+ return [
80
+ path.resolve(binDir, "bluefairy"),
81
+ ]
82
+ }
83
+
66
84
  runtimeArtifacts() {
67
85
  const runtimeHome = this.runtimeHome()
68
86
  if (this.kernel.platform === "win32") {
@@ -97,18 +115,61 @@ class Bluefairy {
97
115
  }
98
116
  }
99
117
 
100
- async install(req, ondata) {
101
- const spec = this.packageSpec().replaceAll('"', '\\"')
102
- const runtimeHome = this.runtimeHome()
103
- if (fs.existsSync(runtimeHome)) {
104
- try {
105
- await fs.promises.rm(runtimeHome, { recursive: true, force: true })
106
- ondata({ raw: `Removed existing Bluefairy runtime: ${runtimeHome}\r\n` })
107
- } catch (error) {
108
- const message = error && error.message ? error.message : String(error)
109
- ondata({ raw: `Warning: failed to remove Bluefairy runtime ${runtimeHome}: ${message}\r\n` })
118
+ async removePath(target, ondata, label) {
119
+ if (!fs.existsSync(target)) {
120
+ return
121
+ }
122
+ try {
123
+ await fs.promises.rm(target, { recursive: true, force: true })
124
+ ondata({ raw: `Removed ${label}: ${target}\r\n` })
125
+ } catch (error) {
126
+ const message = error && error.message ? error.message : String(error)
127
+ ondata({ raw: `Warning: failed to remove ${label} ${target}: ${message}\r\n` })
128
+ }
129
+ }
130
+
131
+ async removeRetiredEntries(parentDir, ondata) {
132
+ let entries
133
+ try {
134
+ entries = await fs.promises.readdir(parentDir)
135
+ } catch (error) {
136
+ const message = error && error.message ? error.message : String(error)
137
+ ondata({ raw: `Warning: failed to list ${parentDir}: ${message}\r\n` })
138
+ return
139
+ }
140
+
141
+ const retiredPrefix = `.${this.packageName()}-`
142
+ for (const entry of entries) {
143
+ if (!entry.startsWith(retiredPrefix)) {
144
+ continue
110
145
  }
146
+ await this.removePath(
147
+ path.resolve(parentDir, entry),
148
+ ondata,
149
+ "stale Bluefairy install temp"
150
+ )
151
+ }
152
+ }
153
+
154
+ async cleanupInstallState(ondata) {
155
+ const cleanupTargets = new Set([
156
+ this.runtimeHome(),
157
+ this.packageInstallPath(),
158
+ ...this.installedArtifacts(),
159
+ ...this.binLauncherArtifacts(),
160
+ ])
161
+
162
+ for (const target of cleanupTargets) {
163
+ await this.removePath(target, ondata, "existing Bluefairy install state")
111
164
  }
165
+
166
+ await this.removeRetiredEntries(this.moduleRoot(), ondata)
167
+ await this.removeRetiredEntries(this.npmBinDir(), ondata)
168
+ }
169
+
170
+ async install(req, ondata) {
171
+ const spec = this.packageSpec().replaceAll('"', '\\"')
172
+ await this.cleanupInstallState(ondata)
112
173
  await this.kernel.exec({
113
174
  env: this.env(),
114
175
  message: `npm install -g "${spec}" --force`,
package/kernel/shell.js CHANGED
@@ -21,6 +21,51 @@ const AnsiStreamTracker = require('./ansi_stream_tracker')
21
21
  const ShellStateSync = require('./shell_state_sync')
22
22
  const home = os.homedir()
23
23
 
24
+ function normalizeComparablePath(filePath, platform) {
25
+ const normalized = path.normalize(filePath)
26
+ return platform === 'win32' ? normalized.toLowerCase() : normalized
27
+ }
28
+
29
+ function isBluefairyShimPath(filePath, platform) {
30
+ if (!filePath || typeof filePath !== "string") {
31
+ return false
32
+ }
33
+ const normalized = normalizeComparablePath(filePath.trim(), platform)
34
+ if (!normalized) {
35
+ return false
36
+ }
37
+ const shimDirName = path.basename(normalized)
38
+ if (shimDirName !== "shims") {
39
+ return false
40
+ }
41
+ const parentDirName = path.basename(path.dirname(normalized))
42
+ return parentDirName === "bluefairy" || parentDirName === ".bluefairy"
43
+ }
44
+
45
+ function stripBluefairyShimPaths(pathValue, platform) {
46
+ if (!pathValue || typeof pathValue !== "string") {
47
+ return pathValue
48
+ }
49
+ return pathValue
50
+ .split(path.delimiter)
51
+ .filter((entry) => entry && !isBluefairyShimPath(entry, platform))
52
+ .join(path.delimiter)
53
+ }
54
+
55
+ const CONDA_ACTIVATION_STATE_PATTERN = /^(?:_CE_(?:M|CONDA)|CONDA_(?:EXE|PYTHON_EXE|PREFIX(?:_\d+)?|DEFAULT_ENV|PROMPT_MODIFIER|SHLVL|PS1_BACKUP))$/
56
+
57
+ function isCondaActivationStateKey(key) {
58
+ return typeof key === "string" && CONDA_ACTIVATION_STATE_PATTERN.test(key)
59
+ }
60
+
61
+ function stripInheritedCondaActivationState(env) {
62
+ for (const key of Object.keys(env)) {
63
+ if (isCondaActivationStateKey(key)) {
64
+ delete env[key]
65
+ }
66
+ }
67
+ }
68
+
24
69
  // xterm.js currently ignores DECSYNCTERM (CSI ? 2026 h/l) and renders it as text on Windows.
25
70
  // filterDecsync() removes these sequences so they do not pollute the terminal output.
26
71
  class Shell {
@@ -91,6 +136,12 @@ class Shell {
91
136
  }
92
137
  async init_env(params) {
93
138
  this.env = Object.assign({}, process.env)
139
+ for (const key of Object.keys(this.env)) {
140
+ if (key.toUpperCase().startsWith("BLUEFAIRY_")) {
141
+ delete this.env[key]
142
+ }
143
+ }
144
+ stripInheritedCondaActivationState(this.env)
94
145
  // If the user has set PYTHONPATH, unset it.
95
146
  if (this.env.PYTHONPATH) {
96
147
  delete this.env.PYTHONPATH
@@ -166,6 +217,7 @@ class Shell {
166
217
  let app_env = await Environment.get(api_path, this.kernel)
167
218
  this.env = Object.assign(this.env, app_env)
168
219
  }
220
+ stripInheritedCondaActivationState(this.env)
169
221
  let PATH_KEY = Object.keys(this.env).find((key) => key.toLowerCase() === "path") || "PATH";
170
222
  if (!this.env[PATH_KEY]) {
171
223
  // fall back to whichever casing exists so we don't end up writing to an undefined key
@@ -232,6 +284,9 @@ class Shell {
232
284
  }
233
285
  }
234
286
 
287
+ stripInheritedCondaActivationState(this.env)
288
+ this.env[PATH_KEY] = stripBluefairyShimPaths(this.env[PATH_KEY], this.platform)
289
+
235
290
  for(let key in this.env) {
236
291
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) && key !== "ProgramFiles(x86)") {
237
292
  delete this.env[key]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.1.73",
3
+ "version": "7.1.74",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -213,6 +213,7 @@ class Server {
213
213
  })
214
214
  this.appRegistry = new AppRegistryService({ kernel: this.kernel })
215
215
  this.appPreferences = new AppPreferencesService({ kernel: this.kernel })
216
+ this.kernel.appPreferences = this.appPreferences
216
217
  this.appLogs = new AppLogService({ registry: this.appRegistry })
217
218
  this.appSearch = new AppSearchService({
218
219
  kernel: this.kernel,
@@ -1251,7 +1252,7 @@ class Server {
1251
1252
  path: 'remote.origin.url'
1252
1253
  })
1253
1254
  if (gitRemote && this.portal) {
1254
- community_url = `${this.portal}/resolve?url=${encodeURIComponent(gitRemote)}&embed=1&theme=${encodeURIComponent(this.theme)}`
1255
+ community_url = `${this.portal}/resolve?url=${encodeURIComponent(gitRemote)}&embed=1&theme=${encodeURIComponent(this.theme)}&pinokio_checkin_bridge=v1`
1255
1256
  }
1256
1257
  } catch (_) {
1257
1258
  community_url = ""
@@ -6417,8 +6418,6 @@ class Server {
6417
6418
  } else {
6418
6419
  registryOverride = null
6419
6420
  }
6420
- console.log("[registry/checkin] repo=%s return=%s registryRaw=%s registryOverride=%s", repoUrl || "", returnUrl || "", registryRaw || "", registryOverride || "")
6421
-
6422
6421
  let candidates = []
6423
6422
  if (repoUrl && this.kernel && this.kernel.git) {
6424
6423
  const apiRoot = this.kernel.path('api')
@@ -6538,7 +6537,6 @@ class Server {
6538
6537
  if (wantPublish) {
6539
6538
  const registry = await this.getRegistryConfig()
6540
6539
  const baseUrl = registryOverride || (registry && registry.url ? String(registry.url).replace(/\/$/, '') : null)
6541
- console.log("[checkpoints/snapshot] publish=1 registryOverride=%s baseUrl=%s hasToken=%s", registryOverride || "", baseUrl || "", registryToken ? "yes" : "no")
6542
6540
  if (!baseUrl || !registryToken) {
6543
6541
  await this.kernel.git.setCheckpointSync(created.remoteKey, created.id, { status: "needs_token", at: Date.now() }).catch(() => {})
6544
6542
  res.json({ ok: true, created: created || null, publish: { ok: false, code: "missing_token" } })
@@ -12714,12 +12712,14 @@ class Server {
12714
12712
  // }
12715
12713
  //
12716
12714
  res.render("setup", {
12715
+ mode: req.params.mode,
12717
12716
  wait,
12718
12717
  error,
12719
12718
  current,
12720
12719
  install_required,
12721
12720
  requirements,
12722
12721
  requirements_pending,
12722
+ portal: this.portal,
12723
12723
  logo: this.logo,
12724
12724
  theme: this.theme,
12725
12725
  agent: req.agent,
@@ -58,16 +58,27 @@ body.dark.task-launcher-page::before {
58
58
  linear-gradient(180deg, #0a0c10 0%, #07090d 100%);
59
59
  }
60
60
 
61
- body.task-page > header.navheader {
62
- background: color-mix(in srgb, var(--task-bg) 96%, white);
61
+ body.setup-page::before {
62
+ background:
63
+ radial-gradient(circle at top left, color-mix(in srgb, var(--pinokio-chrome-accent-fg-light) 10%, transparent), transparent 24rem),
64
+ linear-gradient(180deg, #fafbfd 0%, #f3f5f8 100%);
65
+ }
66
+
67
+ body.dark.setup-page::before {
68
+ background:
69
+ radial-gradient(circle at top left, color-mix(in srgb, var(--pinokio-chrome-accent-fg-dark) 16%, transparent), transparent 24rem),
70
+ linear-gradient(180deg, #0a0c10 0%, #07090d 100%);
71
+ }
72
+
73
+ body.task-launcher-page > header.navheader {
74
+ background: transparent;
63
75
  backdrop-filter: none;
64
- border-bottom: 1px solid var(--task-border);
65
76
  box-shadow: none;
66
77
  isolation: isolate;
67
78
  }
68
79
 
69
- body.dark.task-page > header.navheader {
70
- background: color-mix(in srgb, var(--task-bg) 94%, black);
80
+ body.dark.task-launcher-page > header.navheader {
81
+ background: transparent;
71
82
  }
72
83
 
73
84
  body.task-page main {
@@ -489,7 +500,7 @@ body.connect-page .task-shell {
489
500
  }
490
501
 
491
502
  .task-shell-body {
492
- padding: 0 28px 12px;
503
+ padding: 12px 28px 12px;
493
504
  display: grid;
494
505
  gap: 0;
495
506
  }
@@ -1628,6 +1639,205 @@ body.dark .task-link-button.danger {
1628
1639
  margin-top: 20px;
1629
1640
  }
1630
1641
 
1642
+ .setup-shell {
1643
+ width: min(1120px, 100%);
1644
+ }
1645
+
1646
+ .setup-layout {
1647
+ display: grid;
1648
+ grid-template-columns: minmax(0, 1fr) 320px;
1649
+ gap: 36px;
1650
+ align-items: start;
1651
+ padding-top: 18px;
1652
+ }
1653
+
1654
+ .setup-requirements-section {
1655
+ padding-top: 0;
1656
+ border-top: 0;
1657
+ }
1658
+
1659
+ .setup-requirement-list {
1660
+ display: grid;
1661
+ gap: 10px;
1662
+ }
1663
+
1664
+ .setup-requirement-row {
1665
+ display: grid;
1666
+ grid-template-columns: minmax(0, 1fr) auto;
1667
+ gap: 16px;
1668
+ align-items: center;
1669
+ padding: 14px 16px;
1670
+ border: 1px solid var(--task-border);
1671
+ border-radius: 14px;
1672
+ background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1673
+ }
1674
+
1675
+ .setup-requirement-row.needs-update {
1676
+ border-color: color-mix(in srgb, var(--task-accent) 18%, var(--task-border-strong));
1677
+ background: color-mix(in srgb, var(--task-accent) 8%, var(--task-panel));
1678
+ }
1679
+
1680
+ .setup-requirement-copy {
1681
+ min-width: 0;
1682
+ }
1683
+
1684
+ .setup-requirement-name {
1685
+ font-size: 14px;
1686
+ font-weight: 700;
1687
+ letter-spacing: -0.02em;
1688
+ color: var(--task-text);
1689
+ word-break: break-word;
1690
+ }
1691
+
1692
+ .setup-requirement-detail {
1693
+ margin-top: 4px;
1694
+ color: var(--task-muted);
1695
+ font-size: 12px;
1696
+ line-height: 1.45;
1697
+ }
1698
+
1699
+ .setup-requirement-state {
1700
+ display: inline-flex;
1701
+ align-items: center;
1702
+ justify-content: center;
1703
+ gap: 8px;
1704
+ min-height: 30px;
1705
+ padding: 0 11px;
1706
+ border: 1px solid var(--task-border);
1707
+ border-radius: 999px;
1708
+ background: color-mix(in srgb, var(--task-panel) 88%, var(--task-soft));
1709
+ color: var(--task-muted);
1710
+ font-size: 11px;
1711
+ font-weight: 700;
1712
+ letter-spacing: 0.01em;
1713
+ white-space: nowrap;
1714
+ }
1715
+
1716
+ .setup-requirement-row.is-installed .setup-requirement-state {
1717
+ color: var(--task-text);
1718
+ }
1719
+
1720
+ .setup-requirement-row.needs-update .setup-requirement-state {
1721
+ border-color: color-mix(in srgb, var(--task-accent) 24%, var(--task-border-strong));
1722
+ background: color-mix(in srgb, var(--task-accent) 10%, var(--task-panel));
1723
+ color: color-mix(in srgb, var(--task-accent) 70%, var(--task-text));
1724
+ }
1725
+
1726
+ .setup-actions-column {
1727
+ position: sticky;
1728
+ top: 18px;
1729
+ align-self: start;
1730
+ }
1731
+
1732
+ .setup-action-card {
1733
+ margin: 0;
1734
+ padding: 18px 0 0;
1735
+ border-top: 0;
1736
+ }
1737
+
1738
+ .setup-summary-grid {
1739
+ margin-top: 2px;
1740
+ }
1741
+
1742
+ .setup-primary-button {
1743
+ width: 100%;
1744
+ min-height: 40px;
1745
+ margin-top: 18px;
1746
+ font-size: 12px;
1747
+ }
1748
+
1749
+ .setup-install-form {
1750
+ margin: 0;
1751
+ }
1752
+
1753
+ .setup-secondary-actions {
1754
+ display: grid;
1755
+ gap: 10px;
1756
+ padding-top: 16px;
1757
+ margin-top: 16px;
1758
+ border-top: 1px solid var(--task-border);
1759
+ }
1760
+
1761
+ .setup-secondary-actions .task-inline-help {
1762
+ margin: 0;
1763
+ }
1764
+
1765
+ .setup-secondary-actions .task-button {
1766
+ justify-content: flex-start;
1767
+ }
1768
+
1769
+ .setup-reset-loading {
1770
+ display: flex;
1771
+ align-items: center;
1772
+ gap: 8px;
1773
+ margin-top: 12px;
1774
+ color: var(--task-muted);
1775
+ font-size: 12px;
1776
+ line-height: 1.45;
1777
+ }
1778
+
1779
+ .setup-state-root {
1780
+ min-height: 260px;
1781
+ display: flex;
1782
+ align-items: center;
1783
+ justify-content: center;
1784
+ }
1785
+
1786
+ .setup-state-root-error {
1787
+ width: min(420px, 100%);
1788
+ display: grid;
1789
+ gap: 14px;
1790
+ justify-items: flex-start;
1791
+ }
1792
+
1793
+ .setup-loading-state {
1794
+ width: min(480px, 100%);
1795
+ display: flex;
1796
+ align-items: center;
1797
+ gap: 16px;
1798
+ padding: 20px;
1799
+ border: 1px solid var(--task-border);
1800
+ border-radius: 16px;
1801
+ background: color-mix(in srgb, var(--task-panel) 94%, var(--task-soft));
1802
+ box-sizing: border-box;
1803
+ }
1804
+
1805
+ .setup-loading-icon {
1806
+ width: 42px;
1807
+ height: 42px;
1808
+ flex: 0 0 auto;
1809
+ display: inline-flex;
1810
+ align-items: center;
1811
+ justify-content: center;
1812
+ border: 1px solid var(--task-border);
1813
+ border-radius: 12px;
1814
+ background: color-mix(in srgb, var(--task-panel) 92%, var(--task-soft));
1815
+ color: var(--task-accent);
1816
+ font-size: 16px;
1817
+ }
1818
+
1819
+ .setup-loading-copy {
1820
+ min-width: 0;
1821
+ }
1822
+
1823
+ .setup-loading-title {
1824
+ font-size: 14px;
1825
+ font-weight: 700;
1826
+ letter-spacing: -0.02em;
1827
+ color: var(--task-text);
1828
+ }
1829
+
1830
+ .setup-loading-text {
1831
+ margin: 4px 0 0;
1832
+ color: var(--task-muted);
1833
+ font-size: 13px;
1834
+ line-height: 1.5;
1835
+ }
1836
+
1837
+ .setup-retry-button {
1838
+ min-height: 32px;
1839
+ }
1840
+
1631
1841
  .task-install-output-code {
1632
1842
  min-height: 180px;
1633
1843
  max-height: 320px;
@@ -2014,6 +2224,7 @@ body.task-share-overlay-open {
2014
2224
  body.plugin-page .plugin-shell-body,
2015
2225
  body.connect-page .connect-shell-body {
2016
2226
  padding-bottom: 24px;
2227
+ padding-top: 24px;
2017
2228
  }
2018
2229
 
2019
2230
  body.plugin-page .plugin-category-layout {
@@ -2838,6 +3049,28 @@ body.dark .plugin-option .option-icon {
2838
3049
  grid-template-columns: minmax(0, 1fr);
2839
3050
  align-items: stretch;
2840
3051
  }
3052
+
3053
+ .setup-layout {
3054
+ grid-template-columns: 1fr;
3055
+ gap: 22px;
3056
+ padding-top: 12px;
3057
+ }
3058
+
3059
+ .setup-actions-column {
3060
+ position: sticky;
3061
+ top: 12px;
3062
+ order: -1;
3063
+ z-index: 2;
3064
+ }
3065
+
3066
+ .setup-action-card {
3067
+ padding-top: 0;
3068
+ border-top: 0;
3069
+ }
3070
+
3071
+ .setup-loading-state {
3072
+ padding: 16px;
3073
+ }
2841
3074
  }
2842
3075
 
2843
3076
  @media (max-width: 560px) {
@@ -2889,6 +3122,16 @@ body.dark .plugin-option .option-icon {
2889
3122
  align-self: center;
2890
3123
  justify-self: end;
2891
3124
  }
3125
+
3126
+ .setup-requirement-row {
3127
+ grid-template-columns: 1fr;
3128
+ gap: 10px;
3129
+ align-items: flex-start;
3130
+ }
3131
+
3132
+ .setup-requirement-state {
3133
+ justify-self: flex-start;
3134
+ }
2892
3135
  }
2893
3136
 
2894
3137
  @media (prefers-reduced-motion: reduce) {