/** * @module client/dev-tools/vcs-workspace * * Dev Tools panel button handlers for in-memory VCS Workspace Projection testing in space-gib. * Utilizes the isomorphic VcsRepository facade and WebInMemoryWorkspaceProjectionSpace_V1. */ import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs'; import { getGlobalMetaspace_waitIfNeeded } from '@ibgib/web-gib/dist/helpers.mjs'; import { WebInMemoryWorkspaceProjectionSpace_V1 } from '@ibgib/web-gib/dist/witness/space/web-in-memory-workspace-projection-space/web-in-memory-workspace-projection-space-v1.mjs'; import { InnerSpace_V1 } from '@ibgib/core-gib/dist/witness/space/inner-space/inner-space-v1.mjs'; import { DEFAULT_INNER_SPACE_DATA_V1 } from '@ibgib/core-gib/dist/witness/space/inner-space/inner-space-types.mjs'; import { getB2tFSBranchSpaceName } from '@ibgib/core-gib/dist/vcs/branch/branch-helper.mjs'; import { VcsRepository } from '@ibgib/core-gib/dist/vcs/vcs-repository.mjs'; import { devLog, lc } from './common.mjs'; interface VcsDevState { backingSpace?: InnerSpace_V1; wsSpace?: WebInMemoryWorkspaceProjectionSpace_V1; repo?: VcsRepository; } export const vcsDevState: VcsDevState = {}; (window as any).vcsDevState = vcsDevState; /** * 1. Initialize in-memory branch CAS space, VcsRepository session, and seed sample files. */ export function initVcsInitButton(): void { const btn = document.getElementById('btn-vcs-init') as HTMLButtonElement | null; if (!btn) { return; } btn.addEventListener('click', async () => { const lc_fn = `${lc}[btn-vcs-init]`; try { btn.disabled = true; devLog('⏳ Initializing in-memory branch space & sample repo via VcsRepository...'); const metaspace = await getGlobalMetaspace_waitIfNeeded(); // 1. Create dedicated in-memory branch space const branchSpaceName = getB2tFSBranchSpaceName('main'); const backingSpace = new InnerSpace_V1({ ...DEFAULT_INNER_SPACE_DATA_V1, name: branchSpaceName, spaceSubPath: branchSpaceName, uuid: 'ws_backing_cas_space_uuid', }); await backingSpace.initialized; vcsDevState.backingSpace = backingSpace; // Register backingSpace in metaspace local user spaces try { if ((metaspace as any).localUserSpaces && !(metaspace as any).localUserSpaces.includes(backingSpace)) { (metaspace as any).localUserSpaces.push(backingSpace); } if ((metaspace as any).spaces && !(metaspace as any).spaces.includes(backingSpace)) { (metaspace as any).spaces.push(backingSpace); } } catch { // Ignore } // 2. Instantiate WebInMemoryWorkspaceProjectionSpace_V1 const wsSpace = new WebInMemoryWorkspaceProjectionSpace_V1({ initialData: { name: 'web_ws_space_main', uuid: 'web_ws_uuid', workspaceRoot: '/', vcsFolderName: '.vcsgib', }, backingSpace, }); await wsSpace.initialized; vcsDevState.wsSpace = wsSpace; // 3. Create VcsRepository session const repo = new VcsRepository({ metaspace, workspaceSpace: wsSpace, localSpace: backingSpace, activeBranchSpace: backingSpace, enableBranchLocking: true, }); vcsDevState.repo = repo; // 4. Initialize repository await repo.init({ rootFolderName: 'my-app', defaultBranchName: 'main', }); // 5. Seed initial files into virtual workspace wsSpace.setVirtualFile('README.md', '# Space-Gib In-Memory Repo\n\nThis entire repository is projected in memory inside the browser.'); wsSpace.setVirtualFile('src/index.ts', 'export const app = "space-gib in-memory demo";\nconsole.log(app);'); wsSpace.setVirtualFile('src/helper.ts', 'export function add(a: number, b: number): number {\n return a + b;\n}'); wsSpace.setVirtualFile('.ibgibignore', 'node_modules/\n*.log\n'); // 6. Initial stage & commit await repo.add([], { all: true }); const commitRes = await repo.commit({ message: 'Initial repository commit' }); devLog(`✅ In-Memory Repo initialized via VcsRepository!\n Commit: ${commitRes.commitAddr.slice(0, 8)}\n Files: ${wsSpace.listVirtualPaths().join(', ')}`); // Enable action buttons setBtnDisabled('btn-vcs-list', false); setBtnDisabled('btn-vcs-status', false); setBtnDisabled('btn-vcs-modify', false); setBtnDisabled('btn-vcs-reset', false); } catch (error) { devLog(`❌ Init failed: ${extractErrorMsg(error)}`); console.error(`${lc_fn}`, error); } finally { btn.disabled = true; } }); } /** * 2. List virtual files in in-memory projection space in a single formatted card. */ export function initVcsListButton(): void { const btn = document.getElementById('btn-vcs-list') as HTMLButtonElement | null; if (!btn) { return; } btn.addEventListener('click', () => { try { const wsSpace = vcsDevState.wsSpace; if (!wsSpace) { devLog('⚠️ Repo not initialized yet. Click (1) first.'); return; } const paths = wsSpace.listVirtualPaths(); const lines: string[] = [ `📁 --- IN-MEMORY VIRTUAL FILES (${paths.length} entries) ---` ]; for (const p of paths) { const text = wsSpace.getVirtualText(p); const isDir = text === undefined || wsSpace.getVirtualFile(p)?.length === 0; if (isDir) { lines.push(` 📁 [DIR] ${p}`); } else { const preview = (text || '').replace(/\n/g, ' ').slice(0, 40); lines.push(` 📄 [FILE] ${p.padEnd(22)} (${String(text?.length || 0).padStart(3)} chars) -> "${preview}..."`); } } devLog(lines.join('\n')); } catch (error) { devLog(`❌ List failed: ${extractErrorMsg(error)}`); } }); } /** * 3. Inspect repository status via VcsRepository in a single formatted card. */ export function initVcsStatusButton(): void { const btn = document.getElementById('btn-vcs-status') as HTMLButtonElement | null; if (!btn) { return; } btn.addEventListener('click', async () => { try { const repo = vcsDevState.repo; if (!repo) { devLog('⚠️ Repo not initialized yet.'); return; } const status = await repo.status(); const lines: string[] = [ `📋 --- VCS STATUS (Branch: ${status.branchName}) ---` ]; if (status.staged.length === 0 && status.modified.length === 0 && status.deleted.length === 0 && status.untracked.length === 0 && status.pendingComments.length === 0) { lines.push(' ✨ Working tree clean (no changes)'); } else { if (status.staged.length > 0) { lines.push(` 🟢 Staged changes (${status.staged.length}):`); for (const s of status.staged) { lines.push(` staged: ${s.path}`); } } if (status.modified.length > 0) { lines.push(` 🟡 Changes not staged for commit (${status.modified.length}):`); for (const m of status.modified) { lines.push(` modified: ${m.path}`); } } if (status.deleted.length > 0) { lines.push(` 🔴 Deleted files (${status.deleted.length}):`); for (const d of status.deleted) { lines.push(` deleted: ${d.path}`); } } if (status.untracked.length > 0) { lines.push(` ⚪ Untracked files (${status.untracked.length}):`); for (const u of status.untracked) { lines.push(` untracked: ${u.path}`); } } if (status.pendingComments.length > 0) { lines.push(` 💬 Pending companion comments (${status.pendingComments.length}):`); for (const c of status.pendingComments) { lines.push(` note for: ${c.targetRelPath} (${c.commentRelPath})`); } } } devLog(lines.join('\n')); } catch (error) { devLog(`❌ Status check failed: ${extractErrorMsg(error)}`); } }); } /** * 4. Modify virtual file and add an untracked file directly in memory. */ export function initVcsModifyButton(): void { const btn = document.getElementById('btn-vcs-modify') as HTMLButtonElement | null; if (!btn) { return; } btn.addEventListener('click', () => { try { const wsSpace = vcsDevState.wsSpace; if (!wsSpace) { devLog('⚠️ Repo not initialized yet.'); return; } const lines: string[] = [ '✏️ Simulating user edits in in-browser editor...' ]; // Modify existing file const newContent = 'export const app = "space-gib (EDITED LIVE IN BROWSER!)";\nconsole.log(app);\nexport const timestamp = ' + Date.now() + ';'; wsSpace.setVirtualFile('src/index.ts', newContent); lines.push(' ✏️ Updated "src/index.ts" with new code content.'); // Add an untracked file wsSpace.setVirtualFile('src/new-component.ts', 'export const Component = () => "