mcprigor 1.5.0 → 1.5.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.
@@ -14,9 +14,10 @@ async function api(path, options = {}) {
14
14
  async function start() {
15
15
  const boot = await api('/api/v1/bootstrap');
16
16
  state.csrf = boot.csrf;
17
+ state.rootPath = boot.rootPath || '';
17
18
  state.canPublish = (boot.capabilities || []).includes('publish');
18
19
  $('version').textContent = `v${boot.version}`;
19
- $('workspace').textContent = `Folder: ${boot.root}`;
20
+ setWorkspaceDisplay(boot.root, boot.rootPath || boot.root);
20
21
  $('connection').classList.add('ok');
21
22
  $('connection').innerHTML = '<span class="dot"></span>Local · ready';
22
23
  const count = await refreshList();
@@ -509,6 +510,72 @@ $('rename-cancel').onclick = () => $('rename-dialog').close();
509
510
  $('rename').onclick = () => { if (state.path) openRename(state.path); };
510
511
  $('filename').ondblclick = () => { if (state.path) openRename(state.path); };
511
512
 
513
+ /* ---------- workspace folder switching ---------- */
514
+ const folder = { path: '' };
515
+ function setWorkspaceDisplay(name, fullPath) {
516
+ $('workspace-name').textContent = name;
517
+ $('workspace-path').textContent = fullPath;
518
+ $('workspace').title = `${fullPath} — click to switch workspace`;
519
+ }
520
+ function folderError(message) { $('folder-error').textContent = message; $('folder-error').hidden = false; }
521
+ async function browseFolder(path) {
522
+ $('folder-error').hidden = true;
523
+ const value = await api(`/api/v1/folders${path ? `?path=${encodeURIComponent(path)}` : ''}`);
524
+ folder.path = value.path;
525
+ if (value.current) {
526
+ $('folder-current').hidden = false;
527
+ $('folder-current-name').textContent = value.current.root;
528
+ $('folder-current-path').textContent = value.current.rootPath;
529
+ $('folder-current-count').textContent = `${value.current.suiteCount} test file${value.current.suiteCount === 1 ? '' : 's'}`;
530
+ }
531
+ $('folder-path').value = value.path;
532
+ $('folder-up').disabled = !value.parent;
533
+ $('folder-up').onclick = () => { if (value.parent) browseFolder(value.parent).catch(e => folderError(e.message)); };
534
+ $('folder-info').textContent = value.suiteCount ? `${value.suiteCount} test file${value.suiteCount === 1 ? '' : 's'} found in this folder` : 'No test files here yet — you can create one after switching.';
535
+ const list = $('folder-list');
536
+ list.replaceChildren();
537
+ if (!value.folders.length) { const p = document.createElement('p'); p.className = 'empty'; p.textContent = 'No subfolders.'; list.append(p); }
538
+ for (const name of value.folders) {
539
+ const button = document.createElement('button');
540
+ button.type = 'button';
541
+ button.className = 'folder-item';
542
+ button.textContent = `📁 ${name}`;
543
+ button.onclick = () => browseFolder(`${value.path === '/' ? '' : value.path}/${name}`).catch(e => folderError(e.message));
544
+ list.append(button);
545
+ }
546
+ }
547
+ function openFolderDialog() {
548
+ $('folder-error').hidden = true;
549
+ $('folder-dialog').showModal();
550
+ browseFolder(state.rootPath || '').catch(e => folderError(e.message));
551
+ }
552
+ async function switchWorkspace(path) {
553
+ const value = await api('/api/v1/workspace', { method: 'POST', body: JSON.stringify({ path }) });
554
+ state.rootPath = value.rootPath;
555
+ state.path = ''; state.etag = ''; state.dirty = false; state.selected.clear(); state.run = null; state.runSel = -1; state.history = [];
556
+ $('editor').value = ''; syncEditor();
557
+ $('filename').textContent = 'No file open'; $('filename').classList.remove('open');
558
+ $('dirty').textContent = ''; $('rename').hidden = true;
559
+ $('run-list').replaceChildren(); $('export-run').hidden = true;
560
+ $('output').innerHTML = '<span class="muted">Nothing has run yet.\nOpen a test file and click ▶ Run tests.</span>';
561
+ setWorkspaceDisplay(value.root, value.rootPath);
562
+ hideDiag(); markErrorLine(0); setEnabled(false);
563
+ const count = await refreshList();
564
+ showWelcome(!count);
565
+ loadHistory().catch(() => {});
566
+ toast(`Workspace: ${value.root}`);
567
+ }
568
+ $('workspace').onclick = openFolderDialog;
569
+ $('folder-cancel').onclick = () => $('folder-dialog').close();
570
+ $('folder-home').onclick = () => browseFolder('').catch(e => folderError(e.message));
571
+ $('folder-go').onclick = () => browseFolder($('folder-path').value.trim()).catch(e => folderError(e.message));
572
+ $('folder-path').addEventListener('keydown', event => { if (event.key === 'Enter') { event.preventDefault(); $('folder-go').click(); } });
573
+ $('folder-form').addEventListener('submit', event => {
574
+ event.preventDefault();
575
+ if (state.dirty && !confirm('Discard unsaved changes?')) return;
576
+ switchWorkspace($('folder-path').value.trim() || folder.path).then(() => $('folder-dialog').close()).catch(error => folderError(error.message));
577
+ });
578
+
512
579
  /* ---------- wiring ---------- */
513
580
  $('editor').addEventListener('input', () => { state.dirty = true; $('dirty').textContent = '● unsaved'; markErrorLine(0); openSuggest(); });
514
581
  $('editor').addEventListener('blur', () => setTimeout(closeSuggest, 150));
@@ -38,7 +38,7 @@
38
38
  <button id="batch-clear" class="ghost small">Clear selection</button>
39
39
  </div>
40
40
  <div class="sidebar-foot">
41
- <a href="https://mcprigor.com/docs/plain-language-cookbook.html" target="_blank" rel="noreferrer">📖 Cookbook — copy-ready examples</a>
41
+ <a href="https://mcprigor.com/docs/natural-language-cookbook.html" target="_blank" rel="noreferrer">📖 Cookbook — copy-ready examples</a>
42
42
  </div>
43
43
  </aside>
44
44
  <div class="splitter" id="split-left" role="separator" aria-orientation="vertical" aria-label="Resize file list" tabindex="0"></div>
@@ -120,10 +120,42 @@ with their checkboxes and run them together.</span></pre>
120
120
  </main>
121
121
 
122
122
  <footer>
123
- <span id="workspace"></span>
123
+ <button id="workspace" class="workspace-btn" title="Switch workspace folder">
124
+ <span class="workspace-label">Workspace</span>
125
+ <span id="workspace-name" class="workspace-name"></span>
126
+ <span id="workspace-path" class="workspace-path"></span>
127
+ </button>
124
128
  <span class="foot-right">Natural language and YAML share one runtime model</span>
125
129
  </footer>
126
130
 
131
+ <dialog id="folder-dialog">
132
+ <form method="dialog" id="folder-form">
133
+ <h3>Switch workspace folder</h3>
134
+ <p>Test files, history, and trends come from the selected folder.</p>
135
+ <div id="folder-current" class="folder-current" hidden>
136
+ <span class="folder-current-label">Current workspace</span>
137
+ <span id="folder-current-name" class="folder-current-name"></span>
138
+ <span id="folder-current-path" class="folder-current-path"></span>
139
+ <span id="folder-current-count" class="folder-current-count"></span>
140
+ </div>
141
+ <div class="folder-path-row">
142
+ <input id="folder-path" type="text" autocomplete="off" spellcheck="false" aria-label="Folder path" placeholder="/path/to/your/tests">
143
+ <button type="button" id="folder-go" class="ghost" title="Open typed path">Go</button>
144
+ <button type="button" id="folder-home" class="ghost" title="Jump to home folder">🏠</button>
145
+ </div>
146
+ <div class="folder-browser">
147
+ <button type="button" id="folder-up" class="folder-up">⬆ Parent folder</button>
148
+ <div id="folder-list" class="folder-list" role="listbox" aria-label="Subfolders"></div>
149
+ </div>
150
+ <p id="folder-info" class="dialog-hint"></p>
151
+ <p id="folder-error" class="dialog-error" hidden></p>
152
+ <div class="dialog-actions">
153
+ <button type="button" id="folder-cancel" class="ghost">Cancel</button>
154
+ <button type="submit" class="primary" id="folder-use">Use this folder</button>
155
+ </div>
156
+ </form>
157
+ </dialog>
158
+
127
159
  <dialog id="new-dialog">
128
160
  <form method="dialog" id="new-form">
129
161
  <h3>New test file</h3>
@@ -234,3 +234,27 @@ nav .suite-row button .rn:hover{opacity:1}
234
234
  .export-bar .small{font-size:11px;padding:3px 10px}
235
235
  .published-link{font-size:11px;margin-left:4px;color:var(--accent, #2563eb);text-decoration:none}
236
236
  .published-link:hover{text-decoration:underline}
237
+
238
+ /* ---------- workspace folder switching ---------- */
239
+ .workspace-btn{display:flex;align-items:center;gap:7px;max-width:60vw;background:none;border:none;color:var(--text-faint);font:inherit;font-size:11.5px;cursor:pointer;padding:2px 8px;border-radius:6px}
240
+ .workspace-btn:hover{color:var(--text);background:var(--bg-input)}
241
+ .workspace-btn::after{content:"▾";opacity:.7}
242
+ .workspace-label{text-transform:uppercase;letter-spacing:.4px;font-size:10px;color:var(--text-faint)}
243
+ .workspace-name{font-weight:600;color:var(--text)}
244
+ .workspace-path{font-family:var(--mono, ui-monospace, monospace);font-size:10.5px;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
245
+ #folder-dialog{width:min(560px,92vw)}
246
+ .folder-path-row{display:flex;gap:6px;margin-bottom:10px}
247
+ .folder-path-row input{flex:1;font-family:var(--mono, ui-monospace, monospace);font-size:12.5px}
248
+ .folder-browser{border:1px solid var(--line);border-radius:10px;overflow:hidden;margin-bottom:10px}
249
+ .folder-up{display:block;width:100%;text-align:left;background:var(--bg-raised);border:none;border-bottom:1px solid var(--line-soft);border-radius:0;padding:8px 12px;font-size:12.5px;color:var(--text-dim);cursor:pointer}
250
+ .folder-up:hover:not(:disabled){color:var(--text);background:var(--bg-input)}
251
+ .folder-up:disabled{opacity:.4;cursor:default}
252
+ .folder-list{max-height:220px;overflow-y:auto;padding:4px}
253
+ .folder-item{display:block;width:100%;text-align:left;background:none;border:none;border-radius:6px;padding:6px 10px;font-size:13px;color:var(--text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
254
+ .folder-item:hover{background:var(--bg-input)}
255
+ .folder-list .empty{margin:10px}
256
+ .folder-current{display:grid;grid-template-columns:auto 1fr auto;align-items:center;column-gap:10px;background:var(--bg-raised);border:1px solid var(--line-soft);border-radius:8px;padding:7px 11px;margin-bottom:12px;font-size:12.5px}
257
+ .folder-current-label{color:var(--text-faint);font-size:11px;text-transform:uppercase;letter-spacing:.4px}
258
+ .folder-current-name{color:var(--text);font-weight:600}
259
+ .folder-current-count{margin-left:auto;color:var(--text-dim)}
260
+ .folder-current-path{grid-column:1 / -1;font-family:var(--mono, ui-monospace, monospace);font-size:11px;color:var(--text-dim);word-break:break-all;margin-top:2px}