@myrialabs/clopen 0.1.7 → 0.1.8

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.
@@ -283,6 +283,7 @@ export class GitService {
283
283
 
284
284
  const args = [
285
285
  'log',
286
+ '--topo-order',
286
287
  `--format=${format}`,
287
288
  `--max-count=${limit + 1}`, // +1 to check if there are more
288
289
  `--skip=${skip}`
package/bun.lock CHANGED
@@ -13,8 +13,12 @@
13
13
  "@modelcontextprotocol/sdk": "^1.26.0",
14
14
  "@monaco-editor/loader": "^1.5.0",
15
15
  "@opencode-ai/sdk": "^1.2.15",
16
- "@xterm/addon-fit": "^0.10.0",
17
- "@xterm/addon-web-links": "^0.11.0",
16
+ "@xterm/addon-clipboard": "^0.2.0",
17
+ "@xterm/addon-fit": "^0.11.0",
18
+ "@xterm/addon-ligatures": "^0.10.0",
19
+ "@xterm/addon-unicode11": "^0.9.0",
20
+ "@xterm/addon-web-links": "^0.12.0",
21
+ "@xterm/xterm": "^6.0.0",
18
22
  "bun-pty": "^0.4.2",
19
23
  "cloudflared": "^0.7.1",
20
24
  "elysia": "^1.4.19",
@@ -26,7 +30,6 @@
26
30
  "puppeteer": "^24.33.0",
27
31
  "puppeteer-cluster": "^0.25.0",
28
32
  "qrcode": "^1.5.4",
29
- "xterm": "^5.3.0",
30
33
  },
31
34
  "devDependencies": {
32
35
  "@eslint/js": "^9.31.0",
@@ -35,7 +38,6 @@
35
38
  "@types/bun": "^1.2.18",
36
39
  "@types/node": "^24.0.14",
37
40
  "@types/qrcode": "^1.5.6",
38
- "@types/xterm": "^3.0.0",
39
41
  "concurrently": "^9.2.1",
40
42
  "eslint": "^9.31.0",
41
43
  "eslint-plugin-svelte": "^3.10.1",
@@ -310,8 +312,6 @@
310
312
 
311
313
  "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
312
314
 
313
- "@types/xterm": ["@types/xterm@3.0.0", "", { "dependencies": { "xterm": "*" } }, "sha512-+VaAJQmE7E1d1ebkIh/Zdc2mbXBVwxZGGSgqwzDPpk/HKo0mNT+iX5ZrnswztHSV+CDV+bURl7Yg7PWF7IZfXQ=="],
314
-
315
315
  "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
316
316
 
317
317
  "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="],
@@ -334,11 +334,17 @@
334
334
 
335
335
  "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="],
336
336
 
337
- "@xterm/addon-fit": ["@xterm/addon-fit@0.10.0", "", { "peerDependencies": { "@xterm/xterm": "^5.0.0" } }, "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ=="],
337
+ "@xterm/addon-clipboard": ["@xterm/addon-clipboard@0.2.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-Dl31BCtBhLaUEECUbEiVcCLvLBbaeGYdT7NofB8OJkGTD3MWgBsaLjXvfGAD4tQNHhm6mbKyYkR7XD8kiZsdNg=="],
338
+
339
+ "@xterm/addon-fit": ["@xterm/addon-fit@0.11.0", "", {}, "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g=="],
340
+
341
+ "@xterm/addon-ligatures": ["@xterm/addon-ligatures@0.10.0", "", { "dependencies": { "font-finder": "^1.1.0", "font-ligatures": "^1.4.1" } }, "sha512-/Few8ZSHMib7sGjRJoc5l7bCtEB9XJfkNofvPpOcWADxKaUl8og8P172j67OoACSNJAXqeCLIuvj8WFCBkcTxg=="],
342
+
343
+ "@xterm/addon-unicode11": ["@xterm/addon-unicode11@0.9.0", "", {}, "sha512-FxDnYcyuXhNl+XSqGZL/t0U9eiNb/q3EWT5rYkQT/zuig8Gz/VagnQANKHdDWFM2lTMk9ly0EFQxxxtZUoRetw=="],
338
344
 
