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/bin/cli.js +47 -30
- package/lib/pages.js +18 -17
- package/lib/project.js +77 -63
- package/lib/public/app.js +250 -72
- package/lib/public/css/base.css +3 -0
- package/lib/public/css/filebrowser.css +8 -1
- package/lib/public/css/home-hub.css +1 -1
- package/lib/public/css/input.css +4 -0
- package/lib/public/css/menus.css +1 -44
- package/lib/public/css/messages.css +1 -0
- package/lib/public/css/mobile-nav.css +2 -2
- package/lib/public/css/overlays.css +175 -16
- package/lib/public/css/scheduler.css +1 -0
- package/lib/public/css/server-settings.css +37 -34
- package/lib/public/css/title-bar.css +0 -7
- package/lib/public/index.html +63 -22
- package/lib/public/manifest.json +2 -2
- package/lib/public/modules/notifications.js +15 -103
- package/lib/public/modules/scheduler.js +55 -48
- package/lib/public/modules/server-settings.js +13 -4
- package/lib/sdk-bridge.js +4 -6
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2662
|
+
var countTextEl = document.getElementById("client-count-text");
|
|
2663
|
+
if (countEl && countTextEl) {
|
|
2654
2664
|
if (msg.count > 1) {
|
|
2655
|
-
|
|
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
|
-
|
|
2671
|
-
|
|
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
|
-
// ---
|
|
3801
|
-
var
|
|
3802
|
-
var
|
|
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">🧩</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
|
|
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
|
-
|
|
3810
|
-
|
|
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(
|
|
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
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3973
|
+
requireClayRalph(function () {
|
|
3974
|
+
wizardData = { name: "", task: "", maxIterations: 3 };
|
|
3975
|
+
var el = document.getElementById("ralph-wizard");
|
|
3976
|
+
if (!el) return;
|
|
3820
3977
|
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3934
|
-
|
|
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();
|
package/lib/public/css/base.css
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/lib/public/css/input.css
CHANGED
package/lib/public/css/menus.css
CHANGED
|
@@ -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;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
background: var(--bg);
|
|
23
23
|
border-top: 1px solid var(--border);
|
|
24
24
|
display: flex;
|
|
25
|
-
align-items:
|
|
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:
|
|
38
|
+
height: 56px;
|
|
39
39
|
background: none;
|
|
40
40
|
border: none;
|
|
41
41
|
color: var(--text-dimmer);
|