@sean.holung/minicode 0.3.8 → 0.3.9

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.
@@ -8,7 +8,7 @@ import { computeFileHashes, getWorkspaceCacheDir, loadIndex, saveIndex, } from "
8
8
  import { buildProjectIndex } from "../indexer/project-index.js";
9
9
  import { sortModelsAlphabetically } from "../model-utils.js";
10
10
  import { createToolRegistry } from "../tools/registry.js";
11
- import { listSessions, loadSession, loadSessionByLabel, saveSession, } from "../session/session-store.js";
11
+ import { deleteSession, listSessions, loadSession, loadSessionByLabel, saveSession, } from "../session/session-store.js";
12
12
  import { getSymbolDisplayName } from "../indexer/symbol-names.js";
13
13
  export class AgentBridge {
14
14
  agent;
@@ -349,6 +349,9 @@ export class AgentBridge {
349
349
  async listSess() {
350
350
  return listSessions();
351
351
  }
352
+ async deleteSess(sessionId) {
353
+ return deleteSession(sessionId);
354
+ }
352
355
  // ── Project index queries ──
353
356
  hasIndex() {
354
357
  return this.projectIndex !== undefined;
@@ -473,6 +473,20 @@ export function createRequestHandler(bridge, emit, options = {}) {
473
473
  });
474
474
  return;
475
475
  }
476
+ if (pathname.startsWith("/api/sessions/") && method === "DELETE") {
477
+ const sessionId = decodeURIComponent(pathname.slice("/api/sessions/".length));
478
+ if (!sessionId) {
479
+ sendJson(res, 400, { error: "Session id is required" });
480
+ return;
481
+ }
482
+ const deleted = await bridge.deleteSess(sessionId);
483
+ if (!deleted) {
484
+ sendJson(res, 404, { error: "Session not found" });
485
+ return;
486
+ }
487
+ sendJson(res, 200, { ok: true, deleted: true, id: sessionId });
488
+ return;
489
+ }
476
490
  // ── Graph / Index API ──
477
491
  if (pathname === "/api/symbols" && method === "GET") {
478
492
  if (!bridge.hasIndex()) {
@@ -1,4 +1,4 @@
1
- import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
1
+ import { mkdir, readdir, readFile, unlink, writeFile } from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { Session } from "@minicode/agent-sdk";
@@ -100,3 +100,13 @@ export async function loadSessionByLabel(label) {
100
100
  return undefined;
101
101
  return loadSession(match.id);
102
102
  }
103
+ export async function deleteSession(sessionId) {
104
+ const filePath = path.join(sessionsDir, `${sessionId}.json`);
105
+ try {
106
+ await unlink(filePath);
107
+ return true;
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
@@ -2950,6 +2950,7 @@ var sessionDropdown = document.getElementById("session-dropdown");
2950
2950
  var sessionList = document.getElementById("session-list");
2951
2951
  var sessionUpdateRow = document.getElementById("session-update-row");
2952
2952
  var sessionUpdateBtn = document.getElementById("session-update-btn");
2953
+ var sessionAutoSaveToggle = document.getElementById("session-autosave-toggle");
2953
2954
  var saveBtn = document.getElementById("save-btn");
2954
2955
  var saveLabelInput = document.getElementById("save-label");
2955
2956
  var contextFill = document.getElementById("context-fill");
@@ -3010,6 +3011,8 @@ var sessionRefreshTracker = createLatestRequestTracker();
3010
3011
  var TOOL_RESULT_MAX = 500;
3011
3012
  var OPENROUTER_PKCE_VERIFIER_KEY = "minicode:openrouter:pkce-verifier";
3012
3013
  var OPENROUTER_PERSIST_TO_ENV_KEY = "minicode:openrouter:persist-to-env";
3014
+ var SESSION_AUTOSAVE_KEY = "minicode:session:auto-save";
3015
+ var SESSION_AUTOSAVE_LABEL_PREFIX = "Autosave";
3013
3016
  var OPENAI_COMPATIBLE_PRESETS = {
3014
3017
  lmstudio: {
3015
3018
  baseUrl: "http://localhost:1234/v1",
@@ -3032,6 +3035,11 @@ var OPENAI_COMPATIBLE_PRESETS = {
3032
3035
  apiKeyPlaceholder: "Add an API key only if this endpoint requires auth"
3033
3036
  }
3034
3037
  };
3038
+ var sessionAutoSaveEnabled = loadSessionAutoSavePreference();
3039
+ var pendingAutoSaveLabel = null;
3040
+ var autoSaveInFlight = null;
3041
+ var autoSaveQueued = false;
3042
+ sessionAutoSaveToggle.checked = sessionAutoSaveEnabled;
3035
3043
  function connect() {
3036
3044
  const protocol = location.protocol === "https:" ? "wss:" : "ws:";
3037
3045
  ws = new WebSocket(`${protocol}//${location.host}`);
@@ -3422,6 +3430,7 @@ function handleServerMessage(msg) {
3422
3430
  if (msg.usage) {
3423
3431
  addUsageInfo(msg.usage);
3424
3432
  }
3433
+ queueSessionAutoSave();
3425
3434
  break;
3426
3435
  case "error":
3427
3436
  addMessage(`Error: ${msg.message || ""}`, "error");
@@ -3568,7 +3577,8 @@ function updateContextIndicator(contextTokens, maxContextTokens) {
3568
3577
  }
3569
3578
  contextLabel.textContent = pct + "%";
3570
3579
  const indicator = document.getElementById("context-indicator");
3571
- indicator.title = `Context: ~${contextTokens.toLocaleString()} / ${maxContextTokens.toLocaleString()} tokens (${pct}%)`;
3580
+ indicator.title = `Context: ~${contextTokens.toLocaleString()} / ${maxContextTokens.toLocaleString()} tokens (${pct}%)
3581
+ Adjust context size in Settings if you want it larger or smaller.`;
3572
3582
  }
3573
3583
  async function fetchContext() {
3574
3584
  try {
@@ -4040,23 +4050,122 @@ document.addEventListener("click", (e) => {
4040
4050
  sessionDropdown.classList.add("hidden");
4041
4051
  }
4042
4052
  });
4053
+ function loadSessionAutoSavePreference() {
4054
+ try {
4055
+ return localStorage.getItem(SESSION_AUTOSAVE_KEY) === "1";
4056
+ } catch {
4057
+ return false;
4058
+ }
4059
+ }
4060
+ function persistSessionAutoSavePreference(enabled) {
4061
+ try {
4062
+ if (enabled) {
4063
+ localStorage.setItem(SESSION_AUTOSAVE_KEY, "1");
4064
+ } else {
4065
+ localStorage.removeItem(SESSION_AUTOSAVE_KEY);
4066
+ }
4067
+ } catch {
4068
+ }
4069
+ }
4070
+ function buildAutoSaveLabel() {
4071
+ return `${SESSION_AUTOSAVE_LABEL_PREFIX} ${(/* @__PURE__ */ new Date()).toLocaleString()}`;
4072
+ }
4073
+ async function persistCurrentSession(label) {
4074
+ const res = await fetch("/api/sessions/save", {
4075
+ method: "POST",
4076
+ headers: { "Content-Type": "application/json" },
4077
+ body: JSON.stringify({ label })
4078
+ });
4079
+ const body = await res.json();
4080
+ if (!res.ok) {
4081
+ throw new Error("error" in body ? body.error : `Failed to save session (${res.status})`);
4082
+ }
4083
+ return body;
4084
+ }
4085
+ async function deleteSavedSession(session) {
4086
+ const isCurrentSavedSession = activeSavedSession?.id === session.id;
4087
+ const confirmed = window.confirm(`Delete saved session "${session.label}"?`);
4088
+ if (!confirmed) {
4089
+ return;
4090
+ }
4091
+ try {
4092
+ const res = await fetch(`/api/sessions/${encodeURIComponent(session.id)}`, {
4093
+ method: "DELETE"
4094
+ });
4095
+ const body = await res.json();
4096
+ if (!res.ok) {
4097
+ throw new Error("error" in body ? body.error : `Failed to delete session (${res.status})`);
4098
+ }
4099
+ if (isCurrentSavedSession) {
4100
+ activeSavedSession = null;
4101
+ }
4102
+ if (pendingAutoSaveLabel === session.label) {
4103
+ pendingAutoSaveLabel = null;
4104
+ }
4105
+ addMessage(
4106
+ isCurrentSavedSession ? `Deleted saved session "${session.label}". The current chat stays open until you load another session or refresh.` : `Session deleted: "${session.label}"`,
4107
+ "thinking"
4108
+ );
4109
+ await refreshSessionList();
4110
+ } catch (error) {
4111
+ const message = error instanceof Error ? error.message : "Failed to delete session";
4112
+ addMessage(message, "error");
4113
+ }
4114
+ }
4115
+ async function maybeAutoSaveSession() {
4116
+ if (!sessionAutoSaveEnabled) {
4117
+ return;
4118
+ }
4119
+ const label = activeSavedSession?.label ?? pendingAutoSaveLabel ?? buildAutoSaveLabel();
4120
+ try {
4121
+ const data = await persistCurrentSession(label);
4122
+ pendingAutoSaveLabel = data.label;
4123
+ await refreshSessionList();
4124
+ } catch (error) {
4125
+ const message = error instanceof Error ? error.message : "Failed to auto-save session";
4126
+ addMessage(message, "error");
4127
+ }
4128
+ }
4129
+ function queueSessionAutoSave() {
4130
+ if (!sessionAutoSaveEnabled) {
4131
+ return;
4132
+ }
4133
+ if (autoSaveInFlight) {
4134
+ autoSaveQueued = true;
4135
+ return;
4136
+ }
4137
+ autoSaveInFlight = (async () => {
4138
+ await maybeAutoSaveSession();
4139
+ })();
4140
+ void autoSaveInFlight.finally(() => {
4141
+ autoSaveInFlight = null;
4142
+ if (autoSaveQueued) {
4143
+ autoSaveQueued = false;
4144
+ queueSessionAutoSave();
4145
+ }
4146
+ });
4147
+ }
4148
+ sessionAutoSaveToggle.addEventListener("change", () => {
4149
+ sessionAutoSaveEnabled = sessionAutoSaveToggle.checked;
4150
+ persistSessionAutoSavePreference(sessionAutoSaveEnabled);
4151
+ if (sessionAutoSaveEnabled) {
4152
+ addMessage(
4153
+ activeSavedSession ? `Auto-save enabled. minicode will update "${activeSavedSession.label}" after each completed turn.` : "Auto-save enabled. minicode will save this chat after the next completed turn.",
4154
+ "thinking"
4155
+ );
4156
+ } else {
4157
+ addMessage("Auto-save disabled.", "thinking");
4158
+ }
4159
+ });
4043
4160
  saveBtn.addEventListener("click", async () => {
4044
4161
  const requestedLabel = saveLabelInput.value.trim();
4045
4162
  const label = requestedLabel || activeSavedSession?.label || void 0;
4046
4163
  const isUpdatingCurrentSession = !!activeSavedSession && (requestedLabel.length === 0 || requestedLabel === activeSavedSession.label);
4047
4164
  try {
4048
4165
  saveBtn.setAttribute("disabled", "true");
4049
- const res = await fetch("/api/sessions/save", {
4050
- method: "POST",
4051
- headers: { "Content-Type": "application/json" },
4052
- body: JSON.stringify({ label })
4053
- });
4054
- const body = await res.json();
4055
- if (!res.ok) {
4056
- throw new Error("error" in body ? body.error : `Failed to save session (${res.status})`);
4057
- }
4058
- const data = body;
4166
+ const data = await persistCurrentSession(label);
4059
4167
  saveLabelInput.value = "";
4168
+ pendingAutoSaveLabel = data.label;
4060
4169
  addMessage(
4061
4170
  `${isUpdatingCurrentSession ? "Session updated" : "Session saved"}: "${data.label}"`,
4062
4171
  "thinking"
@@ -4086,6 +4195,7 @@ async function refreshSessionList() {
4086
4195
  const sessions = data.sessions;
4087
4196
  activeSavedSession = sessions.find((session) => session.id === data.currentSessionId) ?? null;
4088
4197
  if (activeSavedSession) {
4198
+ pendingAutoSaveLabel = activeSavedSession.label;
4089
4199
  sessionUpdateRow.classList.remove("hidden");
4090
4200
  sessionUpdateBtn.textContent = `Update "${activeSavedSession.label}"`;
4091
4201
  sessionUpdateBtn.title = `Save changes back to "${activeSavedSession.label}"`;
@@ -4103,8 +4213,22 @@ async function refreshSessionList() {
4103
4213
  const el = document.createElement("div");
4104
4214
  const isActive = activeSavedSession?.id === s.id;
4105
4215
  el.className = "session-item" + (isActive ? " active" : "");
4106
- el.innerHTML = `<span class="session-label">${escapeHtml(s.label)}</span><span class="session-meta">${s.messageCount} msgs${isActive ? ' <span class="session-active-badge">\u2022 active</span>' : ""}</span>`;
4107
- el.addEventListener("click", () => loadSession(s.label));
4216
+ const loadBtn = document.createElement("button");
4217
+ loadBtn.type = "button";
4218
+ loadBtn.className = "session-load-btn";
4219
+ loadBtn.innerHTML = `<span class="session-label">${escapeHtml(s.label)}</span><span class="session-meta">${s.messageCount} msgs${isActive ? ' <span class="session-active-badge">\u2022 active</span>' : ""}</span>`;
4220
+ loadBtn.addEventListener("click", () => loadSession(s.label));
4221
+ const deleteBtn = document.createElement("button");
4222
+ deleteBtn.type = "button";
4223
+ deleteBtn.className = "session-delete-btn";
4224
+ deleteBtn.textContent = "Delete";
4225
+ deleteBtn.title = `Delete "${s.label}"`;
4226
+ deleteBtn.addEventListener("click", (event) => {
4227
+ event.stopPropagation();
4228
+ void deleteSavedSession(s);
4229
+ });
4230
+ el.appendChild(loadBtn);
4231
+ el.appendChild(deleteBtn);
4108
4232
  sessionList.appendChild(el);
4109
4233
  }
4110
4234
  } catch {
@@ -4126,6 +4250,7 @@ async function loadSession(label) {
4126
4250
  if (res.ok) {
4127
4251
  const body = await res.json();
4128
4252
  sessionDropdown.classList.add("hidden");
4253
+ pendingAutoSaveLabel = body.label;
4129
4254
  renderLoadedSessionMessages(body.messages);
4130
4255
  if (body.messages.length === 0) {
4131
4256
  addMessage(`Session "${body.label}" restored`, "thinking");
@@ -4141,16 +4266,8 @@ sessionUpdateBtn.addEventListener("click", async () => {
4141
4266
  }
4142
4267
  try {
4143
4268
  sessionUpdateBtn.disabled = true;
4144
- const res = await fetch("/api/sessions/save", {
4145
- method: "POST",
4146
- headers: { "Content-Type": "application/json" },
4147
- body: JSON.stringify({ label: activeSavedSession.label })
4148
- });
4149
- const body = await res.json();
4150
- if (!res.ok) {
4151
- throw new Error("error" in body ? body.error : `Failed to update session (${res.status})`);
4152
- }
4153
- const data = body;
4269
+ const data = await persistCurrentSession(activeSavedSession.label);
4270
+ pendingAutoSaveLabel = data.label;
4154
4271
  addMessage(`Session updated: "${data.label}"`, "thinking");
4155
4272
  await refreshSessionList();
4156
4273
  } catch (error) {
@@ -16,7 +16,7 @@
16
16
  <div class="header-left">
17
17
  <h1>minicode</h1>
18
18
  <span id="status-badge" class="badge ready">ready</span>
19
- <div id="context-indicator" title="Context window usage">
19
+ <div id="context-indicator" title="Context window usage. Adjust context size in Settings if you want it larger or smaller.">
20
20
  <div id="context-bar">
21
21
  <div id="context-fill"></div>
22
22
  </div>
@@ -45,6 +45,10 @@
45
45
  <div id="session-update-row" class="session-update-row hidden">
46
46
  <button id="session-update-btn" class="dropdown-action" type="button">Update current saved session</button>
47
47
  </div>
48
+ <label id="session-autosave-row" class="session-autosave-row" title="Automatically save or update this chat after each completed turn.">
49
+ <input id="session-autosave-toggle" type="checkbox" />
50
+ <span>Auto-save after each turn</span>
51
+ </label>
48
52
  <div class="dropdown-row">
49
53
  <input id="save-label" type="text" placeholder="Label (optional)" />
50
54
  <button id="save-btn" class="dropdown-action">Save</button>
@@ -222,6 +222,20 @@ h1 {
222
222
  margin-bottom: 8px;
223
223
  }
224
224
 
225
+ .session-autosave-row {
226
+ display: flex;
227
+ align-items: center;
228
+ gap: 8px;
229
+ margin-bottom: 8px;
230
+ font-size: 12px;
231
+ color: var(--text-dim);
232
+ cursor: pointer;
233
+ }
234
+
235
+ .session-autosave-row input {
236
+ accent-color: var(--accent);
237
+ }
238
+
225
239
  .dropdown-divider {
226
240
  height: 1px;
227
241
  background: var(--border);
@@ -275,10 +289,8 @@ h1 {
275
289
  .session-item {
276
290
  display: flex;
277
291
  align-items: center;
278
- justify-content: space-between;
279
- padding: 6px 8px;
292
+ gap: 8px;
280
293
  border-radius: 4px;
281
- cursor: pointer;
282
294
  font-size: 12px;
283
295
  transition: background 0.15s;
284
296
  }
@@ -292,6 +304,28 @@ h1 {
292
304
  outline: 1px solid rgba(122, 162, 247, 0.35);
293
305
  }
294
306
 
307
+ .session-load-btn {
308
+ flex: 1;
309
+ display: flex;
310
+ align-items: center;
311
+ justify-content: space-between;
312
+ gap: 8px;
313
+ width: 100%;
314
+ padding: 6px 8px;
315
+ background: transparent;
316
+ border: none;
317
+ color: inherit;
318
+ font: inherit;
319
+ text-align: left;
320
+ cursor: pointer;
321
+ }
322
+
323
+ .session-load-btn:focus-visible,
324
+ .session-delete-btn:focus-visible {
325
+ outline: 1px solid var(--accent);
326
+ outline-offset: 1px;
327
+ }
328
+
295
329
  .session-label {
296
330
  color: var(--text);
297
331
  overflow: hidden;
@@ -310,6 +344,26 @@ h1 {
310
344
  color: var(--accent);
311
345
  }
312
346
 
347
+ .session-delete-btn {
348
+ flex-shrink: 0;
349
+ margin: 4px 6px 4px 0;
350
+ padding: 4px 8px;
351
+ background: transparent;
352
+ border: 1px solid var(--border);
353
+ border-radius: 4px;
354
+ color: var(--text-dim);
355
+ font-family: var(--font-mono);
356
+ font-size: 11px;
357
+ cursor: pointer;
358
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
359
+ }
360
+
361
+ .session-delete-btn:hover {
362
+ color: var(--red);
363
+ border-color: rgba(247, 118, 142, 0.55);
364
+ background: rgba(247, 118, 142, 0.08);
365
+ }
366
+
313
367
  .badge {
314
368
  font-size: 11px;
315
369
  padding: 2px 8px;
@@ -1,11 +1,14 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test, afterEach } from "node:test";
3
3
  import { createServer } from "node:http";
4
+ import { readFileSync } from "node:fs";
5
+ import { join } from "node:path";
4
6
  import { createRequestHandler } from "../src/serve/server.js";
5
7
  import { AgentBridge } from "../src/serve/agent-bridge.js";
6
8
  import { CodingAgent, ToolRegistry, } from "@minicode/agent-sdk";
7
9
  import { UiStore } from "../src/ui/state/ui-store.js";
8
10
  import { createTestAgentConfig } from "./test-utils.js";
11
+ const distWeb = join(import.meta.dirname, "..", "dist", "src", "web");
9
12
  // ── Mock model client ──
10
13
  class SequenceModelClient {
11
14
  responses;
@@ -111,6 +114,12 @@ test("GET /api/context reflects updated context state", async () => {
111
114
  assert.equal(body.contextTokens, 8000);
112
115
  assert.equal(body.maxContextTokens, 16000);
113
116
  });
117
+ test("built web UI explains that context size can be adjusted in Settings", () => {
118
+ const html = readFileSync(join(distWeb, "index.html"), "utf8");
119
+ const js = readFileSync(join(distWeb, "app.js"), "utf8");
120
+ assert.ok(html.includes("Context window usage. Adjust context size in Settings"), "HTML should provide a helpful default tooltip for the context indicator");
121
+ assert.ok(js.includes("Adjust context size in Settings if you want it larger or smaller."), "JS should include guidance about adjusting context size in Settings");
122
+ });
114
123
  // ── Agent emits context_status UiUpdate ──
115
124
  test("agent emits context_status UiUpdate during turn", async () => {
116
125
  const config = createTestAgentConfig("/tmp/test-workspace");
@@ -24,6 +24,7 @@ class MockBridge extends AgentBridge {
24
24
  openAiCompatibleApiKey;
25
25
  openAiCompatibleSessionActive = false;
26
26
  refreshIndexCount = 0;
27
+ deletedSessionIds = [];
27
28
  constructor() {
28
29
  super(() => { }, false);
29
30
  }
@@ -89,6 +90,13 @@ class MockBridge extends AgentBridge {
89
90
  }
90
91
  return { session, label };
91
92
  }
93
+ async deleteSess(sessionId) {
94
+ if (sessionId !== "sess-1") {
95
+ return false;
96
+ }
97
+ this.deletedSessionIds.push(sessionId);
98
+ return true;
99
+ }
92
100
  getCurrentSessionId() {
93
101
  return this._currentSessionId;
94
102
  }
@@ -536,6 +544,29 @@ test("POST /api/sessions/load returns success for known session", async () => {
536
544
  assert.equal(body.messages[9]?.content, "message-11");
537
545
  assert.ok(body.messages.every((message) => !message.content.startsWith("[Conversation Summary")));
538
546
  });
547
+ test("DELETE /api/sessions/:id deletes a saved session", async () => {
548
+ const bridge = new MockBridge();
549
+ const base = await startTestServer(bridge);
550
+ const res = await fetch(`${base}/api/sessions/sess-1`, {
551
+ method: "DELETE",
552
+ });
553
+ assert.equal(res.status, 200);
554
+ const body = (await res.json());
555
+ assert.equal(body.ok, true);
556
+ assert.equal(body.deleted, true);
557
+ assert.equal(body.id, "sess-1");
558
+ assert.deepEqual(bridge.deletedSessionIds, ["sess-1"]);
559
+ });
560
+ test("DELETE /api/sessions/:id returns 404 for unknown session", async () => {
561
+ const bridge = new MockBridge();
562
+ const base = await startTestServer(bridge);
563
+ const res = await fetch(`${base}/api/sessions/missing`, {
564
+ method: "DELETE",
565
+ });
566
+ assert.equal(res.status, 404);
567
+ const body = (await res.json());
568
+ assert.match(body.error, /Session not found/);
569
+ });
539
570
  test("POST /api/chat returns agent response", async () => {
540
571
  const bridge = new MockBridge();
541
572
  const base = await startTestServer(bridge);
@@ -4,7 +4,7 @@ import { mkdtemp, readdir, rm } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import os from "node:os";
6
6
  import { Session } from "@minicode/agent-sdk";
7
- import { DuplicateSessionLabelError, listSessions, loadSession, loadSessionByLabel, saveSession, setSessionsDir, } from "../src/session/session-store.js";
7
+ import { deleteSession, DuplicateSessionLabelError, listSessions, loadSession, loadSessionByLabel, saveSession, setSessionsDir, } from "../src/session/session-store.js";
8
8
  async function withTmpDir(fn) {
9
9
  const dir = await mkdtemp(path.join(os.tmpdir(), "minicode-test-"));
10
10
  setSessionsDir(dir);
@@ -127,3 +127,20 @@ test("saving same session twice overwrites the file", async () => {
127
127
  assert.equal(result.session.getMessages().length, 2);
128
128
  });
129
129
  });
130
+ test("deleteSession removes the saved session file", async () => {
131
+ await withTmpDir(async (dir) => {
132
+ const session = new Session("test-id");
133
+ session.addMessage({ role: "user", content: "hello" });
134
+ await saveSession(session, "delete me");
135
+ const deleted = await deleteSession("test-id");
136
+ assert.equal(deleted, true);
137
+ const files = await readdir(dir);
138
+ assert.equal(files.length, 0);
139
+ });
140
+ });
141
+ test("deleteSession returns false for a missing session", async () => {
142
+ await withTmpDir(async () => {
143
+ const deleted = await deleteSession("missing-session");
144
+ assert.equal(deleted, false);
145
+ });
146
+ });
@@ -7,11 +7,14 @@ test("built HTML contains update action for the current saved session", () => {
7
7
  const html = readFileSync(join(distWeb, "index.html"), "utf8");
8
8
  assert.ok(html.includes('id="session-update-row"'), "HTML should contain the session update row");
9
9
  assert.ok(html.includes('id="session-update-btn"'), "HTML should contain the session update button");
10
+ assert.ok(html.includes('id="session-autosave-toggle"'), "HTML should contain the auto-save sessions toggle");
10
11
  });
11
12
  test("built CSS contains active-session styling", () => {
12
13
  const css = readFileSync(join(distWeb, "style.css"), "utf8");
13
14
  assert.ok(css.includes(".session-item.active"), "CSS should style the active saved session row");
14
15
  assert.ok(css.includes(".session-active-badge"), "CSS should style the active session badge");
16
+ assert.ok(css.includes(".session-delete-btn"), "CSS should style the session delete button");
17
+ assert.ok(css.includes(".session-autosave-row"), "CSS should style the auto-save toggle row");
15
18
  });
16
19
  test("built JS contains active saved session update logic", () => {
17
20
  const js = readFileSync(join(distWeb, "app.js"), "utf8");
@@ -22,4 +25,7 @@ test("built JS contains active saved session update logic", () => {
22
25
  assert.ok(js.includes('saveBtn.setAttribute("disabled", "true")'), "JS should disable saving while the first save is in flight");
23
26
  assert.ok(js.includes("renderLoadedSessionMessages"), "JS should render session previews after load");
24
27
  assert.ok(js.includes("body.messages"), "JS should read preview messages from the load session response");
28
+ assert.ok(js.includes("SESSION_AUTOSAVE_KEY"), "JS should persist the auto-save preference");
29
+ assert.ok(js.includes("window.confirm"), "JS should confirm before deleting a saved session");
30
+ assert.ok(js.includes('method: "DELETE"'), "JS should call the delete session API");
25
31
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sean.holung/minicode",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "workspaces": [
5
5
  "packages/*"
6
6
  ],