339
- "@xterm/addon-web-links": ["@xterm/addon-web-links@0.11.0", "", { "peerDependencies": { "@xterm/xterm": "^5.0.0" } }, "sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q=="],
345
+ "@xterm/addon-web-links": ["@xterm/addon-web-links@0.12.0", "", {}, "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw=="],
340
346
 
341
- "@xterm/xterm": ["@xterm/xterm@5.5.0", "", {}, "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="],
347
+ "@xterm/xterm": ["@xterm/xterm@6.0.0", "", {}, "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg=="],
342
348
 
343
349
  "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
344
350
 
@@ -562,6 +568,10 @@
562
568
 
563
569
  "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
564
570
 
571
+ "font-finder": ["font-finder@1.1.0", "", { "dependencies": { "get-system-fonts": "^2.0.0", "promise-stream-reader": "^1.0.1" } }, "sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw=="],
572
+
573
+ "font-ligatures": ["font-ligatures@1.4.1", "", { "dependencies": { "font-finder": "^1.0.3", "lru-cache": "^6.0.0", "opentype.js": "^0.8.0" } }, "sha512-7W6zlfyhvCqShZ5ReUWqmSd9vBaUudW0Hxis+tqUjtHhsPU+L3Grf8mcZAtCiXHTzorhwdRTId2WeH/88gdFkw=="],
574
+
565
575
  "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
566
576
 
567
577
  "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
@@ -578,6 +588,8 @@
578
588
 
579
589
  "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
580
590
 
591
+ "get-system-fonts": ["get-system-fonts@2.0.2", "", {}, "sha512-zzlgaYnHMIEgHRrfC7x0Qp0Ylhw/sHpM6MHXeVBTYIsvGf5GpbnClB+Q6rAPdn+0gd2oZZIo6Tj3EaWrt4VhDQ=="],
592
+
581
593
  "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
582
594
 
583
595
  "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -638,6 +650,8 @@
638
650
 
639
651
  "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
640
652
 
653
+ "js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
654
+
641
655
  "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
642
656
 
643
657
  "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -694,7 +708,7 @@
694
708
 
695
709
  "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
696
710
 
697
- "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
711
+ "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
698
712
 
699
713
  "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
700
714
 
@@ -742,6 +756,8 @@
742
756
 
743
757
  "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
744
758
 
759
+ "opentype.js": ["opentype.js@0.8.0", "", { "dependencies": { "tiny-inflate": "^1.0.2" }, "bin": { "ot": "./bin/ot" } }, "sha512-FQHR4oGP+a0m/f6yHoRpBOIbn/5ZWxKd4D/djHVJu8+KpBTYrJda0b7mLcgDEMWXE9xBCJm+qb0yv6FcvPjukg=="],
760
+
745
761
  "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
746
762
 
747
763
  "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@@ -790,6 +806,8 @@
790
806
 
791
807
  "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
792
808
 
809
+ "promise-stream-reader": ["promise-stream-reader@1.0.1", "", {}, "sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg=="],
810
+
793
811
  "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
794
812
 
795
813
  "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
@@ -904,6 +922,8 @@
904
922
 
905
923
  "text-extensions": ["text-extensions@3.1.0", "", {}, "sha512-anOjtXr8OT5w4vc/2mP4AYTCE0GWc/21icGmaHtBHnI7pN7o01a/oqG9m06/rGzoAsDm/WNzggBpqptuCmRlZQ=="],
906
924
 
925
+ "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
926
+
907
927
  "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
908
928
 
909
929
  "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
@@ -958,10 +978,10 @@
958
978
 
959
979
  "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
960
980
 
961
- "xterm": ["xterm@5.3.0", "", {}, "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="],
962
-
963
981
  "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
964
982
 
983
+ "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
984
+
965
985
  "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
