pinokiod 7.2.17 → 7.2.18

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 (51) hide show
  1. package/kernel/agent_instructions.js +166 -0
  2. package/kernel/api/index.js +137 -12
  3. package/kernel/bin/huggingface.js +1 -1
  4. package/kernel/environment.js +23 -9
  5. package/kernel/plugin_sources.js +57 -4
  6. package/kernel/prototype.js +4 -0
  7. package/kernel/shell.js +2 -0
  8. package/kernel/watch/index.js +31 -4
  9. package/package.json +1 -1
  10. package/server/features/index.js +4 -4
  11. package/server/features/{drafts → notes}/index.js +9 -9
  12. package/server/features/{drafts → notes}/parser.js +12 -7
  13. package/server/features/notes/public/notes.css +955 -0
  14. package/server/features/notes/public/notes.js +1149 -0
  15. package/server/features/{drafts → notes}/registry_import.js +22 -22
  16. package/server/features/notes/routes.js +156 -0
  17. package/server/features/notes/service.js +326 -0
  18. package/server/features/{drafts → notes}/watcher.js +14 -16
  19. package/server/index.js +61 -30
  20. package/server/lib/content_validation.js +19 -8
  21. package/server/lib/workspace_catalog.js +18 -18
  22. package/server/public/task-launcher.css +11 -3
  23. package/server/public/tasker.css +336 -0
  24. package/server/public/tasker.js +407 -0
  25. package/server/views/d.ejs +33 -2
  26. package/server/views/partials/menu.ejs +1 -1
  27. package/server/views/partials/workspace_row.ejs +11 -11
  28. package/server/views/pre.ejs +1 -1
  29. package/server/views/task_launch.ejs +10 -10
  30. package/server/views/tasker.ejs +40 -0
  31. package/server/views/terminal.ejs +15 -6
  32. package/server/views/terminals.ejs +0 -1
  33. package/server/views/workspaces.ejs +2 -1
  34. package/system/plugin/antigravity/pinokio.js +2 -4
  35. package/system/plugin/claude/pinokio.js +2 -4
  36. package/system/plugin/claude-auto/pinokio.js +2 -4
  37. package/system/plugin/claude-desktop/pinokio.js +2 -4
  38. package/system/plugin/codex/pinokio.js +2 -4
  39. package/system/plugin/codex-auto/pinokio.js +2 -4
  40. package/system/plugin/codex-desktop/pinokio.js +2 -4
  41. package/system/plugin/crush/pinokio.js +2 -4
  42. package/system/plugin/cursor/pinokio.js +2 -4
  43. package/system/plugin/gemini/pinokio.js +2 -4
  44. package/system/plugin/gemini-auto/pinokio.js +2 -4
  45. package/system/plugin/qwen/pinokio.js +2 -4
  46. package/system/plugin/vscode/pinokio.js +2 -4
  47. package/system/plugin/windsurf/pinokio.js +2 -4
  48. package/test/plugin-sources.test.js +45 -0
  49. package/server/features/drafts/public/drafts.js +0 -1504
  50. package/server/features/drafts/routes.js +0 -68
  51. package/server/features/drafts/service.js +0 -261
package/server/index.js CHANGED
@@ -2758,10 +2758,12 @@ class Server {
2758
2758
  } else {
2759
2759
  resolved = runner(this.kernel, this.kernel.info)
2760
2760
  }
2761
- runnable = resolved && Array.isArray(resolved[actionKey]) && resolved[actionKey].length > 0
2761
+ const action = resolved ? resolved[actionKey] : null
2762
+ runnable = typeof action === "function" || (Array.isArray(action) && action.length > 0)
2762
2763
  } else {
2763
- runnable = runner && Array.isArray(runner[actionKey]) && runner[actionKey].length > 0
2764
2764
  resolved = runner
2765
+ const action = resolved ? resolved[actionKey] : null
2766
+ runnable = typeof action === "function" || (Array.isArray(action) && action.length > 0)
2765
2767
  }
2766
2768
 
2767
2769
  let template = "terminal"
