skopix 2.0.89 → 2.0.91

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.
@@ -1896,6 +1896,12 @@ export async function dashboardCommand(options) {
1896
1896
  sendJSON(res, 200, folders);
1897
1897
  return;
1898
1898
  }
1899
+ if (pathname === '/api/folders' && method === 'PUT') {
1900
+ const folders = JSON.parse(await readBody(req));
1901
+ await saveFolders(folders);
1902
+ sendJSON(res, 200, folders);
1903
+ return;
1904
+ }
1899
1905
  if (pathname === '/api/folders' && method === 'DELETE') {
1900
1906
  const { name, type } = JSON.parse(await readBody(req));
1901
1907
  const folders = await getFolders();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skopix",
3
- "version": "2.0.89",
3
+ "version": "2.0.91",
4
4
  "description": "Browser-based QA tool — record tests by using your app, replay them deterministically, generate Playwright code automatically",
5
5
  "main": "cli/index.js",
6
6
  "bin": {
@@ -5738,13 +5738,16 @@ function renderFolderTree(containerId, type, selectedFolder, onSelect) {
5738
5738
  const isSelected = selectedFolder === node.fullPath;
5739
5739
  return `<div>
5740
5740
  <div class="folder-item ${isSelected ? 'folder-item--active' : ''}"
5741
- style="padding:7px 14px 7px ${14 + depth * 16}px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:12px;color:${isSelected ? 'var(--cyan)' : 'var(--text)'};background:${isSelected ? 'rgba(34,211,238,0.08)' : ''};transition:background 0.1s"
5741
+ draggable="true"
5742
+ ondragstart="onFolderReorderStart(event,'${escapeAttr(node.fullPath)}','${type}')"
5743
+ ondragover="event.preventDefault();event.stopPropagation();this.style.outline=&quot;2px solid var(--cyan)&quot;"
5744
+ ondragleave="this.style.outline=&quot;&quot;"
5745
+ ondrop="onFolderReorderDrop(event,'${escapeAttr(node.fullPath)}','${type}')"
5746
+ style="padding:7px 14px 7px ${14 + depth * 16}px;cursor:pointer;display:flex;align-items:center;gap:8px;font-size:12px;color:${isSelected ? 'var(--cyan)' : 'var(--text)'};background:${isSelected ? 'rgba(34,211,238,0.08)' : ''};transition:background 0.1s;border-top:2px solid transparent"
5742
5747
  data-folder="${escapeAttr(node.fullPath)}" data-type="${type}"
5743
5748
  onmouseover="if(!this.classList.contains('folder-item--active'))this.style.background='var(--surface2)'"
5744
- onmouseout="if(!this.classList.contains('folder-item--active'))this.style.background=''"
5745
- ondragover="event.preventDefault();this.style.background='rgba(34,211,238,0.15)'"
5746
- ondragleave="this.style.background=''"
5747
- ondrop="onFolderDrop(event,'${escapeAttr(node.fullPath)}','${type}')">
5749
+ onmouseout="if(!this.classList.contains('folder-item--active'))this.style.background=''">
5750
+ <span style="color:var(--muted2);cursor:grab;font-size:12px;flex-shrink:0" onmousedown="event.stopPropagation()">⠿</span>
5748
5751
  <svg width="13" height="13" viewBox="0 0 24 24" fill="${isSelected ? 'rgba(34,211,238,0.3)' : 'none'}" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
5749
5752
  <span style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escapeHtml(node.name)}</span>
5750
5753
  <span class="folder-actions" style="display:none;gap:4px">
@@ -5763,7 +5766,7 @@ function renderFolderTree(containerId, type, selectedFolder, onSelect) {
5763
5766
  onmouseover="if(${!allActive})this.style.background='var(--surface2)'" onmouseout="if(${!allActive})this.style.background=''"
5764
5767
  ondragover="event.preventDefault();this.style.background='rgba(34,211,238,0.15)'"
5765
5768
  ondragleave="this.style.background=''"
5766
- ondrop="onFolderDrop(event,'','${type}')">
5769
+ ondrop="if(!event.dataTransfer.getData('folder-reorder'))onFolderDrop(event,'','${type}')">
5767
5770
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/></svg>
5768
5771
  All
5769
5772
  </div>
@@ -5805,7 +5808,44 @@ document.addEventListener('click', (e) => {
5805
5808
  }
5806
5809
  });
5807
5810
 
5808
- // Drag and drop
5811
+ // Folder reordering
5812
+ let folderDragName = null;
5813
+
5814
+ function onFolderReorderStart(e, folderName, type) {
5815
+ folderDragName = folderName;
5816
+ e.dataTransfer.setData('folder-reorder', JSON.stringify({ name: folderName, type }));
5817
+ e.dataTransfer.effectAllowed = 'move';
5818
+ e.stopPropagation();
5819
+ }
5820
+
5821
+ async function onFolderReorderDrop(e, targetName, type) {
5822
+ e.preventDefault();
5823
+ e.stopPropagation();
5824
+ e.currentTarget.style.outline = '';
5825
+ const data = e.dataTransfer.getData('folder-reorder');
5826
+ if (!data) return;
5827
+ const { name: draggedName } = JSON.parse(data);
5828
+ if (draggedName === targetName) return;
5829
+ // Reorder in foldersCache
5830
+ const typeFolders = foldersCache.filter(f => f.type === type);
5831
+ const others = foldersCache.filter(f => f.type !== type);
5832
+ const dragIdx = typeFolders.findIndex(f => f.name === draggedName);
5833
+ const targetIdx = typeFolders.findIndex(f => f.name === targetName);
5834
+ if (dragIdx === -1 || targetIdx === -1) return;
5835
+ const [dragged] = typeFolders.splice(dragIdx, 1);
5836
+ typeFolders.splice(targetIdx, 0, dragged);
5837
+ foldersCache = [...others, ...typeFolders];
5838
+ // Save new order
5839
+ await fetch(API_BASE + '/api/folders', {
5840
+ method: 'PUT',
5841
+ headers: { 'Content-Type': 'application/json' },
5842
+ body: JSON.stringify(foldersCache),
5843
+ });
5844
+ if (type === 'library') renderFolderTree('library-folder-tree', 'library', selectedLibraryFolder);
5845
+ else renderFolderTree('tests-folder-tree', 'tests', selectedTestsFolder);
5846
+ }
5847
+
5848
+ // Drag items into folders
5809
5849
  function onItemDragStart(e, id, type) {
5810
5850
  e.dataTransfer.setData('text/plain', JSON.stringify({ id, type }));
5811
5851
  e.dataTransfer.effectAllowed = 'move';
@@ -6777,10 +6817,30 @@ async function saveBuiltTest() {
6777
6817
  }));
6778
6818
 
6779
6819
  try {
6820
+ showToast('Processing steps...');
6821
+
6822
+ // Run through LLM to generate Playwright code
6823
+ let processedSteps = steps;
6824
+ let playwrightJs = '';
6825
+ let playwrightTs = '';
6826
+ try {
6827
+ const procRes = await fetch(API_BASE + '/api/record/process', {
6828
+ method: 'POST',
6829
+ headers: { 'Content-Type': 'application/json' },
6830
+ body: JSON.stringify({ steps, testName: name, url }),
6831
+ });
6832
+ if (procRes.ok) {
6833
+ const proc = await procRes.json();
6834
+ processedSteps = proc.steps || steps;
6835
+ playwrightJs = proc.playwrightJs || '';
6836
+ playwrightTs = proc.playwrightTs || '';
6837
+ }
6838
+ } catch {}
6839
+
6780
6840
  const res = await fetch(API_BASE + '/api/record/save', {
6781
6841
  method: 'POST',
6782
6842
  headers: { 'Content-Type': 'application/json' },
6783
- body: JSON.stringify({ scope: 'saved', name, url, steps, tags: [] }),
6843
+ body: JSON.stringify({ scope: 'saved', name, url, steps: processedSteps, playwrightJs, playwrightTs, tags: [] }),
6784
6844
  });
6785
6845
  if (!res.ok) throw new Error(await res.text());
6786
6846
  showToast(`Test "${name}" saved`);