966
986
 
967
987
  "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
@@ -1012,6 +1032,8 @@
1012
1032
 
1013
1033
  "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
1014
1034
 
1035
+ "proxy-agent/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
1036
+
1015
1037
  "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
1016
1038
 
1017
1039
  "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
@@ -58,9 +58,9 @@
58
58
  indentGuide: '#21262d',
59
59
  indentGuideActive: '#30363d',
60
60
  ruler: '#21262d',
61
- scrollbar: '#21262d60',
62
- scrollbarHover: '#30363d80',
63
- scrollbarActive: '#6e7681'
61
+ scrollbar: '#6e768140',
62
+ scrollbarHover: '#6e768180',
63
+ scrollbarActive: '#8b949e'
64
64
  },
65
65
  tokens: {
66
66
  comment: '6A9955',
@@ -87,9 +87,9 @@
87
87
  indentGuide: '#e3e3e3',
88
88
  indentGuideActive: '#d3d3d3',
89
89
  ruler: '#e3e3e3',
90
- scrollbar: '#cccccc60',
91
- scrollbarHover: '#999999a0',
92
- scrollbarActive: '#666666'
90
+ scrollbar: '#92929240',
91
+ scrollbarHover: '#92929280',
92
+ scrollbarActive: '#555555'
93
93
  },
94
94
  tokens: {
95
95
  comment: '008000',
@@ -14,7 +14,7 @@
14
14
  import { settings } from '$frontend/lib/stores/features/settings.svelte';
15
15
 
16
16
  // Import CSS directly - Vite will handle it properly
17
- import 'xterm/css/xterm.css';
17
+ import '@xterm/xterm/css/xterm.css';
18
18
 
19
19
  // Props
20
20
  const {
@@ -183,127 +183,39 @@
183
183
  };
184
184
  }
185
185
 
