clay-server 2.9.0 → 2.9.2

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.
package/lib/public/app.js CHANGED
@@ -542,7 +542,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
542
542
  }
543
543
  renderHomeHub(cachedProjects);
544
544
  startTipRotation();
545
- history.pushState(null, "", "/");
545
+ if (document.documentElement.classList.contains("pwa-standalone")) {
546
+ history.replaceState(null, "", "/");
547
+ } else {
548
+ history.pushState(null, "", "/");
549
+ }
546
550
  // Update icon strip active state
547
551
  var homeIcon = document.querySelector(".icon-strip-home");
548
552
  if (homeIcon) homeIcon.classList.add("active");
@@ -557,7 +561,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
557
561
  hubCloseBtn.addEventListener("click", function () {
558
562
  hideHomeHub();
559
563
  if (currentSlug) {
560
- history.pushState(null, "", "/p/" + currentSlug + "/");
564
+ if (document.documentElement.classList.contains("pwa-standalone")) {
565
+ history.replaceState(null, "", "/p/" + currentSlug + "/");
566
+ } else {
567
+ history.pushState(null, "", "/p/" + currentSlug + "/");
568
+ }
561
569
  // Restore icon strip active state
562
570
  var homeIcon = document.querySelector(".icon-strip-home");
563
571
  if (homeIcon) homeIcon.classList.remove("active");
@@ -1830,6 +1838,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1830
1838
 
1831
1839
  // --- DOM: Messages ---
1832
1840
  function addUserMessage(text, images, pastes) {
1841
+ if (!text && (!images || images.length === 0) && (!pastes || pastes.length === 0)) return;
1833
1842
  var div = document.createElement("div");
1834
1843
  div.className = "msg-user";
1835
1844
  div.dataset.turn = ++turnCounter;
@@ -2360,7 +2369,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2360
2369
  currentSlug = slug;
2361
2370
  basePath = "/p/" + slug + "/";
2362
2371
  wsPath = "/p/" + slug + "/ws";
2363
- history.pushState(null, "", basePath);
2372
+ if (document.documentElement.classList.contains("pwa-standalone")) {
2373
+ history.replaceState(null, "", basePath);
2374
+ } else {
2375
+ history.pushState(null, "", basePath);
2376
+ }
2364
2377
  resetClientState();
2365
2378
  connect();
2366
2379
  }
@@ -2555,10 +2568,6 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2555
2568
  var vEl = $("footer-version");
2556
2569
  if (vEl) vEl.textContent = "v" + msg.version;
2557
2570
  }
2558
- if (msg.debug) {
2559
- var debugWrap = $("debug-menu-wrap");
2560
- if (debugWrap) debugWrap.classList.remove("hidden");
2561
- }
2562
2571
  if (msg.lanHost) window.__lanHost = msg.lanHost;
2563
2572
  if (msg.dangerouslySkipPermissions) {
2564
2573
  skipPermsEnabled = true;
@@ -2650,9 +2659,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2650
2659
  // Non-multi-user mode: simple count in topbar
2651
2660
  if (!msg.users) {
2652
2661
  var countEl = document.getElementById("client-count");
2653
- if (countEl) {
2662
+ var countTextEl = document.getElementById("client-count-text");
2663
+ if (countEl && countTextEl) {
2654
2664
  if (msg.count > 1) {
2655
- countEl.textContent = msg.count;
2665
+ countTextEl.textContent = msg.count + " connected";
2656
2666
  countEl.classList.remove("hidden");
2657
2667
  } else {
2658
2668
  countEl.classList.add("hidden");
@@ -2667,24 +2677,13 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2667
2677
 
2668
2678
  case "skill_installed":
2669
2679
  handleSkillInstalled(msg);
2670
- // Advance ralph wizard if we were installing clay-ralph
2671
- if (msg.skill === "clay-ralph" && ralphSkillInstalling) {
2672
- ralphSkillInstalling = false;
2673
- ralphSkillInstalled = true;
2674
- if (msg.success) {
2675
- wizardStep = 2;
2676
- updateWizardStep();
2677
- } else {
2678
- var rNextBtn = document.getElementById("ralph-wizard-next");
2679
- if (rNextBtn) { rNextBtn.disabled = false; rNextBtn.textContent = "Get Started"; }
2680
- var rStatusEl = document.getElementById("ralph-install-status");
2681
- if (rStatusEl) { rStatusEl.innerHTML = "Failed to install skill. Try again."; }
2682
- }
2683
- }
2680
+ if (msg.success) knownInstalledSkills[msg.skill] = true;
2681
+ handleSkillInstallWs(msg);
2684
2682
  break;
2685
2683
 
2686
2684
  case "skill_uninstalled":
2687
2685
  handleSkillUninstalled(msg);
2686
+ if (msg.success) delete knownInstalledSkills[msg.skill];
2688
2687
  break;
2689
2688
 
2690
2689
  case "loop_registry_updated":
@@ -3797,35 +3796,191 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3797
3796
  }
3798
3797
  }
3799
3798
 
3800
- // --- Ralph Wizard ---
3801
- var ralphSkillInstalled = false;
3802
- var ralphSkillInstalling = false;
3799
+ // --- Skill install dialog (generic) ---
3800
+ var skillInstallModal = document.getElementById("skill-install-modal");
3801
+ var skillInstallTitle = document.getElementById("skill-install-title");
3802
+ var skillInstallReason = document.getElementById("skill-install-reason");
3803
+ var skillInstallList = document.getElementById("skill-install-list");
3804
+ var skillInstallOk = document.getElementById("skill-install-ok");
3805
+ var skillInstallCancel = document.getElementById("skill-install-cancel");
3806
+ var skillInstallStatus = document.getElementById("skill-install-status");
3807
+
3808
+ var pendingSkillInstalls = []; // [{ name, url, scope, installed }]
3809
+ var skillInstallCallback = null;
3810
+ var skillInstalling = false;
3811
+ var knownInstalledSkills = {}; // client-side cache of installed skills
3812
+
3813
+ function renderSkillInstallDialog(opts, missing) {
3814
+ skillInstallTitle.textContent = opts.title || "Skill Installation Required";
3815
+ skillInstallReason.textContent = opts.reason || "";
3816
+ skillInstallList.innerHTML = "";
3817
+ for (var i = 0; i < missing.length; i++) {
3818
+ var s = missing[i];
3819
+ var item = document.createElement("div");
3820
+ item.className = "skill-install-item";
3821
+ item.setAttribute("data-skill", s.name);
3822
+ item.innerHTML = '<span class="skill-icon">&#x1f9e9;</span>' +
3823
+ '<div class="skill-info">' +
3824
+ '<span class="skill-name">' + escapeHtml(s.name) + '</span>' +
3825
+ '<span class="skill-scope">' + escapeHtml(s.scope || "global") + '</span>' +
3826
+ '</div>' +
3827
+ '<span class="skill-status"></span>';
3828
+ skillInstallList.appendChild(item);
3829
+ }
3830
+ skillInstallStatus.classList.add("hidden");
3831
+ skillInstallStatus.innerHTML = "";
3832
+ skillInstallOk.disabled = false;
3833
+ skillInstallOk.textContent = "Install";
3834
+ skillInstallOk.className = "confirm-btn confirm-delete";
3835
+ skillInstallModal.classList.remove("hidden");
3836
+ }
3837
+
3838
+ function hideSkillInstallModal() {
3839
+ skillInstallModal.classList.add("hidden");
3840
+ skillInstallCallback = null;
3841
+ pendingSkillInstalls = [];
3842
+ skillInstalling = false;
3843
+ skillInstallDone = false;
3844
+ }
3845
+
3846
+ skillInstallCancel.addEventListener("click", hideSkillInstallModal);
3847
+ skillInstallModal.querySelector(".confirm-backdrop").addEventListener("click", hideSkillInstallModal);
3848
+
3849
+ var skillInstallDone = false;
3850
+
3851
+ skillInstallOk.addEventListener("click", function () {
3852
+ // "Proceed" state — all done, close and invoke callback
3853
+ if (skillInstallDone) {
3854
+ var proceedCb = skillInstallCallback;
3855
+ skillInstallCallback = null;
3856
+ hideSkillInstallModal();
3857
+ if (proceedCb) proceedCb();
3858
+ return;
3859
+ }
3860
+ if (skillInstalling) return;
3861
+ skillInstalling = true;
3862
+ skillInstallOk.disabled = true;
3863
+ skillInstallOk.textContent = "Installing...";
3864
+
3865
+ var total = 0;
3866
+ for (var i = 0; i < pendingSkillInstalls.length; i++) {
3867
+ if (!pendingSkillInstalls[i].installed) total++;
3868
+ }
3869
+ skillInstallStatus.classList.remove("hidden");
3870
+ updateSkillInstallProgress(0, total);
3871
+
3872
+ for (var j = 0; j < pendingSkillInstalls.length; j++) {
3873
+ var s = pendingSkillInstalls[j];
3874
+ if (s.installed) continue;
3875
+ fetch(basePath + "api/install-skill", {
3876
+ method: "POST",
3877
+ headers: { "Content-Type": "application/json" },
3878
+ body: JSON.stringify({ url: s.url, skill: s.name, scope: s.scope || "global" }),
3879
+ }).catch(function () {});
3880
+ }
3881
+ });
3882
+
3883
+ function updateSkillInstallProgress(done, total) {
3884
+ skillInstallStatus.innerHTML = '<div class="skills-spinner small"></div> Installing skills... (' + done + '/' + total + ')';
3885
+ }
3886
+
3887
+ function updateSkillListItems() {
3888
+ var items = skillInstallList.querySelectorAll(".skill-install-item");
3889
+ for (var i = 0; i < items.length; i++) {
3890
+ var name = items[i].getAttribute("data-skill");
3891
+ for (var j = 0; j < pendingSkillInstalls.length; j++) {
3892
+ if (pendingSkillInstalls[j].name === name) {
3893
+ var statusEl = items[i].querySelector(".skill-status");
3894
+ if (pendingSkillInstalls[j].installed) {
3895
+ if (statusEl) {
3896
+ statusEl.innerHTML = '<span class="skill-status-ok">' + iconHtml("circle-check") + '</span>';
3897
+ refreshIcons();
3898
+ }
3899
+ }
3900
+ break;
3901
+ }
3902
+ }
3903
+ }
3904
+ }
3905
+
3906
+ function handleSkillInstallWs(msg) {
3907
+ if (!skillInstalling || pendingSkillInstalls.length === 0) return;
3908
+ for (var i = 0; i < pendingSkillInstalls.length; i++) {
3909
+ if (pendingSkillInstalls[i].name === msg.skill) {
3910
+ if (msg.success) {
3911
+ pendingSkillInstalls[i].installed = true;
3912
+ knownInstalledSkills[msg.skill] = true;
3913
+ } else {
3914
+ skillInstalling = false;
3915
+ skillInstallOk.disabled = false;
3916
+ skillInstallOk.textContent = "Install";
3917
+ skillInstallStatus.innerHTML = "Failed to install " + escapeHtml(msg.skill) + ". Try again.";
3918
+ updateSkillListItems();
3919
+ return;
3920
+ }
3921
+ }
3922
+ }
3923
+
3924
+ var doneCount = 0;
3925
+ var totalCount = pendingSkillInstalls.length;
3926
+ for (var k = 0; k < pendingSkillInstalls.length; k++) {
3927
+ if (pendingSkillInstalls[k].installed) doneCount++;
3928
+ }
3929
+ updateSkillListItems();
3930
+ updateSkillInstallProgress(doneCount, totalCount);
3931
+
3932
+ if (doneCount === totalCount) {
3933
+ skillInstallDone = true;
3934
+ skillInstallStatus.innerHTML = '<span class="skill-status-ok">' + iconHtml("circle-check") + '</span> All skills installed successfully.';
3935
+ refreshIcons();
3936
+ skillInstallOk.disabled = false;
3937
+ skillInstallOk.textContent = "Proceed";
3938
+ skillInstallOk.className = "confirm-btn confirm-proceed";
3939
+ }
3940
+ }
3803
3941
 
3804
- function checkRalphSkillInstalled(cb) {
3942
+ function requireSkills(opts, cb) {
3805
3943
  fetch(basePath + "api/installed-skills")
3806
3944
  .then(function (res) { return res.json(); })
3807
3945
  .then(function (data) {
3808
3946
  var installed = data.installed || {};
3809
- ralphSkillInstalled = !!installed["clay-ralph"];
3810
- cb(ralphSkillInstalled);
3947
+ var missing = [];
3948
+ for (var i = 0; i < opts.skills.length; i++) {
3949
+ var sName = opts.skills[i].name;
3950
+ if (!installed[sName] && !knownInstalledSkills[sName]) {
3951
+ missing.push({ name: sName, url: opts.skills[i].url, scope: opts.skills[i].scope || "global", installed: false });
3952
+ }
3953
+ }
3954
+ if (missing.length === 0) { cb(); return; }
3955
+ pendingSkillInstalls = missing;
3956
+ skillInstallCallback = cb;
3957
+ renderSkillInstallDialog(opts, missing);
3811
3958
  })
3812
- .catch(function () { cb(false); });
3959
+ .catch(function () { cb(); });
3813
3960
  }
3814
3961
 
3962
+ function requireClayRalph(cb) {
3963
+ requireSkills({
3964
+ title: "Skill Installation Required",
3965
+ reason: "This feature requires the following skill to be installed.",
3966
+ skills: [{ name: "clay-ralph", url: "https://github.com/chadbyte/clay-ralph", scope: "global" }]
3967
+ }, cb);
3968
+ }
3969
+
3970
+ // --- Ralph Wizard ---
3971
+
3815
3972
  function openRalphWizard() {
3816
- wizardData = { name: "", task: "", maxIterations: 3 };
3817
- ralphSkillInstalling = false;
3818
- var el = document.getElementById("ralph-wizard");
3819
- if (!el) return;
3973
+ requireClayRalph(function () {
3974
+ wizardData = { name: "", task: "", maxIterations: 3 };
3975
+ var el = document.getElementById("ralph-wizard");
3976
+ if (!el) return;
3820
3977
 
3821
- var taskEl = document.getElementById("ralph-task");
3822
- if (taskEl) taskEl.value = "";
3823
- var iterEl = document.getElementById("ralph-max-iterations");
3824
- if (iterEl) iterEl.value = "25";
3978
+ var taskEl = document.getElementById("ralph-task");
3979
+ if (taskEl) taskEl.value = "";
3980
+ var iterEl = document.getElementById("ralph-max-iterations");
3981
+ if (iterEl) iterEl.value = "25";
3825
3982
 
3826
- // Check if clay-ralph skill is installed — skip onboarding if so
3827
- checkRalphSkillInstalled(function (installed) {
3828
- wizardStep = installed ? 2 : 1;
3983
+ wizardStep = 1;
3829
3984
  el.classList.remove("hidden");
3830
3985
  var statusEl = document.getElementById("ralph-install-status");
3831
3986
  if (statusEl) { statusEl.classList.add("hidden"); statusEl.innerHTML = ""; }
@@ -3928,38 +4083,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3928
4083
  function wizardNext() {
3929
4084
  collectWizardData();
3930
4085
 
3931
- // Step 1: install clay-ralph skill if needed, otherwise just advance
3932
4086
  if (wizardStep === 1) {
3933
- if (ralphSkillInstalled) {
3934
- wizardStep++;
3935
- updateWizardStep();
3936
- return;
3937
- }
3938
- if (ralphSkillInstalling) return;
3939
- ralphSkillInstalling = true;
3940
- var nextBtn = document.getElementById("ralph-wizard-next");
3941
- if (nextBtn) {
3942
- nextBtn.disabled = true;
3943
- nextBtn.textContent = "Installing...";
3944
- }
3945
- var statusEl = document.getElementById("ralph-install-status");
3946
- if (statusEl) {
3947
- statusEl.classList.remove("hidden");
3948
- statusEl.innerHTML = '<div class="skills-spinner small"></div> Installing clay-ralph skill...';
3949
- }
3950
- fetch(basePath + "api/install-skill", {
3951
- method: "POST",
3952
- headers: { "Content-Type": "application/json" },
3953
- body: JSON.stringify({ url: "https://github.com/chadbyte/clay-ralph", skill: "clay-ralph", scope: "global" }),
3954
- })
3955
- .then(function () {
3956
- // Wait for skill_installed websocket message to advance
3957
- })
3958
- .catch(function () {
3959
- ralphSkillInstalling = false;
3960
- if (nextBtn) { nextBtn.disabled = false; nextBtn.textContent = "Get Started"; }
3961
- if (statusEl) { statusEl.innerHTML = "Failed to install skill. Try again."; }
3962
- });
4087
+ wizardStep++;
4088
+ updateWizardStep();
3963
4089
  return;
3964
4090
  }
3965
4091
 
@@ -4299,6 +4425,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
4299
4425
  basePath: basePath,
4300
4426
  currentSlug: currentSlug,
4301
4427
  openRalphWizard: function () { openRalphWizard(); },
4428
+ requireClayRalph: function (cb) { requireClayRalph(cb); },
4302
4429
  getProjects: function () { return cachedProjects; },
4303
4430
  });
4304
4431
 
@@ -4607,6 +4734,57 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
4607
4734
  closeAddProjectModal();
4608
4735
  });
4609
4736
 
4737
+ // --- PWA install prompt ---
4738
+ (function () {
4739
+ var installPill = document.getElementById("pwa-install-pill");
4740
+ var modal = document.getElementById("pwa-install-modal");
4741
+ var confirmBtn = document.getElementById("pwa-modal-confirm");
4742
+ var cancelBtn = document.getElementById("pwa-modal-cancel");
4743
+ if (!installPill || !modal) return;
4744
+
4745
+ // Already standalone — never show
4746
+ if (document.documentElement.classList.contains("pwa-standalone")) return;
4747
+
4748
+ // Show pill on mobile browsers (the primary target for PWA install)
4749
+ var isMobile = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
4750
+ (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
4751
+ if (isMobile) {
4752
+ installPill.classList.remove("hidden");
4753
+ }
4754
+
4755
+ // Also show on desktop if beforeinstallprompt fires
4756
+ window.addEventListener("beforeinstallprompt", function (e) {
4757
+ e.preventDefault();
4758
+ installPill.classList.remove("hidden");
4759
+ });
4760
+
4761
+ function openModal() {
4762
+ modal.classList.remove("hidden");
4763
+ lucide.createIcons({ nodes: [modal] });
4764
+ }
4765
+
4766
+ function closeModal() {
4767
+ modal.classList.add("hidden");
4768
+ }
4769
+
4770
+ installPill.addEventListener("click", openModal);
4771
+ cancelBtn.addEventListener("click", closeModal);
4772
+ modal.querySelector(".pwa-modal-backdrop").addEventListener("click", closeModal);
4773
+
4774
+ confirmBtn.addEventListener("click", function () {
4775
+ // Redirect to HTTP setup page (port + 1)
4776
+ var port = parseInt(location.port, 10) || (location.protocol === "https:" ? 443 : 80);
4777
+ var setupUrl = "http://" + location.hostname + ":" + (port + 1) + "/setup";
4778
+ location.href = setupUrl;
4779
+ });
4780
+
4781
+ // Hide after install
4782
+ window.addEventListener("appinstalled", function () {
4783
+ installPill.classList.add("hidden");
4784
+ closeModal();
4785
+ });
4786
+ })();
4787
+
4610
4788
  // --- Init ---
4611
4789
  lucide.createIcons();
4612
4790
  connect();
@@ -158,6 +158,8 @@ img.emoji {
158
158
  --content-width: 760px;
159
159
  }
160
160
 
161
+ /* PWA standalone: home indicator safe area still applies on notched devices */
162
+
161
163
  ::selection {
162
164
  background: rgba(80, 250, 123, 0.25);
163
165
  }
@@ -170,6 +172,7 @@ html, body {
170
172
  font-size: 15px;
171
173
  line-height: 1.6;
172
174
  overflow: hidden;
175
+ overscroll-behavior: none;
173
176
  -webkit-font-smoothing: antialiased;
174
177
  -moz-osx-font-smoothing: grayscale;
175
178
  }
@@ -677,6 +677,7 @@
677
677
  color: var(--text);
678
678
  white-space: pre-wrap;
679
679
  word-break: break-word;
680
+ tab-size: 2;
680
681
  }
681
682
 
682
683
  .file-viewer-body pre code {
@@ -700,6 +701,7 @@
700
701
  line-height: 1.55;
701
702
  white-space: pre;
702
703
  word-break: normal;
704
+ tab-size: 2;
703
705
  }
704
706
 
705
707
  .file-viewer-gutter {
@@ -890,7 +892,12 @@
890
892
  @media (max-width: 1023px) {
891
893
  #terminal-container {
892
894
  position: fixed;
893
- inset: 0;
895
+ top: 0;
896
+ left: 0;
897
+ right: 0;
898
+ bottom: 0;
899
+ padding-top: var(--safe-top);
900
+ padding-bottom: var(--safe-bottom);
894
901
  background: var(--bg);
895
902
  z-index: 300;
896
903
  display: flex;
@@ -431,7 +431,7 @@
431
431
  /* --- Mobile --- */
432
432
  @media (max-width: 768px) {
433
433
  #home-hub {
434
- padding: 32px 16px 32px;
434
+ padding: 32px 16px calc(80px + var(--safe-bottom, 0px));
435
435
  }
436
436
  .hub-greeting h1 {
437
437
  font-size: 22px;
@@ -559,6 +559,10 @@
559
559
  ========================================================================== */
560
560
 
561
561
  @media (max-width: 768px) {
562
+ #layout-body {
563
+ background: var(--bg);
564
+ }
565
+
562
566
  #main-area {
563
567
  overflow: visible;
564
568
  border-radius: 0;
@@ -142,46 +142,6 @@
142
142
  flex-shrink: 0;
143
143
  }
144
144
 
145
- /* --- Debug menu --- */
146
- #debug-menu-wrap {
147
- position: relative;
148
- }
149
-
150
- #debug-menu-wrap.hidden { display: none; }
151
-
152
- #debug-btn {
153
- display: flex;
154
- align-items: center;
155
- justify-content: center;
156
- background: none;
157
- border: 1px solid transparent;
158
- border-radius: 8px;
159
- color: var(--error);
160
- cursor: pointer;
161
- padding: 4px;
162
- opacity: 0.6;
163
- transition: opacity 0.15s, background 0.15s, border-color 0.15s;
164
- }
165
-
166
- #debug-btn .lucide { width: 15px; height: 15px; }
167
- #debug-btn:hover { opacity: 1; background: var(--error-8); border-color: var(--error-25); }
168
- #debug-btn.active { opacity: 1; background: var(--error-12); border-color: var(--error-25); }
169
-
170
- #debug-menu {
171
- position: absolute;
172
- top: calc(100% + 6px);
173
- right: 0;
174
- background: var(--bg-alt);
175
- border: 1px solid var(--border);
176
- border-radius: 10px;
177
- padding: 8px 0;
178
- min-width: 200px;
179
- box-shadow: 0 4px 16px rgba(var(--shadow-rgb), 0.4);
180
- z-index: 200;
181
- }
182
-
183
- #debug-menu.hidden { display: none; }
184
-
185
145
  /* --- Terminal toggle button (mobile only) --- */
186
146
  #footer-status,
187
147
  #terminal-toggle-btn {
@@ -447,13 +407,10 @@
447
407
  .notif-action.copied { color: var(--success); }
448
408
 
449
409
  #client-count {
450
- display: inline-flex;
451
- align-items: center;
452
410
  cursor: default;
411
+ font-size: 11px;
453
412
  }
454
413
 
455
- #client-count.hidden { display: none; }
456
-
457
414
  .client-avatar {
458
415
  width: 22px;
459
416
  height: 22px;
@@ -6,6 +6,7 @@
6
6
  flex: 1;
7
7
  overflow-y: auto;
8
8
  -webkit-overflow-scrolling: touch;
9
+ overscroll-behavior-y: contain;
9
10
  padding: 20px 0 12px;
10
11
  }
11
12
 
@@ -22,7 +22,7 @@
22
22
  background: var(--bg);
23
23
  border-top: 1px solid var(--border);
24
24
  display: flex;
25
- align-items: center;
25
+ align-items: flex-start;
26
26
  justify-content: space-around;
27
27
  z-index: 200;
28
28
  }
@@ -35,7 +35,7 @@
35
35
  align-items: center;
36
36
  justify-content: center;
37
37
  gap: 2px;
38
- height: 100%;
38
+ height: 56px;
39
39
  background: none;
40
40
  border: none;
41
41
  color: var(--text-dimmer);