Skip to content

Commit 9f415d7

Browse files
committed
Fix timing issue, CPU meter smoothing, default editor panel to open, editor default to closed on a closed session, add discord links, fix double close message, handle errs on modals better, comments cleanup
1 parent 16be4cc commit 9f415d7

13 files changed

Lines changed: 91 additions & 33 deletions

File tree

web/frontend/src/App.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { onMount, afterUpdate } from "svelte";
3+
import { get } from "svelte/store";
34
import Modal from "./components/Modal.svelte";
45
import IPLimited from "./components/IPLimited.svelte";
56
import Console from "./components/Console.svelte";
@@ -41,7 +42,7 @@
4142
}
4243
4344
const editorOpen = localStorage.getItem(EDITOR_OPEN_KEY);
44-
isEditorOpen.set(editorOpen ? JSON.parse(editorOpen) : false);
45+
isEditorOpen.set(editorOpen === null ? true : JSON.parse(editorOpen));
4546
4647
const handleKeydown = (e: KeyboardEvent) => {
4748
if (e.key === "." && (e.metaKey || e.ctrlKey)) {
@@ -58,6 +59,8 @@
5859
});
5960
6061
isEditorOpen.subscribe((open) => {
62+
const state = get(sessionState);
63+
if (state === "readonly" || state === "closed") return;
6164
localStorage.setItem(EDITOR_OPEN_KEY, JSON.stringify(open));
6265
});
6366
@@ -81,6 +84,7 @@
8184
sessionMetadata.set(data.metadata);
8285
}
8386
sessionState.set("readonly");
87+
isEditorOpen.set(false);
8488
view = "console";
8589
return;
8690
}

