@bakapiano/ccsm 0.9.0 → 0.10.1
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/CLAUDE.md +222 -195
- package/README.md +77 -79
- package/lib/cliSessionWatcher.js +249 -0
- package/lib/config.js +101 -24
- package/lib/folders.js +96 -0
- package/lib/localCliSessions.js +177 -0
- package/lib/persistedSessions.js +134 -0
- package/lib/webTerminal.js +31 -18
- package/lib/workspace.js +26 -4
- package/package.json +1 -1
- package/public/assets/claude-color.svg +1 -0
- package/public/assets/codex-color.svg +1 -0
- package/public/assets/copilot-color.svg +1 -0
- package/public/css/base.css +22 -5
- package/public/css/cards.css +37 -3
- package/public/css/feedback.css +127 -43
- package/public/css/forms.css +97 -25
- package/public/css/layout.css +74 -26
- package/public/css/modal.css +40 -26
- package/public/css/responsive.css +2 -2
- package/public/css/sidebar.css +424 -25
- package/public/css/terminals.css +138 -0
- package/public/css/tokens.css +28 -12
- package/public/css/wco.css +38 -39
- package/public/css/widgets.css +1177 -6
- package/public/index.html +35 -2
- package/public/js/api.js +194 -37
- package/public/js/components/AdoptModal.js +171 -0
- package/public/js/components/App.js +1 -11
- package/public/js/components/DirectoryPicker.js +203 -0
- package/public/js/components/EntityFormModal.js +105 -0
- package/public/js/components/Modal.js +51 -0
- package/public/js/components/OfflineBanner.js +29 -23
- package/public/js/components/PageTitleBar.js +13 -0
- package/public/js/components/Picker.js +179 -0
- package/public/js/components/Popover.js +55 -0
- package/public/js/components/Sidebar.js +219 -32
- package/public/js/components/TerminalView.js +27 -3
- package/public/js/components/useDragSort.js +67 -0
- package/public/js/dialog.js +10 -2
- package/public/js/icons.js +66 -3
- package/public/js/main.js +54 -3
- package/public/js/pages/AboutPage.js +80 -0
- package/public/js/pages/ConfigurePage.js +429 -207
- package/public/js/pages/LaunchPage.js +326 -86
- package/public/js/pages/SessionsPage.js +91 -41
- package/public/js/state.js +102 -73
- package/public/manifest.webmanifest +2 -2
- package/scripts/install.js +7 -2
- package/server.js +755 -441
- package/lib/favorites.js +0 -51
- package/lib/focus.js +0 -369
- package/lib/labels.js +0 -29
- package/lib/launcher.js +0 -219
- package/lib/sessions.js +0 -272
- package/lib/snapshot.js +0 -141
- package/public/js/actions.js +0 -107
- package/public/js/components/Fab.js +0 -11
- package/public/js/components/FavoritesTable.js +0 -81
- package/public/js/components/Footer.js +0 -12
- package/public/js/components/NewSessionModal.js +0 -153
- package/public/js/components/PageHead.js +0 -33
- package/public/js/components/Pagination.js +0 -27
- package/public/js/components/RecentTable.js +0 -68
- package/public/js/components/SessionsTable.js +0 -71
- package/public/js/components/SnapshotPanel.js +0 -77
- package/public/js/components/TitleCell.js +0 -40
- package/public/js/components/WorkspacesGrid.js +0 -41
- package/public/js/pages/TerminalsPage.js +0 -74
package/public/js/main.js
CHANGED
|
@@ -4,13 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
import { render } from 'preact';
|
|
6
6
|
import { html } from './html.js';
|
|
7
|
-
import { loadPersisted, clockTick, lastRefreshAt, installPrompt, isInstalledPwa } from './state.js';
|
|
7
|
+
import { loadPersisted, clockTick, lastRefreshAt, installPrompt, isInstalledPwa, sidebarForcedCollapsed } from './state.js';
|
|
8
8
|
import { httpBase } from './backend.js';
|
|
9
|
-
import { loadConfig, refreshAll, loadSessions,
|
|
9
|
+
import { loadConfig, refreshAll, loadSessions, loadFolders, loadWorkspaces, pollHealth } from './api.js';
|
|
10
10
|
import { setToast } from './toast.js';
|
|
11
11
|
import { App } from './components/App.js';
|
|
12
12
|
|
|
13
13
|
loadPersisted();
|
|
14
|
+
// Pin the document title to "CCSM" — some Chromium builds will inject the
|
|
15
|
+
// current URL or path into the standalone window title bar if the page
|
|
16
|
+
// title is empty / changes; locking it here keeps the OS title bar text
|
|
17
|
+
// stable across navigation, tab switches, and PWA-install refresh.
|
|
18
|
+
const lockTitle = () => { if (document.title !== 'CCSM') document.title = 'CCSM'; };
|
|
19
|
+
lockTitle();
|
|
20
|
+
new MutationObserver(lockTitle).observe(
|
|
21
|
+
document.querySelector('title') || document.head,
|
|
22
|
+
{ childList: true, subtree: true, characterData: true }
|
|
23
|
+
);
|
|
14
24
|
render(html`<${App} />`, document.getElementById('app'));
|
|
15
25
|
|
|
16
26
|
// PWA install affordance — Chromium fires `beforeinstallprompt` when the
|
|
@@ -43,7 +53,22 @@ function applyIsAppClass() {
|
|
|
43
53
|
applyIsAppClass();
|
|
44
54
|
matchMedia('(display-mode: browser)').addEventListener('change', applyIsAppClass);
|
|
45
55
|
|
|
56
|
+
// Force-collapse the sidebar on narrow viewports. Mirrors the responsive
|
|
57
|
+
// CSS so JS state (toggle visibility, tree-render gating) agrees with the
|
|
58
|
+
// rendered layout.
|
|
59
|
+
const narrowMq = matchMedia('(max-width: 900px)');
|
|
60
|
+
function applyNarrow() { sidebarForcedCollapsed.value = narrowMq.matches; }
|
|
61
|
+
applyNarrow();
|
|
62
|
+
narrowMq.addEventListener('change', applyNarrow);
|
|
63
|
+
|
|
46
64
|
(async () => {
|
|
65
|
+
// Version-mismatch guard runs FIRST. If the user's backend has been
|
|
66
|
+
// upgraded since this per-version frontend was loaded, bounce back to
|
|
67
|
+
// the router immediately — no point loading config from a server that
|
|
68
|
+
// speaks a different API revision. Runs in dev too (it no-ops without
|
|
69
|
+
// the build-time <meta>).
|
|
70
|
+
await bootVersionGuard();
|
|
71
|
+
|
|
47
72
|
try {
|
|
48
73
|
await loadConfig();
|
|
49
74
|
await refreshAll();
|
|
@@ -61,7 +86,7 @@ matchMedia('(display-mode: browser)').addEventListener('change', applyIsAppClass
|
|
|
61
86
|
// move in/out of a workspace silently and the grid stays stale.
|
|
62
87
|
setInterval(async () => {
|
|
63
88
|
try {
|
|
64
|
-
await Promise.all([loadSessions(),
|
|
89
|
+
await Promise.all([loadSessions(), loadFolders(), loadWorkspaces()]);
|
|
65
90
|
lastRefreshAt.value = Date.now();
|
|
66
91
|
} catch { /* swallow — next tick retries */ }
|
|
67
92
|
pollHealth();
|
|
@@ -79,3 +104,29 @@ matchMedia('(display-mode: browser)').addEventListener('change', applyIsAppClass
|
|
|
79
104
|
setInterval(ping, 10_000);
|
|
80
105
|
document.addEventListener('visibilitychange', () => { if (!document.hidden) ping(); });
|
|
81
106
|
})();
|
|
107
|
+
|
|
108
|
+
// ─── version routing guard ───────────────────────────────────────────
|
|
109
|
+
// Each deployed frontend is pinned to one backend version. The GH-Pages
|
|
110
|
+
// workflow bakes the version into <meta name="ccsm-frontend-version">
|
|
111
|
+
// so we can detect "backend has been upgraded since this frontend was
|
|
112
|
+
// loaded" and bounce back through the router at /ccsm/ for a fresh
|
|
113
|
+
// match. In dev (no meta tag, same-origin served-by-backend), the check
|
|
114
|
+
// no-ops — we're always running the frontend that ships with this
|
|
115
|
+
// backend by definition.
|
|
116
|
+
async function bootVersionGuard() {
|
|
117
|
+
const meta = document.querySelector('meta[name="ccsm-frontend-version"]');
|
|
118
|
+
if (!meta) return; // dev mode
|
|
119
|
+
const myVer = meta.getAttribute('content');
|
|
120
|
+
if (!myVer) return;
|
|
121
|
+
let backendVer = null;
|
|
122
|
+
try {
|
|
123
|
+
const r = await fetch(httpBase() + '/api/health', { cache: 'no-store' });
|
|
124
|
+
if (!r.ok) return;
|
|
125
|
+
backendVer = (await r.json()).version;
|
|
126
|
+
} catch { return; } // offline → OfflineBanner takes over
|
|
127
|
+
if (!backendVer || backendVer === myVer) return;
|
|
128
|
+
// Mismatch. Bounce up one level to the router. The router will
|
|
129
|
+
// probe /api/health again and redirect to ./<backendVer>/.
|
|
130
|
+
console.warn(`[ccsm] frontend ${myVer} ≠ backend ${backendVer} — re-routing`);
|
|
131
|
+
location.replace('../');
|
|
132
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { html } from '../html.js';
|
|
2
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
2
3
|
import { serverHealth, installPrompt, isInstalledPwa } from '../state.js';
|
|
3
4
|
import { setToast } from '../toast.js';
|
|
5
|
+
import { api } from '../api.js';
|
|
4
6
|
import { Card } from '../components/Card.js';
|
|
7
|
+
import { PageTitleBar } from '../components/PageTitleBar.js';
|
|
5
8
|
import { BrandMark, IconGithub, IconExternal } from '../icons.js';
|
|
6
9
|
|
|
7
10
|
const REPO_URL = 'https://github.com/bakapiano/ccsm';
|
|
@@ -40,10 +43,87 @@ function InstallCard() {
|
|
|
40
43
|
</${Card}>`;
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
function UpgradeCard() {
|
|
47
|
+
const [info, setInfo] = useState(null); // { current, latest, updateAvailable, fetchedAt, error? }
|
|
48
|
+
const [checking, setChecking] = useState(true);
|
|
49
|
+
const [upgrading, setUpgrading] = useState(false);
|
|
50
|
+
|
|
51
|
+
const refresh = async (force = false) => {
|
|
52
|
+
setChecking(true);
|
|
53
|
+
try {
|
|
54
|
+
const r = await api('GET', '/api/version' + (force ? '?refresh=1' : ''));
|
|
55
|
+
setInfo(r);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
setInfo({ error: e.message });
|
|
58
|
+
} finally {
|
|
59
|
+
setChecking(false);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
useEffect(() => { refresh(false); }, []);
|
|
63
|
+
|
|
64
|
+
const onUpgrade = async () => {
|
|
65
|
+
if (!info?.updateAvailable) return;
|
|
66
|
+
setUpgrading(true);
|
|
67
|
+
try {
|
|
68
|
+
await api('POST', '/api/upgrade', { target: 'latest' });
|
|
69
|
+
setToast(`upgrading to v${info.latest} · backend will restart`);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
setUpgrading(false);
|
|
72
|
+
setToast(e.message, 'error');
|
|
73
|
+
}
|
|
74
|
+
// No "finally" reset — the server is about to shut down, and the
|
|
75
|
+
// OfflineBanner takes over UI. When the router reroutes us to the new
|
|
76
|
+
// version's frontend, this component re-mounts fresh.
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const current = info?.current || serverHealth.value.version || '';
|
|
80
|
+
const latest = info?.latest;
|
|
81
|
+
const updateAvailable = !!info?.updateAvailable;
|
|
82
|
+
|
|
83
|
+
return html`
|
|
84
|
+
<${Card} title="Version">
|
|
85
|
+
<div class="about-version-row">
|
|
86
|
+
<div>
|
|
87
|
+
<div class="about-version-line">
|
|
88
|
+
Installed · <span class="mono">v${current || '?'}</span>
|
|
89
|
+
</div>
|
|
90
|
+
${latest && !updateAvailable ? html`
|
|
91
|
+
<div class="muted-text" style="margin-top:4px">You're on the latest release.</div>
|
|
92
|
+
` : null}
|
|
93
|
+
${updateAvailable ? html`
|
|
94
|
+
<div class="about-update-line">
|
|
95
|
+
Update available · <span class="mono">v${latest}</span>
|
|
96
|
+
</div>
|
|
97
|
+
` : null}
|
|
98
|
+
${info?.error ? html`
|
|
99
|
+
<div class="muted-text" style="margin-top:4px">Couldn't reach npm registry.</div>
|
|
100
|
+
` : null}
|
|
101
|
+
</div>
|
|
102
|
+
<div class="about-version-actions">
|
|
103
|
+
<button class="action subtle" onClick=${() => refresh(true)} disabled=${checking || upgrading}>
|
|
104
|
+
${checking ? 'Checking…' : 'Check'}
|
|
105
|
+
</button>
|
|
106
|
+
${updateAvailable ? html`
|
|
107
|
+
<button class="action primary" onClick=${onUpgrade} disabled=${upgrading}>
|
|
108
|
+
${upgrading ? 'Upgrading…' : `Upgrade to v${latest}`}
|
|
109
|
+
</button>
|
|
110
|
+
` : null}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
${upgrading ? html`
|
|
114
|
+
<p class="muted-text" style="margin-top:var(--s-3)">
|
|
115
|
+
Running <code>npm i -g @bakapiano/ccsm@latest</code>. The backend will restart automatically — you'll see the "Backend not running" screen briefly.
|
|
116
|
+
</p>
|
|
117
|
+
` : null}
|
|
118
|
+
</${Card}>`;
|
|
119
|
+
}
|
|
120
|
+
|
|
43
121
|
export function AboutPage() {
|
|
44
122
|
const version = serverHealth.value.version;
|
|
45
123
|
|
|
46
124
|
return html`
|
|
125
|
+
<${PageTitleBar} title="About" />
|
|
126
|
+
<${UpgradeCard} />
|
|
47
127
|
<${InstallCard} />
|
|
48
128
|
<${Card} title="ccsm">
|
|
49
129
|
<div class="about-block">
|