@@ -2870,10 +2872,10 @@ class Server {
2870
2872
  const protectionPreference = protectionAppId && this.appPreferences && typeof this.appPreferences.getPreference === "function"
2871
2873
  ? await this.appPreferences.getPreference(protectionAppId)
2872
2874
  : null
2873
- const draftWatchEnabled = this.kernel.watch && typeof this.kernel.watch.hasHandler === "function"
2874
- ? this.kernel.watch.hasHandler(resolved, "draft")
2875
+ const noteWatchEnabled = this.kernel.watch && typeof this.kernel.watch.hasHandler === "function"
2876
+ ? this.kernel.watch.hasHandler(resolved, "note")
2875
2877
  : false
2876
- const draftWatchCwd = draftWatchEnabled
2878
+ const noteWatchCwd = noteWatchEnabled
2877
2879
  ? (req.query.cwd || path.dirname(filepath))
2878
2880
  : ""
2879
2881
  const activeProcessWait = this.kernel.activeProcessWaits && this.kernel.activeProcessWaits[filepath]
@@ -2884,8 +2886,8 @@ class Server {
2884
2886
  projectName: (pathComponents.length > 0 ? pathComponents[0] : ''),
2885
2887
  protection_app_id: protectionAppId,
2886
2888
  protection_enabled: protectionPreference ? protectionPreference.protection_enabled !== false : false,
2887
- draft_watch_enabled: draftWatchEnabled,
2888
- draft_watch_cwd: draftWatchCwd,
2889
+ note_watch_enabled: noteWatchEnabled,
2890
+ note_watch_cwd: noteWatchCwd,
2889
2891
  active_process_wait: activeProcessWait ? {
2890
2892
  title: activeProcessWait.title,
2891
2893
  description: activeProcessWait.description,
@@ -5311,15 +5313,7 @@ class Server {
5311
5313
  return normalized
5312
5314
  }
5313
5315
  isValidBundledPluginConfig(pluginConfig) {
5314
- if (!pluginConfig || !Array.isArray(pluginConfig.run)) {
5315
- return false
5316
- }
5317
- for (const key of Object.keys(pluginConfig)) {
5318
- if (typeof pluginConfig[key] === "function") {
5319
- return false
5320
- }
5321
- }
5322
- return true
5316
+ return PluginSources.isValidPluginConfig(pluginConfig)
5323
5317
  }
5324
5318
  isPathInsideRootForBundledPlugin(candidatePath, rootPath) {
5325
5319
  const relative = path.relative(rootPath, candidatePath)
@@ -6857,9 +6851,9 @@ class Server {
6857
6851
  cwd: typeof pluginItem.ownerApp.cwd === "string" ? pluginItem.ownerApp.cwd : "",
6858
6852
  }
6859
6853
  : null,
6860
- hasInstall: Array.isArray(pluginItem?.install),
6861
- hasUninstall: Array.isArray(pluginItem?.uninstall),
6862
- hasUpdate: Array.isArray(pluginItem?.update),
6854
+ hasInstall: PluginSources.isAction(pluginItem?.install),
6855
+ hasUninstall: PluginSources.isAction(pluginItem?.uninstall),
6856
+ hasUpdate: PluginSources.isAction(pluginItem?.update),
6863
6857
  category,
6864
6858
  categoryTitle: category === "ide" ? "Desktop Plugin" : "Terminal Plugin",
6865
6859
  categorySubtitle: category === "ide" ? "Launch externally" : "Launch in Pinokio",
@@ -8415,12 +8409,12 @@ class Server {
8415
8409
  defaultRegistryUrl: DEFAULT_REGISTRY_URL
8416
8410
  })
8417
8411
  this.features = features
8418
- const drafts = features.drafts.service
8419
- this.drafts = drafts
8412
+ const notes = features.notes.service
8413
+ this.notes = notes
8420
8414
  const workspaceCatalog = createWorkspaceCatalogService({
8421
8415
  kernel: this.kernel,
8422
8416
  workspaceRuntime,
8423
- drafts
8417
+ notes
8424
8418
  })
8425
8419
  registerWorkspacesRoutes(this.app, {
8426
8420
  workspaceCatalog,
@@ -9744,6 +9738,13 @@ class Server {
9744
9738
  })
9745
9739
  }))
9746
9740
 
9741
+ this.app.get("/tasker", ex(async (req, res) => {
9742
+ res.render("tasker", {
9743
+ theme: this.theme,
9744
+ agent: req.agent,
9745
+ })
9746
+ }))
9747
+
9747
9748
  this.app.get("/tasks/new", ex(async (req, res) => {
9748
9749
  const sourceWorkspace = normalizeTaskBuilderSourceWorkspace(req.query.sourceWorkspaceCwd)
9749
9750
  const lockTargetSelection = req.query.lockTarget === "1" || req.query.lockTarget === "true"
@@ -13415,9 +13416,21 @@ class Server {
13415
13416
  // }
13416
13417
  // }))
13417
13418
  this.app.post("/env", ex(async (req, res) => {
13418
- let fullpath = path.resolve(this.kernel.homedir, req.body.filepath, "ENVIRONMENT")
13419
+ const requestFilepath = typeof req.body.filepath === "string" ? req.body.filepath : ""
13420
+ const requestRoot = path.resolve(this.kernel.homedir, requestFilepath)
13421
+ let fullpath = path.resolve(requestRoot, "ENVIRONMENT")
13422
+ if (!(await this.kernel.exists(fullpath))) {
13423
+ const normalizedFilepath = requestFilepath.replace(/\\/g, "/")
13424
+ const filepathParts = normalizedFilepath.split("/").filter(Boolean)
13425
+ if (filepathParts[0] === "api" && filepathParts[1] && filepathParts.length === 2) {
13426
+ const launcher = await this.kernel.api.launcher(filepathParts[1])
13427
+ if (launcher && launcher.launcher_root) {
13428
+ fullpath = path.resolve(launcher.root, launcher.launcher_root, "ENVIRONMENT")
13429
+ }
13430
+ }
13431
+ }
13419
13432
  let updated = req.body.vals
13420
- let hosts = req.body.hosts
13433
+ let hosts = req.body.hosts || {}
13421
13434
  await Util.update_env(fullpath, updated)
13422
13435
  const normalizedFilepath = typeof req.body.filepath === "string"
13423
13436
  ? req.body.filepath.replace(/\\/g, "/")
@@ -13570,14 +13583,15 @@ class Server {
13570
13583
  this.app.get("/pre/api/:name", ex(async (req, res) => {
13571
13584
  let launcher = await this.kernel.api.launcher(req.params.name)
13572
13585
  let config = launcher.script
13573
- if (config && config.pre) {
13574
- config.pre.forEach((item) => {
13575
- if (item.icon) {
13586
+ if (config && Array.isArray(config.pre)) {
13587
+ const items = config.pre.filter((item) => item && typeof item === "object")
13588
+ items.forEach((item) => {
13589
+ if (typeof item.icon === "string" && item.icon) {
13576
13590
  item.icon = `/api/${req.params.name}/${item.icon}?raw=true`
13577
13591
  } else {
13578
13592
  item.icon = "/pinokio-black.png"
13579
13593
  }
13580
- if (!item.href.startsWith("http")) {
13594
+ if (typeof item.href === "string" && item.href && !item.href.startsWith("http")) {
13581
13595
  item.href = path.resolve(this.kernel.homedir, "api", req.params.name, item.href)
13582
13596
  }
13583
13597
  })
@@ -13588,7 +13602,7 @@ class Server {
13588
13602
  theme: this.theme,
13589
13603
  agent: req.agent,
13590
13604
  name: req.params.name,
13591
- items: config.pre,
13605
+ items,
13592
13606
  env
13593
13607
  })
13594
13608
  } else {
@@ -14074,7 +14088,7 @@ class Server {
14074
14088
  mode = "launch_type.desktop"
14075
14089
  } else if (launchType === "terminal") {
14076
14090
  mode = "launch_type.terminal"
14077
- } else {
14091
+ } else if (Array.isArray(item.run)) {
14078
14092
  for(let step of item.run) {
14079
14093
  if (step.method === "exec") {
14080
14094
  mode = "exec"
@@ -14089,6 +14103,8 @@ class Server {
14089
14103
  break
14090
14104
  }
14091
14105
  }
14106
+ } else if (typeof item.run === "function") {
14107
+ mode = "shell"
14092
14108
  }
14093
14109
  if (mode === "launch_type.desktop" || mode === "exec" || mode === "launch") {
14094
14110
  item.type = "Open"
@@ -14141,6 +14157,20 @@ class Server {
14141
14157
  spec = await fs.promises.readFile(path.resolve(filepath, "SPEC.md"), "utf8")
14142
14158
  } catch (e) {
14143
14159
  }
14160
+ const registryEnabled = await this.isRegistryEnabled().catch(() => false)
14161
+ let registry_parent_url = ""
14162
+ if (registryEnabled) {
14163
+ try {
14164
+ registry_parent_url = await git.getConfig({
14165
+ fs,
14166
+ http,
14167
+ dir: filepath,
14168
+ path: "remote.origin.url"
14169
+ }) || ""
14170
+ } catch (_) {
14171
+ registry_parent_url = ""
14172
+ }
14173
+ }
14144
14174
  res.render("d", {
14145
14175
  filepath,
14146
14176
  spec,
@@ -14151,6 +14181,7 @@ class Server {
14151
14181
  install: this.install,
14152
14182
  agent: req.agent,
14153
14183
  theme: this.theme,
14184
+ registry_parent_url,
14154
14185
  //dynamic: plugin_menu
14155
14186
  dynamic,
14156
14187
  })
@@ -147,9 +147,9 @@ function createContentValidationService({ kernel }) {
147
147
  absolutePath,
148
148
  dir: pluginDir,
149
149
  config,
150
- hasInstall: Array.isArray(config && config.install),
151
- hasUpdate: Array.isArray(config && config.update),
152
- hasUninstall: Array.isArray(config && config.uninstall),
150
+ hasInstall: PluginSources.isAction(config && config.install),
151
+ hasUpdate: PluginSources.isAction(config && config.update),
152
+ hasUninstall: PluginSources.isAction(config && config.uninstall),
153
153
  image: null,
154
154
  }
155
155
 
@@ -252,21 +252,32 @@ function createContentValidationService({ kernel }) {
252
252
  { file: absolutePath }
253
253
  ))
254
254
  } else {
255
- if (!Array.isArray(config.run)) {
255
+ if (!PluginSources.isAction(config.run)) {
256
256
  errors.push(buildError(
257
- "Plugins must define a top-level run array.",
258
- "Add `run: [...]` to pinokio.js.",
257
+ "Plugins must define a top-level run array or function.",
258
+ "Add `run: [...]` or `run: async (ctx) => [...]` to pinokio.js.",
259
259
  { file: absolutePath }
260
260
  ))
261
261
  }
262
- const topLevelFunctionKeys = Object.keys(config).filter((key) => typeof config[key] === "function")
262
+ const topLevelFunctionKeys = Object.keys(config).filter((key) => {
263
+ return typeof config[key] === "function" && !PluginSources.ACTION_KEYS.has(key)
264
+ })
263
265
  if (topLevelFunctionKeys.length > 0) {
264
266
  errors.push(buildError(
265
267
  `Top-level function fields are not supported: ${topLevelFunctionKeys.join(", ")}.`,
266
- "Move those functions out of pinokio.js or replace them with data.",
268
+ "Only action fields such as run, install, uninstall, and update may be functions.",
267
269
  { file: absolutePath }
268
270
  ))
269
271
  }
272
+ for (const key of PluginSources.ACTION_KEYS) {
273
+ if (key in config && !PluginSources.isAction(config[key])) {
274
+ errors.push(buildError(
275
+ `Plugin action ${key} must be an array or function.`,
276
+ `Set ${key} to an array or async function returning an array.`,
277
+ { file: absolutePath }
278
+ ))
279
+ }
280
+ }
270
281
  if (normalizedPath.startsWith("/plugin/")) {
271
282
  const declaredPath = typeof config.path === "string" ? config.path.trim() : ""
272
283
  if (declaredPath !== "plugin") {
@@ -52,13 +52,13 @@ function sortWorkspaces(items, sort) {
52
52
  return sorted
53
53
  }
54
54
 
55
- function newestDraft(drafts) {
56
- return [...drafts].sort((a, b) => {
55
+ function newestNote(notes) {
56
+ return [...notes].sort((a, b) => {
57
57
  return (new Date(b.updatedAt || 0).getTime() || 0) - (new Date(a.updatedAt || 0).getTime() || 0)
58
58
  })[0] || null
59
59
  }
60
60
 
61
- function createWorkspaceCatalogService({ kernel, workspaceRuntime, drafts }) {
61
+ function createWorkspaceCatalogService({ kernel, workspaceRuntime, notes }) {
62
62
  async function list(options = {}) {
63
63
  const sort = normalizeSortMode(options.sort)
64
64
  const root = path.resolve(kernel.path("workspaces"))
@@ -71,14 +71,14 @@ function createWorkspaceCatalogService({ kernel, workspaceRuntime, drafts }) {
71
71
  liveByPath.set(normalizePathKey(group.cwd), group)
72
72
  }
73
73
 
74
- const draftByPath = new Map()
75
- const pendingDrafts = drafts ? await drafts.listPending({}).catch(() => []) : []
76
- for (const draft of pendingDrafts) {
77
- if (!draft.cwd) continue
78
- const key = normalizePathKey(draft.cwd)
79
- const list = draftByPath.get(key) || []
80
- list.push(draft)
81
- draftByPath.set(key, list)
74
+ const noteByPath = new Map()
75
+ const pendingNotes = notes ? await notes.listPending({}).catch(() => []) : []
76
+ for (const note of pendingNotes) {
77
+ if (!note.cwd) continue
78
+ const key = normalizePathKey(note.cwd)
79
+ const list = noteByPath.get(key) || []
80
+ list.push(note)
81
+ noteByPath.set(key, list)
82
82
  }
83
83
 
84
84
  const folders = entries.filter((entry) => entry.isDirectory())
@@ -91,13 +91,13 @@ function createWorkspaceCatalogService({ kernel, workspaceRuntime, drafts }) {
91
91
  const live = liveByPath.get(key)
92
92
  const shells = live?.shells || []
93
93
  const scripts = live?.scripts || []
94
- const workspaceDrafts = draftByPath.get(key) || []
95
- const draft = newestDraft(workspaceDrafts)
94
+ const workspaceNotes = noteByPath.get(key) || []
95
+ const note = newestNote(workspaceNotes)
96
96
  const modifiedAtMs = stats?.mtimeMs || 0
97
97
  const lastOpenedAtMs = latestTimestamp([
98
98
  modifiedAtMs,
99
99
  ...shells.map((shell) => shell.start_time),
100
- ...workspaceDrafts.map((item) => item.updatedAt),
100
+ ...workspaceNotes.map((item) => item.updatedAt),
101
101
  ])
102
102
  const primaryShell = shells.length === 1 ? shells[0] : null
103
103
  const primaryScript = scripts.length === 1 ? scripts[0] : null
@@ -116,12 +116,12 @@ function createWorkspaceCatalogService({ kernel, workspaceRuntime, drafts }) {
116
116
  counts: {
117
117
  shells: shells.length,
118
118
  scripts: scripts.length,
119
- drafts: workspaceDrafts.length,
119
+ notes: workspaceNotes.length,
120
120
  },
121
121
  shells,
122
122
  scripts,
123
- draft,
124
- draftReady: Boolean(draft),
123
+ note,
124
+ noteReady: Boolean(note),
125
125
  primaryUrl: primaryScript?.url || primaryShell?.url || null,
126
126
  launchUrl: toRoutePath(cwd),
127
127
  })
@@ -140,7 +140,7 @@ function createWorkspaceCatalogService({ kernel, workspaceRuntime, drafts }) {
140
140
  total: items.length,
141
141
  running: running.length,
142
142
  offline: offline.length,
143
- drafts: items.filter((item) => item.draftReady).length,
143
+ notes: items.filter((item) => item.noteReady).length,
144
144
  },
145
145
  }
146
146
  }
@@ -1575,11 +1575,19 @@ body.dark .task-link-button.danger {
1575
1575
  }
1576
1576
 
1577
1577
  .task-run-footer {
1578
- margin-top: 14px;
1578
+ display: grid;
1579
+ gap: 8px;
1580
+ margin-top: 2px;
1581
+ padding-top: 14px;
1582
+ border-top: 1px solid var(--task-border);
1579
1583
  }
1580
1584
 
1581
- .task-run-actions {
1585
+ .task-run-actions,
1586
+ .task-run-footer .task-run-button {
1582
1587
  margin: 0;
1588
+ width: 100%;
1589
+ min-height: 38px;
1590
+ font-size: 12px;
1583
1591
  }
1584
1592
 
1585
1593
  .task-submit-feedback {
@@ -1856,7 +1864,7 @@ body.dark .task-link-button.danger {
1856
1864
  opacity: 0.55;
1857
1865
  }
1858
1866
 
1859
- .task-prompt-actions .task-submit-feedback {
1867
+ .task-run-footer .task-submit-feedback {
1860
1868
  margin-top: 2px;
1861
1869
  }
1862
1870
 
@@ -0,0 +1,336 @@
1
+ body.tasker-page {
2
+ --tasker-bg: #f5f6f8;
3
+ --tasker-panel: #ffffff;
4
+ --tasker-row: #f8fafc;
5
+ --tasker-row-hover: #f1f5f9;
6
+ --tasker-row-selected: #e8edf4;
7
+ --tasker-control: #ffffff;
8
+ --tasker-icon-bg: #ffffff;
9
+ --tasker-icon-selected-bg: #f8fafc;
10
+ --tasker-border: rgba(15, 23, 42, 0.1);
11
+ --tasker-border-strong: rgba(15, 23, 42, 0.18);
12
+ --tasker-text: rgba(15, 23, 42, 0.9);
13
+ --tasker-text-strong: rgba(15, 23, 42, 0.98);
14
+ --tasker-muted: rgba(71, 85, 105, 0.78);
15
+ --tasker-muted-strong: rgba(51, 65, 85, 0.82);
16
+ --tasker-focus: rgba(15, 23, 42, 0.1);
17
+ --tasker-focus-border: rgba(15, 23, 42, 0.28);
18
+ --tasker-clear-hover: rgba(15, 23, 42, 0.06);
19
+ --tasker-selection-bar: rgba(15, 23, 42, 0.48);
20
+ --tasker-shadow: 0 28px 80px rgba(15, 23, 42, 0.12);
21
+ min-height: 100vh;
22
+ margin: 0;
23
+ background: var(--tasker-bg);
24
+ color: var(--tasker-text);
25
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
26
+ font-size: 14px;
27
+ display: block;
28
+ }
29
+
30
+ body.dark.tasker-page {
31
+ --tasker-bg: #050607;
32
+ --tasker-panel: #0d0d0f;
33
+ --tasker-row: #141416;
34
+ --tasker-row-hover: #18181b;
35
+ --tasker-row-selected: #202024;
36
+ --tasker-control: #111114;
37
+ --tasker-icon-bg: #08090b;
38
+ --tasker-icon-selected-bg: #0c0d10;
39
+ --tasker-border: rgba(255, 255, 255, 0.09);
40
+ --tasker-border-strong: rgba(255, 255, 255, 0.18);
41
+ --tasker-text: rgba(255, 255, 255, 0.88);
42
+ --tasker-text-strong: rgba(255, 255, 255, 0.96);
43
+ --tasker-muted: rgba(255, 255, 255, 0.48);
44
+ --tasker-muted-strong: rgba(255, 255, 255, 0.62);
45
+ --tasker-focus: rgba(255, 255, 255, 0.14);
46
+ --tasker-focus-border: rgba(255, 255, 255, 0.24);
47
+ --tasker-clear-hover: rgba(255, 255, 255, 0.06);
48
+ --tasker-selection-bar: rgba(255, 255, 255, 0.64);
49
+ --tasker-shadow: 0 32px 90px rgba(0, 0, 0, 0.42);
50
+ }
51
+
52
+ body.tasker-page .tasker-main {
53
+ min-height: calc(100vh - 44px);
54
+ box-sizing: border-box;
55
+ display: grid;
56
+ place-items: center;
57
+ padding: 32px 16px;
58
+ }
59
+
60
+ .tasker-panel {
61
+ width: min(760px, calc(100vw - 32px));
62
+ max-height: min(660px, calc(100vh - 96px));
63
+ box-sizing: border-box;
64
+ display: grid;
65
+ grid-template-rows: auto auto minmax(0, 1fr);
66
+ overflow: hidden;
67
+ padding: 14px;
68
+ border: 1px solid var(--tasker-border);
69
+ border-radius: 8px;
70
+ background: var(--tasker-panel);
71
+ box-shadow: var(--tasker-shadow);
72
+ }
73
+
74
+ .tasker-search-wrap {
75
+ padding: 0 0 14px;
76
+ }
77
+
78
+ .tasker-search-field {
79
+ min-height: 42px;
80
+ box-sizing: border-box;
81
+ display: grid;
82
+ grid-template-columns: auto minmax(0, 1fr) auto;
83
+ align-items: center;
84
+ gap: 10px;
85
+ padding: 0 12px;
86
+ border: 1px solid var(--tasker-border-strong);
87
+ border-radius: 7px;
88
+ background: var(--tasker-control);
89
+ }
90
+
91
+ .tasker-search-field:focus-within {
92
+ border-color: var(--tasker-focus-border);
93
+ box-shadow: 0 0 0 3px var(--tasker-focus);
94
+ }
95
+
96
+ .tasker-search-field > i {
97
+ color: var(--tasker-muted);
98
+ font-size: 13px;
99
+ }
100
+
101
+ .tasker-search-input {
102
+ width: 100%;
103
+ min-width: 0;
104
+ min-height: 40px;
105
+ padding: 0;
106
+ border: 0;
107
+ outline: 0;
108
+ background: transparent;
109
+ color: var(--tasker-text);
110
+ font: inherit;
111
+ font-size: 14px;
112
+ line-height: 1.4;
113
+ }
114
+
115
+ .tasker-search-input::placeholder {
116
+ color: var(--tasker-muted);
117
+ }
118
+
119
+ .tasker-search-input::-webkit-search-cancel-button {
120
+ appearance: none;
121
+ -webkit-appearance: none;
122
+ }
123
+
124
+ .tasker-search-clear {
125
+ appearance: none;
126
+ width: 28px;
127
+ height: 28px;
128
+ display: inline-flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ border: 0;
132
+ border-radius: 6px;
133
+ background: transparent;
134
+ color: var(--tasker-muted);
135
+ cursor: pointer;
136
+ }
137
+
138
+ .tasker-search-clear:hover,
139
+ .tasker-search-clear:focus-visible {
140
+ background: var(--tasker-clear-hover);
141
+ color: var(--tasker-text);
142
+ outline: none;
143
+ }
144
+
145
+ .tasker-search-clear[hidden] {
146
+ display: none !important;
147
+ }
148
+
149
+ .tasker-status {
150
+ padding: 0 2px 12px;
151
+ color: var(--tasker-muted-strong);
152
+ font-size: 12px;
153
+ font-weight: 750;
154
+ line-height: 1.2;
155
+ letter-spacing: 0;
156
+ text-transform: uppercase;
157
+ }
158
+
159
+ .tasker-status[hidden] {
160
+ display: none !important;
161
+ }
162
+
163
+ .tasker-list {
164
+ min-height: 0;
165
+ display: grid;
166
+ gap: 0;
167
+ overflow: auto;
168
+ border: 1px solid var(--tasker-border);
169
+ border-radius: 7px;
170
+ background: var(--tasker-row);
171
+ }
172
+
173
+ .tasker-list[hidden] {
174
+ display: none !important;
175
+ }
176
+
177
+ .tasker-row {
178
+ position: relative;
179
+ width: 100%;
180
+ min-width: 0;
181
+ box-sizing: border-box;
182
+ display: grid;
183
+ grid-template-columns: auto minmax(0, 1fr) auto;
184
+ align-items: center;
185
+ gap: 13px;
186
+ padding: 15px 14px;
187
+ border-bottom: 1px solid var(--tasker-border);
188
+ background: var(--tasker-row);
189
+ color: var(--tasker-text);
190
+ text-align: left;
191
+ text-decoration: none;
192
+ }
193
+
194
+ .tasker-row:last-child {
195
+ border-bottom: 0;
196
+ }
197
+
198
+ .tasker-row:hover,
199
+ .tasker-row:focus-visible {
200
+ background: var(--tasker-row-hover);
201
+ color: var(--tasker-text);
202
+ outline: none;
203
+ }
204
+
205
+ .tasker-row.selected {
206
+ background: var(--tasker-row-selected);
207
+ color: var(--tasker-text);
208
+ outline: none;
209
+ }
210
+
211
+ .tasker-row.selected::before {
212
+ content: "";
213
+ position: absolute;
214
+ top: 12px;
215
+ bottom: 12px;
216
+ left: 0;
217
+ width: 3px;
218
+ border-radius: 0 3px 3px 0;
219
+ background: var(--tasker-selection-bar);
220
+ }
221
+
222
+ .tasker-row.selected .tasker-row-icon {
223
+ border-color: var(--tasker-border-strong);
224
+ background: var(--tasker-icon-selected-bg);
225
+ color: var(--tasker-text-strong);
226
+ }
227
+
228
+ .tasker-row.selected .tasker-row-label {
229
+ color: var(--tasker-text-strong);
230
+ }
231
+
232
+ .tasker-row.selected .tasker-row-meta {
233
+ color: var(--tasker-muted-strong);
234
+ }
235
+
236
+ .tasker-row-icon {
237
+ width: 29px;
238
+ height: 29px;
239
+ display: inline-flex;
240
+ align-items: center;
241
+ justify-content: center;
242
+ border: 1px solid var(--tasker-border);
243
+ border-radius: 7px;
244
+ background: var(--tasker-icon-bg);
245
+ color: var(--tasker-muted-strong);
246
+ font-size: 12px;
247
+ }
248
+
249
+ .tasker-row-copy {
250
+ min-width: 0;
251
+ display: grid;
252
+ gap: 3px;
253
+ }
254
+
255
+ .tasker-row-label {
256
+ min-width: 0;
257
+ overflow: hidden;
258
+ color: var(--tasker-text);
259
+ font-size: 15px;
260
+ font-weight: 750;
261
+ line-height: 1.25;
262
+ letter-spacing: 0;
263
+ text-overflow: ellipsis;
264
+ white-space: nowrap;
265
+ }
266
+
267
+ .tasker-row-meta {
268
+ min-width: 0;
269
+ overflow: hidden;
270
+ display: -webkit-box;
271
+ -webkit-box-orient: vertical;
272
+ -webkit-line-clamp: 2;
273
+ color: var(--tasker-muted);
274
+ font-size: 13px;
275
+ font-weight: 600;
276
+ line-height: 1.38;
277
+ }
278
+
279
+ .tasker-row-hint {
280
+ color: var(--tasker-muted);
281
+ font-size: 11px;
282
+ opacity: 0;
283
+ }
284
+
285
+ .tasker-row:hover .tasker-row-hint,
286
+ .tasker-row:focus-visible .tasker-row-hint,
287
+ .tasker-row.selected .tasker-row-hint {
288
+ opacity: 1;
289
+ }
290
+
291
+ .tasker-empty {
292
+ padding: 18px 14px;
293
+ border: 1px solid var(--tasker-border);
294
+ border-radius: 7px;
295
+ background: var(--tasker-row);
296
+ color: var(--tasker-muted);
297
+ font-size: 13px;
298
+ line-height: 1.45;
299
+ text-align: center;
300
+ }
301
+
302
+ .tasker-empty[hidden] {
303
+ display: none !important;
304
+ }
305
+
306
+ @media (max-width: 768px) {
307
+ body.tasker-page {
308
+ display: block !important;
309
+ }
310
+
311
+ body.tasker-page .tasker-main {
312
+ align-items: start;
313
+ padding: 16px 10px 24px;
314
+ }
315
+
316
+ .tasker-panel {
317
+ width: min(100%, calc(100vw - 20px));
318
+ max-height: none;
319
+ }
320
+ }
321
+
322
+ @media (max-width: 520px) {
323
+ .tasker-panel {
324
+ padding: 10px;
325
+ }
326
+
327
+ .tasker-row {
328
+ grid-template-columns: minmax(0, 1fr) auto;
329
+ gap: 8px;
330
+ padding: 13px 12px;
331
+ }
332
+
333
+ .tasker-row-icon {
334
+ display: none;
335
+ }
336
+ }