StructureGen
A zero-backend, browser-native project scaffolding tool with a VS Code-style interface — paste an ASCII folder tree or build one manually, edit file contents in a multi-tab code editor, drag and drop nodes to reorganize, right-click for a context menu, and export a real, fully populated ZIP file using JSZip. No server, no CLI, instant download.

Introduction
Every time I start a new project, I spend the first 20 minutes doing the same thing: creating folders, touching files, writing boilerplate. For projects where I already know the structure upfront — or when I want to scaffold something from an ASCII tree I found in a README — there's no good browser-based tool that lets you go from structure to downloadable ZIP in one step.
StructureGen is that tool. It's a browser-native, VS Code-inspired project scaffolding environment where you can paste an ASCII folder tree, build a structure manually, edit file contents in a tabbed code editor, reorganize nodes with drag and drop, and export a real, fully populated ZIP file — all without installing anything or leaving the browser.
What it does
StructureGen has three layers that work together: a file explorer sidebar, a multi-tab code editor, and an import/export system.
- ASCII tree import — paste any Unix-style
treecommand output (with├──,└──,│characters) into the import modal and the tool parses it into a live, interactive node tree. Depth is inferred from leading whitespace and tree drawing characters. - Manual structure building — add root folders and files from the topbar. Add children from the inline node action buttons or right-click context menu. Every new node is immediately placed into rename mode.
- Inline rename — click the rename button or press Enter in the rename input to confirm. Press Escape to cancel and revert to the previous name. The rename input auto-focuses and selects all text when activated.
- Drag and drop reorganization — drag any node to reorder it (before/after a sibling) or move it inside a folder. Drop position is determined by the cursor's vertical position within the target row: top 25% = before, bottom 25% = after, middle 50% on a folder = inside. Drop indicators render as highlighted lines above/below the target.
- Right-click context menu — on any node, right-clicking opens a context menu with options to add a folder or file inside (for folders), add a sibling before or after, rename, or delete. The menu closes on any click outside.
- Multi-tab code editor — clicking a file opens it in a tab in the editor pane. Multiple files can be open simultaneously. Each tab shows the file icon, name, and a close button. The editor is a styled
<textarea>with Tab key support (inserts 2 spaces instead of moving focus), file path breadcrumb, and language label. - Boilerplate file templates — new files get intelligent starter content based on their name and extension.
package.jsongets a full JSON scaffold.next.config.js,tailwind.config.js,postcss.config.js, andmiddleware.jsget their standard boilerplate..envfiles get a comment header..gitignoregets common ignore patterns. JS/TS files get a default export component stub. HTML files get a full DOCTYPE skeleton. - ZIP export with JSZip — clicking Export ZIP dynamically imports JSZip, walks the entire node tree, reconstructs full file paths, adds each file with its current editor content, and generates a compressed
.zipdownload named after the root folder. Folders are explicitly created in the ZIP so empty directories are preserved. - Collapse/expand folders — click any folder row to toggle its children. The arrow indicator rotates to show open/closed state. Collapsing a folder with a dragged node inside still preserves the drop target logic.
- Delete with cascade — deleting a folder recursively collects all descendant IDs and removes them from the node map, closes any open tabs for deleted files, and removes the node from its parent's children array in a single state update.
- Toast notifications — every significant action (import, delete, rename, export, error) shows a short-lived toast in the bottom-right corner. Toasts auto-dismiss after 2.8 seconds and are replaced immediately if a new one fires before the previous one clears.
Tech stack
- Next.js 14 (App Router) — framework, build pipeline, and Vercel deployment. The entire application is a single
'use client'component — no SSR, no server actions. - React 18 —
useState,useEffect,useCallback,useRef. No external state management. The entire node tree is stored as a flatnodeMap(id → node object) plus arootIdsarray — a normalized structure that makes insertions, deletions, and moves O(1) without deep cloning. - JSZip (dynamic import) — loaded on demand only when the user clicks Export ZIP, via
import('jszip'). This keeps the initial bundle lean — JSZip (~100KB) is never loaded unless needed. - Custom CSS (no Tailwind) — the entire UI is styled with CSS custom properties and class-based rules. The VS Code dark aesthetic is implemented directly without a utility framework.
- FileSystem Entry API / Drag API — native browser drag and drop events (
dragstart,dragover,dragleave,drop,dragend) drive the tree reorganization system. - Blob + URL.createObjectURL — ZIP file download is generated entirely client-side.
- Vercel — deployment and hosting.
Data model
The entire file system is represented as a flat normalized map — not a nested tree. This is the most important architectural decision in the project.
// Each node in the nodeMap:
{
id: 'n42',
name: 'components',
isFolder: true,
parentId: 'n7', // null for root nodes
children: ['n43','n44'], // only for folders
content: '' // only for files
}
// rootIds: string[] — ordered list of top-level node IDs
// nodeMap: Record — all nodes indexed by ID
A flat map means inserting a node into the middle of a sibling list, moving a node to a different parent, or deleting a subtree are all done by updating a few array entries and object references — no recursive tree mutation, no deep cloning on every operation. The tradeoff is that rendering the tree requires a recursive TreeNode component that looks up children by ID, but React handles this efficiently with stable keys.
The ASCII tree parser
The parseStructure function converts pasted ASCII tree text into the flat nodeMap + rootIds representation. It processes the input line by line, using indentation depth to determine parent-child relationships:
for (const line of lines) {
// Strip tree-drawing chars (├ └ ─ │) to get clean indentation
const cleaned = line.replace(/[├└]/g, ' ').replace(/─/g, ' ').replace(/│/g, ' ')
const depth = Math.floor(leading / 4)
const name = line.replace(/[│├└─]/g, '').trim()
const isFolder = name.endsWith('/')
// Stack tracks the current ancestor chain by depth
while (stack.length && stack[stack.length - 1].depth >= depth) stack.pop()
const parentId = stack.length ? stack[stack.length - 1].id : null
const node = makeNode(cleanName, isFolder, parentId)
if (isFolder) stack.push({ id: node.id, depth })
}
The stack is the key — it maintains the current ancestor chain. When a line is shallower than the previous one, ancestors are popped until the stack matches the current depth. This handles arbitrary nesting without recursion and correctly processes the tree in a single O(n) pass.
Drag and drop implementation
The drag and drop system uses native HTML5 drag events wired up on each TreeNode row. The dragged node's ID is tracked in a ref (not state) to avoid the stale closure problem in drag event handlers — state updates in dragstart don't propagate to dragover handlers that closed over the initial value.
// Drop position determined by cursor Y within the target row
const y = e.clientY - rect.top
const h = rect.height
if (isFolder && y > h * 0.25 && y < h * 0.75) pos = 'inside'
else if (y <= h * 0.5) pos = 'before'
else pos = 'after'
The moveNode function handles all three cases: moving inside a folder (change parentId, append to folder's children), moving before a sibling (find target index in parent's children array, splice at that index), moving after a sibling (splice at index + 1). It also guards against invalid moves — dropping a node onto itself or onto one of its own descendants — using the isDescendant helper that walks up the parent chain.
Tab management
The tab system mirrors how VS Code manages open files. Each tab is a { id, nodeId } object stored in the openTabs array, with activeTabId tracking which tab is focused. Opening a file that's already in a tab activates that tab rather than opening a duplicate. Closing a tab activates the tab to the left, or the first remaining tab if the closed tab was first. If no tabs remain, the editor pane shows the "select a file" empty state.
Tab content is kept in the nodeMap rather than in the tab objects — so edits to a file persist even after the tab is closed and reopened. The <textarea> uses defaultValue (not value) with a key={activeTab.nodeId} to reset the DOM value when switching between files, avoiding stale content rendering without triggering a controlled input re-render on every keystroke.
ZIP export with dynamic import
The export pipeline is triggered by a single button click and runs entirely client-side:
const JSZip = (await import('jszip')).default // loaded on demand
const zip = new JSZip()
Object.values(nodeMap).forEach(node => {
const path = getFullPath(node.id, nodeMap) // reconstruct full path
node.isFolder
? zip.folder(path)
: zip.file(path, node.content || '')
})
const blob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE' })
// → trigger download
The getFullPath function reconstructs the full path for any node by recursively walking up the parentId chain and joining names with /. Empty folders are added explicitly with zip.folder(path) so they appear in the ZIP even with no files inside. The ZIP is named after the root node — so if your tree starts with crewera-next/, the download is crewera-next.zip.
Windows SmartScreen notice
One non-obvious challenge with generating ZIP files containing .js files client-side is that Windows SmartScreen may block or warn about extracted JavaScript files from an unrecognized source. The tool displays a persistent notice at the bottom of the page with two fixes: right-click the ZIP → Properties → Unblock, or extract with 7-Zip or WinRAR. This is a real friction point for Windows users and worth surfacing prominently rather than leaving them to debug it themselves.
What I'd improve
- Monaco Editor integration — the current editor is a styled
<textarea>with Tab key support. Replacing it with Monaco (the engine behind VS Code) would add real syntax highlighting, auto-indentation, bracket matching, and IntelliSense — making the file editing experience genuinely useful rather than functional. - Persist state to localStorage — the node tree and file contents reset on page refresh. Serializing the
nodeMapandrootIdstolocalStorageon every change would let users pick up where they left off without re-importing. - Template gallery — a set of pre-built structure templates (Next.js app router, Express API, Python FastAPI, monorepo) that users can load as a starting point instead of pasting a tree manually.
- Shareable URL — encoding the current structure as a compressed, base64-encoded URL parameter would let users share a specific scaffold with a link, without any backend.
- Multi-select and bulk operations — currently you can only operate on one node at a time. Shift-click or Ctrl-click selection with bulk move, delete, and copy would significantly speed up large restructuring operations.
- Undo/redo — the flat normalized state model makes this very tractable. Each mutation produces a new state snapshot; storing a history array with a pointer would enable full undo/redo with minimal code.
Conclusion
StructureGen started as a personal frustration — I kept wanting to go from an ASCII tree to a real folder structure without opening a terminal. Building it taught me a lot about normalized state design, the subtleties of the HTML5 drag and drop API (especially the stale ref problem in drag handlers), how to parse structured text with a stack-based algorithm, and the practical limits of <textarea> as a code editor. The tool is genuinely useful in my own workflow, which is the best kind of project to build.
// Tech Stack