web/frontend/src/components/Console.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
import StatusPanel from "./StatusPanel.svelte";
99
import SessionEndedCard from "./SessionEndedCard.svelte";
1010
11+
let endedCardShown = false;
1112
function appendEndedCard(container: HTMLElement) {
13+
if (endedCardShown) return;
14+
endedCardShown = true;
1215
const target = document.createElement("div");
1316
container.appendChild(target);
1417
mount(SessionEndedCard, { target });
@@ -40,8 +43,6 @@
4043
onMount(() => {
4144
virtualConsole = new VirtualConsole(outputContainer);
4245
43-
// Single subscription for the lifetime of the component — server-driven
44-
// close events flow through the centralized handler into sessionState
4546
unsubscribeSessionState = sessionState.subscribe((state) => {
4647
if (state === "closed" && !cleanClose && socket) {
4748
cleanClose = true;

web/frontend/src/components/Editor.svelte

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script lang="ts">
2-
import { onMount } from "svelte";
32
import CodeMirror from "svelte-codemirror-editor";
43
import { keymap } from "@codemirror/view";
54
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";
@@ -16,7 +15,8 @@
1615
1716
$: inactive = $sessionState === "closed" || $sessionState === "readonly";
1817
19-
let value = `-- Welcome to the GLua Editor!
18+
const STORAGE_KEY = "glua-editor-content";
19+
const DEFAULT_CONTENT = `-- Welcome to the GLua Editor!
2020
2121
function hello()
2222
print("Hello from the editor!")
@@ -25,12 +25,20 @@ end
2525
hello()
2626
`;
2727
28-
onMount(() => {
29-
const savedContent = localStorage.getItem("glua-editor-content");
30-
if (savedContent && savedContent !== "undefined") {
31-
value = savedContent;
32-
}
33-
});
28+
// Read synchronously so CodeMirror sees the correct initial value on mount.
29+
// Doing this in onMount races with CodeMirror's own init and can also let
30+
// the editor's first on:change overwrite localStorage with the default
31+
function loadInitialContent(): string {
32+
const saved = localStorage.getItem(STORAGE_KEY);
33+
return saved && saved !== "undefined" ? saved : DEFAULT_CONTENT;
34+
}
35+
36+
let value = loadInitialContent();
37+
38+
// Driven by the bound `value` rather than CodeMirror's on:change event,
39+
// which can fire with a transient empty string during init and wipe the
40+
// saved script before the user ever types
41+
$: if (typeof value === "string") localStorage.setItem(STORAGE_KEY, value);
3442
3543
function runScript() {
3644
if (!socket || socket.readyState !== WebSocket.OPEN) return;
@@ -59,7 +67,6 @@ hello()
5967
keymap.of([...defaultKeymap, ...historyKeymap, {key: "Ctrl-Enter", run: () => { runScript(); return true; }}]),
6068
gluaTheme
6169
]}
62-
on:change={(e) => localStorage.setItem("glua-editor-content", e.detail.value)}
6370
/>
6471
</div>
6572
<div id="editor-footer">

web/frontend/src/components/Modal.svelte

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,34 @@
1010
async function requestSession() {
1111
inQueue = true;
1212
queuePosition = "Connecting...";
13-
13+
1414
try {
1515
const response = await fetch(`/api/request-session?type=${containerType}`, { method: "POST" });
16-
const data = await response.json();
16+
// Non-JSON error pages (e.g. a Cloudflare 5xx HTML) fall through to
17+
// the unknown-status branch below and surface a friendly error
18+
const data = await response.json().catch(() => ({}));
1719
1820
if (data.status === "READY") {
1921
dispatch("startsession", { sessionId: data.sessionId, sessionType: containerType });
20-
} else if (data.status === "IP_LIMIT") {
22+
return;
23+
}
24+
if (data.status === "IP_LIMIT") {
2125
dispatch("iplimited", { limit: data.limit });
2226
inQueue = false;
23-
} else if (data.status === "QUEUED") {
27+
return;
28+
}
29+
if (data.status === "QUEUED") {
2430
queuePosition = data.position;
2531
pollQueueStatus(data.ticketId);
32+
return;
2633
}
34+
35+
inQueue = false;
36+
queueError = "Couldn't start a session. Please try again in a moment.";
2737
} catch (e) {
2838
console.error("Failed to request session:", e);
2939
inQueue = false;
30-
// You can add error handling here to show a message to the user
40+
queueError = "Couldn't reach the server. Check your connection and try again.";
3141
}
3242
}
3343

web/frontend/src/components/SessionEndedCard.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<script lang="ts">
2+
import { DISCORD_URL } from "../lib/links";
3+
24
let copied = false;
35
46
function share() {
@@ -21,5 +23,6 @@
2123
{copied ? "Copied!" : "Share Logs"}
2224
</button>
2325
</div>
26+
<p class="text-gray-500 text-xs mt-3">Questions or ideas? <a href={DISCORD_URL} target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300 hover:underline">Come say hi on Discord</a></p>
2427
</div>
2528
</div>

web/frontend/src/components/StatusPanel.svelte

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { sessionState, sessionMetadata, sessionTimer } from "../lib/stores";
44
import { healthData } from "../lib/socket";
55
import { formatTime } from "../lib/format";
6+
import { DISCORD_URL } from "../lib/links";
67
import SessionMetadata from "./SessionMetadata.svelte";
78
import TimeoutBar from "./TimeoutBar.svelte";
89
import EndSessionButton from "./EndSessionButton.svelte";
@@ -30,10 +31,19 @@
3031

3132
<div id="status-panel" class="absolute top-4 right-4 z-20 w-72" class:collapsed>
3233
<div class="bg-gray-800 rounded-lg shadow-lg border border-gray-700/50">
33-
<button on:click={() => collapsed = !collapsed} class="w-full flex justify-between items-center p-3 text-left focus:outline-none">
34-
<span class="font-bold text-sm {inactive ? 'text-gray-400' : 'text-white'}">{inactive ? "Session Ended" : "Session Status"}</span>
35-
<svg class="toggle-icon w-5 h-5 text-gray-400" class:rotate-180={collapsed} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>
36-
</button>
34+
<div class="w-full flex justify-between items-center p-3">
35+
<button on:click={() => collapsed = !collapsed} class="flex-1 flex justify-between items-center text-left focus:outline-none pr-2">
36+
<span class="font-bold text-sm {inactive ? 'text-gray-400' : 'text-white'}">{inactive ? "Session Ended" : "Session Status"}</span>
37+
</button>
38+
<div class="flex items-center gap-2">
39+
<a href={DISCORD_URL} target="_blank" rel="noopener noreferrer" title="Questions, bugs, or feedback? Join the CFC dev Discord" class="text-gray-500 hover:text-indigo-300 transition-colors" aria-label="Join the CFC developer Discord">
40+
<svg class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M20.317 4.369A19.79 19.79 0 0 0 16.558 3.2a.074.074 0 0 0-.079.037c-.34.607-.719 1.4-.984 2.024a18.302 18.302 0 0 0-5.487 0 12.64 12.64 0 0 0-.998-2.024.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.058a.082.082 0 0 0 .031.056 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.105 13.1 13.1 0 0 1-1.872-.892.077.077 0 0 1-.008-.128c.126-.094.252-.192.372-.291a.074.074 0 0 1 .077-.01c3.927 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.01c.12.099.246.197.373.29a.077.077 0 0 1-.006.129 12.3 12.3 0 0 1-1.873.891.077.077 0 0 0-.041.106c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.03ZM8.02 15.331c-1.183 0-2.157-1.086-2.157-2.42 0-1.334.955-2.42 2.157-2.42 1.211 0 2.176 1.095 2.157 2.42 0 1.334-.955 2.42-2.157 2.42Zm7.974 0c-1.183 0-2.157-1.086-2.157-2.42 0-1.334.955-2.42 2.157-2.42 1.211 0 2.176 1.095 2.157 2.42 0 1.334-.946 2.42-2.157 2.42Z"/></svg>
41+
</a>
42+
<button on:click={() => collapsed = !collapsed} class="focus:outline-none" aria-label="Toggle panel">
43+
<svg class="toggle-icon w-5 h-5 text-gray-400" class:rotate-180={collapsed} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" /></svg>
44+
</button>
45+
</div>
46+
</div>
3747
<div class="panel-content">
3848
{#if inactive}
3949
<div class="px-4 pb-4 font-mono text-xs space-y-2">

web/frontend/src/lib/links.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const DISCORD_URL = "https://discord.gg/5JUqZjzmYJ";

web/frontend/src/lib/smoothing.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Exponential moving average — blends each new sample into a running state
3+
* to dampen jitter. Lower alpha is smoother but laggier; 0.3 is a reasonable
4+
* default for noisy metrics like CPU usage
5+
*
6+
* The first sample seeds the state directly so readings don't ramp up from 0
7+
*/
8+
export function createEma(alpha: number) {
9+
let state = 0;
10+
let initialized = false;
11+
return (sample: number): number => {
12+
if (!initialized) {
13+
state = sample;
14+
initialized = true;
15+
} else {
16+
state += alpha * (sample - state);
17+
}
18+
return state;
19+
};
20+
}

web/frontend/src/lib/socket.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import type { ServerMessage } from "@glua/shared";
22
import type { VirtualConsole } from "./virtual-console";
33
import { sessionState, sessionTimer, sessionMetadata, scriptMap } from "./stores";
44
import { writable } from "svelte/store";
5+
import { createEma } from "./smoothing";
56

67
export const healthData = writable<{ cpuUsage: number; diskUsage: number }>({ cpuUsage: 0, diskUsage: 0 });
78

9+
const smoothCpu = createEma(0.3);
10+
811
/**
912
* Wires up a single message handler on the socket that parses once
1013
* and dispatches to the right store or console method.
@@ -22,7 +25,7 @@ export function attachMessageHandler(socket: WebSocket, virtualConsole: VirtualC
2225
break;
2326
case "HEALTH":
2427
healthData.set({
25-
cpuUsage: msg.payload.cpuusage ?? 0,
28+
cpuUsage: smoothCpu(msg.payload.cpuusage ?? 0),
2629
diskUsage: msg.payload.diskusage ?? 0,
2730
});
2831
break;

web/src/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// Server-side timing and capacity constants
21
// Shared constants (session types, limits) live in @glua/shared
32

43
export const SESSION_TIMING = {

0 commit comments

Comments
 (0)