186
- // Handle right-click copy/paste functionality
187
- function setupRightClickCopy() {
186
+ // Handle right-click copy/paste via clipboard addon
187
+ function setupClipboardHandling() {
188
188
  if (!terminalContainer || !xtermService.terminal) return;
189
189
 
190
- const handleRightClick = async (event: MouseEvent) => {
191
- event.preventDefault(); // Prevent default context menu
192
-
193
- // Get selected text from xterm.js
190
+ const handleContextMenu = async (event: MouseEvent) => {
191
+ event.preventDefault();
192
+
194
193
  const selectedText = xtermService.getSelectedText();
195
-
196
- if (selectedText && selectedText.trim()) {
194
+
195
+ if (selectedText?.trim()) {
197
196
  // Copy selected text to clipboard
198
197
  try {
199
198
  await navigator.clipboard.writeText(selectedText);
200
-
201
- // Clear selection after copy (like most terminals do)
202
199
  xtermService.clearSelection();
203
-
204
- // Show brief visual feedback
205
- showCopyFeedback();
206
- } catch (err) {
207
- }
200
+ } catch { /* clipboard not available */ }
208
201
  } else {
209
- // No text selected - paste from clipboard instead
202
+ // No text selected - paste from clipboard
210
203
  try {
211
- const clipboardText = await navigator.clipboard.readText();
212
- if (clipboardText && clipboardText.trim() && xtermService.isReady) {
213
-
214
- // Use terminal's built-in paste functionality
215
- // This simulates typing each character through the input handler
216
- if ((xtermService as any).inputHandler) {
217
- // Process each character through the input handler
218
- for (const char of clipboardText) {
219
- // Skip newlines - let user decide when to execute
220
- if (char !== '\n' && char !== '\r') {
221
- (xtermService as any).inputHandler(char);
222
- }
223
- }
224
- }
225
-
226
- // Show brief visual feedback
227
- showPasteFeedback();
204
+ const text = await navigator.clipboard.readText();
205
+ if (text?.trim()) {
206
+ xtermService.pasteText(text);
228
207
  }
229
- } catch {
230
- // paste not supported
231
- }
208
+ } catch { /* clipboard not available */ }
232
209
  }
233
210
  };
234
211
 
235
- // Add right-click event listener to terminal container
236
- terminalContainer.addEventListener('contextmenu', handleRightClick);
237
-
212
+ terminalContainer.addEventListener('contextmenu', handleContextMenu);
213
+
238
214
  return () => {
239
- terminalContainer?.removeEventListener('contextmenu', handleRightClick);
215
+ terminalContainer?.removeEventListener('contextmenu', handleContextMenu);
240
216
  };
241
217
  }
242
218
 
243
- // Show brief visual feedback for copy action
244
- function showCopyFeedback() {
245
- if (!terminalContainer) return;
246
-
247
- // Create temporary feedback element
248
- const feedback = document.createElement('div');
249
- feedback.textContent = 'Copied!';
250
- feedback.style.cssText = `
251
- position: absolute;
252
- top: 10px;
253
- right: 10px;
254
- background: rgb(34 197 94 / 0.9);
255
- color: white;
256
- padding: 4px 8px;
257
- border-radius: 4px;
258
- font-size: 12px;
259
- font-family: system-ui, sans-serif;
260
- z-index: 1000;
261
- pointer-events: none;
262
- `;
263
-
264
- terminalContainer.style.position = 'relative';
265
- terminalContainer.appendChild(feedback);
266
-
267
- // Remove feedback after 1 second
268
- setTimeout(() => {
269
- if (feedback.parentNode) {
270
- feedback.parentNode.removeChild(feedback);
271
- }
272
- }, 1000);
273
- }
274
-
275
- // Show brief visual feedback for paste action
276
- function showPasteFeedback() {
277
- if (!terminalContainer) return;
278
-
279
- // Create temporary feedback element
280
- const feedback = document.createElement('div');
281
- feedback.textContent = 'Pasted!';
282
- feedback.style.cssText = `
283
- position: absolute;
284
- top: 10px;
285
- right: 10px;
286
- background: rgba(59, 130, 246, 0.9);
287
- color: white;
288
- padding: 4px 8px;
289
- border-radius: 4px;
290
- font-size: 12px;
291
- font-family: system-ui, sans-serif;
292
- z-index: 1000;
293
- pointer-events: none;
294
- `;
295
-
296
- terminalContainer.style.position = 'relative';
297
- terminalContainer.appendChild(feedback);
298
-
299
- // Remove feedback after 1 second
300
- setTimeout(() => {
301
- if (feedback.parentNode) {
302
- feedback.parentNode.removeChild(feedback);
303
- }
304
- }, 1000);
305
- }
306
-
307
219
  // Handle theme changes
308
220
  function setupThemeHandling() {
309
221
  xtermService.updateTheme();
@@ -404,7 +316,9 @@
404
316
  $effect(() => {
405
317
  const size = settings.fontSize;
406
318
  if (isInitialized) {
407
- xtermService.updateFontSize(size, session?.id);
319
+ const fontSize = Math.round(size * 0.9);
320
+ const lineHeight = Math.round(size * 0.9);
321
+ xtermService.updateFontSize(fontSize, lineHeight, session?.id);
408
322
  }
409
323
  });
410
324
 
@@ -656,12 +570,12 @@
656
570
 
657
571
  const cleanupResize = setupResizeHandling();
658
572
  const cleanupTheme = setupThemeHandling();
659
- const cleanupRightClick = setupRightClickCopy();
573
+ const cleanupClipboard = setupClipboardHandling();
660
574
 
661
575
  return () => {
662
576
  cleanupResize();
663
577
  cleanupTheme();
664
- cleanupRightClick?.();
578
+ cleanupClipboard?.();
665
579
  };
666
580
  });
667
581
 
@@ -711,6 +625,11 @@
711
625
  export function clearSelection() {
712
626
  xtermService.clearSelection();
713
627
  }
628
+
629
+ export function pasteText(text: string) {
630
+ xtermService.pasteText(text);
631
+ }
632
+
714
633
  </script>
715
634
 
716
635
  <!-- Pure xterm.js terminal container -->
@@ -4,7 +4,7 @@
4
4
  * Centralized xterm.js configuration and utilities
5
5
  */
6
6
 
7
- import type { ITerminalOptions } from 'xterm';
7
+ import type { ITerminalOptions } from '@xterm/xterm';
8
8
 
9
9
  // Terminal theme configuration
10
10
  export const terminalConfig: ITerminalOptions = {
@@ -40,7 +40,7 @@ export const terminalConfig: ITerminalOptions = {
40
40
  convertEol: true,
41
41
  scrollback: 1000,
42
42
  tabStopWidth: 4,
43
- allowProposedApi: false,
43
+ allowProposedApi: true,
44
44
  altClickMovesCursor: true,
45
45
  disableStdin: false, // ✅ ENABLED for interactive PTY mode - stdin forwards to backend
46
46
  allowTransparency: false
@@ -23,6 +23,7 @@ export interface XTermMethods {
23
23
  writeData(data: string): void;
24
24
  getSelectedText(): string;
25
25
  clearSelection(): void;
26
+ pasteText(text: string): void;
26
27
  }
27
28
 
28
29
  export interface TerminalSize {
@@ -6,9 +6,12 @@
6
6
  */
7
7
 
8
8
  import { browser } from '$frontend/lib/app-environment';
9
- import type { Terminal } from 'xterm';
9
+ import type { Terminal } from '@xterm/xterm';
10
10
  import type { FitAddon } from '@xterm/addon-fit';
11
11
  import type { WebLinksAddon } from '@xterm/addon-web-links';
12
+ import type { ClipboardAddon } from '@xterm/addon-clipboard';
13
+ import type { Unicode11Addon } from '@xterm/addon-unicode11';
14
+ import type { LigaturesAddon } from '@xterm/addon-ligatures';
12
15
  import type { TerminalLine } from '$shared/types/terminal';
13
16
  import { terminalConfig } from './terminal-config';
14
17
  import { debug } from '$shared/utils/logger';
@@ -18,12 +21,16 @@ export class XTermService {
18
21
  public terminal: Terminal | null = null;
19
22
  public fitAddon: FitAddon | null = null;
20
23
  public webLinksAddon: WebLinksAddon | null = null;
24
+ public clipboardAddon: ClipboardAddon | null = null;
25
+ private unicode11Addon: Unicode11Addon | null = null;
26
+ private ligaturesAddon: LigaturesAddon | null = null;
21
27
  public isInitialized = false;
22
28
  public isReady = false;
23
29
 
24
30
  private sessionId: string | null = null;
25
31
  private resizeTimeout: ReturnType<typeof setTimeout> | null = null;
26
32
  private inputDisposable: any = null;
33
+ private lastSentDims: { cols: number; rows: number } | null = null;
27
34
 
28
35
  constructor() {
29
36
  // Service is stateless by design
@@ -49,27 +56,39 @@ export class XTermService {
49
56
  this.terminal = null;
50
57
  this.fitAddon = null;
51
58
  this.webLinksAddon = null;
59
+ this.clipboardAddon = null;
60
+ this.unicode11Addon = null;
61
+ this.ligaturesAddon = null;
52
62
  }
53
63
 
54
64
  try {
55
65
  debug.log('terminal', '🚀 Initializing XTerm...');
56
66
 
57
67
  // Dynamic import xterm classes
58
- const [{ Terminal }, { FitAddon }, { WebLinksAddon }] = await Promise.all([
59
- import('xterm'),
68
+ const [{ Terminal }, { FitAddon }, { WebLinksAddon }, { ClipboardAddon }, { Unicode11Addon }] = await Promise.all([
69
+ import('@xterm/xterm'),
60
70
  import('@xterm/addon-fit'),
61
- import('@xterm/addon-web-links')
71
+ import('@xterm/addon-web-links'),
72
+ import('@xterm/addon-clipboard'),
73
+ import('@xterm/addon-unicode11')
62
74
  ]);
63
75
 
64
76
  // Create terminal instance
65
77
  this.terminal = new Terminal(terminalConfig);
66
78
 
67
- // Create and load addons
79
+ // Create and load core addons
68
80
  this.fitAddon = new FitAddon();
69
81
  this.webLinksAddon = new WebLinksAddon();
82
+ this.clipboardAddon = new ClipboardAddon();
83
+ this.unicode11Addon = new Unicode11Addon();
70
84
 
71
85
  this.terminal.loadAddon(this.fitAddon);
72
86
  this.terminal.loadAddon(this.webLinksAddon);
87
+ this.terminal.loadAddon(this.clipboardAddon);
88
+ this.terminal.loadAddon(this.unicode11Addon);
89
+
90
+ // Enable Unicode 11 for better character width support
91
+ this.terminal.unicode.activeVersion = '11';
73
92
 
74
93
  // Open terminal in container
75
94
  this.terminal.open(container);
@@ -82,6 +101,16 @@ export class XTermService {
82
101
  debug.log('terminal', '⚠️ Initial fit failed (container may have zero dimensions), will retry on resize');
83
102
  }
84
103
 
104
+ // Try ligatures addon for font ligature rendering (non-critical)
105
+ try {
106
+ const { LigaturesAddon } = await import('@xterm/addon-ligatures');
107
+ this.ligaturesAddon = new LigaturesAddon();
108
+ this.terminal.loadAddon(this.ligaturesAddon);
109
+ debug.log('terminal', '🔤 Font ligatures enabled');
110
+ } catch {
111
+ debug.log('terminal', '⚠️ Ligatures addon not available');
112
+ }
113
+
85
114
  this.isInitialized = true;
86
115
  this.isReady = true;
87
116
 
@@ -94,6 +123,9 @@ export class XTermService {
94
123
  }
95
124
  this.fitAddon = null;
96
125
  this.webLinksAddon = null;
126
+ this.clipboardAddon = null;
127
+ this.unicode11Addon = null;
128
+ this.ligaturesAddon = null;
97
129
  debug.error('terminal', '❌ Failed to initialize XTerm:', error);
98
130
  }
99
131
  }
@@ -180,11 +212,12 @@ export class XTermService {
180
212
  }
181
213
 
182
214
  /**
183
- * Update terminal font size and refit to container
215
+ * Update terminal font size, line height, and refit to container
184
216
  */
185
- updateFontSize(size: number, sessionId?: string): void {
217
+ updateFontSize(size: number, lineHeight: number, sessionId?: string): void {
186
218
  if (!this.terminal) return;
187
219
  this.terminal.options.fontSize = size;
220
+ this.terminal.options.lineHeight = lineHeight / size;
188
221
  this.fit(sessionId);
189
222
  }
190
223
 
@@ -262,6 +295,12 @@ export class XTermService {
262
295
  const dims = this.fitAddon.proposeDimensions();
263
296
 
264
297
  if (dims && sessionId) {
298
+ // Skip if dimensions haven't changed
299
+ if (this.lastSentDims && this.lastSentDims.cols === dims.cols && this.lastSentDims.rows === dims.rows) {
300
+ return;
301
+ }
302
+ this.lastSentDims = { cols: dims.cols, rows: dims.rows };
303
+
265
304
  // Notify backend of new terminal size via WebSocket HTTP
266
305
  debug.log('terminal', `🔧 Syncing terminal size: ${dims.cols}x${dims.rows}`);
267
306
  ws.http('terminal:resize', {
@@ -293,20 +332,11 @@ export class XTermService {
293
332
  scrollToBottomIfNearEnd(): void {
294
333
  if (!this.terminal) return;
295
334
 
296
- const viewport = (this.terminal as any)._core?.viewport;
297
- if (!viewport) {
298
- this.scrollToBottom();
299
- return;
300
- }
301
-
302
- const scrollTop = viewport._viewportElement.scrollTop;
303
- const scrollHeight = viewport._viewportElement.scrollHeight;
304
- const clientHeight = viewport._viewportElement.clientHeight;
335
+ const buffer = this.terminal.buffer.active;
336
+ const isNearBottom = buffer.viewportY >= buffer.baseY - 3;
305
337
 
306
- // If within 3 lines of bottom, scroll to bottom
307
- const threshold = this.terminal.rows * 3;
308
- if (scrollHeight - scrollTop - clientHeight < threshold) {
309
- this.scrollToBottom();
338
+ if (isNearBottom) {
339
+ this.terminal.scrollToBottom();
310
340
  }
311
341
  }
312
342
 
@@ -361,6 +391,21 @@ export class XTermService {
361
391
  };
362
392
  }
363
393
 
394
+ /**
395
+ * Paste text by sending to PTY via WebSocket
396
+ */
397
+ pasteText(text: string): void {
398
+ if (!this.sessionId || !text) return;
399
+ try {
400
+ ws.emit('terminal:input', {
401
+ sessionId: this.sessionId,
402
+ data: text
403
+ });
404
+ } catch (error) {
405
+ debug.error('terminal', '❌ Error pasting text:', error);
406
+ }
407
+ }
408
+
364
409
  /**
365
410
  * Cleanup terminal resources
366
411
  */
@@ -382,6 +427,10 @@ export class XTermService {
382
427
 
383
428
  this.fitAddon = null;
384
429
  this.webLinksAddon = null;
430
+ this.clipboardAddon = null;
431
+ this.unicode11Addon = null;
432
+ this.ligaturesAddon = null;
433
+ this.lastSentDims = null;
385
434
  this.isInitialized = false;
386
435
  this.isReady = false;
387
436
  }
@@ -420,9 +420,9 @@
420
420
 
421
421
  <div class="relative flex flex-col h-full overflow-hidden">
422
422
  <!-- Modern Header -->
423
- <div class="px-5 py-3 border-b border-slate-200 dark:border-slate-700">
423
+ <div class="px-5 py-2.5 border-b border-slate-200 dark:border-slate-700">
424
424
  <div class="flex items-start justify-between gap-2">
425
- <div class="flex-1 min-w-0">
425
+ <div class="flex-1 min-w-0" title={projectState.currentProject?.path}>
426
426
  <h3 class="text-sm font-bold text-slate-900 dark:text-slate-100">
427
427
  {projectState.currentProject?.name}
428
428
  </h3>
@@ -466,9 +466,8 @@
466
466
  <div class="relative flex border-b border-slate-200 dark:border-slate-700">
467
467
  <button
468
468
  onclick={() => { searchVisible = false; }}
469
- class="relative flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors {!searchVisible ? 'text-violet-600 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300'}"
469
+ class="relative flex-1 flex items-center justify-center px-3 py-2 text-xs font-medium transition-colors {!searchVisible ? 'text-violet-600 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300'}"
470
470
  >
471
- <Icon name="lucide:folder" class="w-3.5 h-3.5" />
472
471
  Explorer
473
472
  {#if !searchVisible}
474
473
  <span class="absolute bottom-0 inset-x-0 h-px bg-violet-600 dark:bg-violet-400"></span>
@@ -476,9 +475,8 @@
476
475
  </button>
477
476
  <button
478
477
  onclick={switchToSearch}
479
- class="relative flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors {searchVisible ? 'text-violet-600 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300'}"
478
+ class="relative flex-1 flex items-center justify-center px-3 py-2 text-xs font-medium transition-colors {searchVisible ? 'text-violet-600 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300'}"
480
479
  >
481
- <Icon name="lucide:search" class="w-3.5 h-3.5" />
482
480
  Search
483
481
  {#if searchVisible}
484
482
  <span class="absolute bottom-0 inset-x-0 h-px bg-violet-600 dark:bg-violet-400"></span>