A local workbench for words, code, and sketches. Made for Green Shoe Garage.
New or open → edit → preview when useful → save or export.
TYPEBENCH is a static, local-first text and code editor. It works without an account, subscription, application backend, database server, build step, CDN, or external editing service. CodeMirror 6 provides the editing surface. All runtime dependencies are bundled locally. Hosting the app does not store your documents on the server.
Unzip the release. Upload these together, retaining their folder structure:
index.htmlat the root of your chosen directory- the entire
assets/folder sw.jsREADME.md,LICENSE,THIRD-PARTY-NOTICES.md, andlicenses/
The remaining folders contain editable source, tests, and release evidence. The ZIP can also be committed directly as a GitHub repository. No build is needed to use the supplied release.
Root hosting and a subdirectory such as https://example.com/typebench/ both work. Use the directory URL with its trailing slash. Serve .js as JavaScript and .css as CSS. HTTPS is needed for service-worker offline setup on a public server. Localhost is also allowed.
For local use with Python installed, open a terminal in the extracted directory:
python3 -m http.server 8080Open http://localhost:8080/. On Windows, use py -m http.server 8080 if appropriate. This is a static file server, not an application backend. There is no document-upload endpoint. Stop it with Ctrl+C.
Double-clicking index.html is only a limited fallback: basic editing and downloads may work, but browser storage for file URLs varies, worker-based search/preview/snippets/ZIP may be blocked, and service-worker installation is unavailable. Use HTTPS hosting or localhost for the complete editor.
When updating a server, replace the deployable files as one release, ideally with an atomic directory switch. Uploading only index.html is insufficient.
- New creates a blank text document. New Markdown is available on the empty workbench and in Commands. Optional examples are explicitly loaded and never appear in a new workspace by themselves.
- Open files imports one or several local text files. Drag and drop works too. Duplicate open names receive a suffix. Use Import folder or drop a folder to import a whole hierarchy of text/code files, preserving its root name and relative paths.
- Project files open in independent tabs, with modified markers, selections, scroll positions, and undo/redo histories. Rename, duplicate, close, recover, and all other actions are searchable in Commands.
- Right-click a document tab or file row, or use its ⋯ button, for Open, Pin/unpin, Close tab, Close other unpinned tabs, Save, Download, Rename, Move, Duplicate, and Delete from workspace. The menu targets the clicked file, even if another tab is active. Keyboard: Shift+F10 or the Menu key, then arrows/Home/End, first-letter navigation, and Enter; Escape or Tab dismisses it. F2 renames the focused file.
- Rename / move supports relative paths such as
sketches/blink.ino, which appear as nested folders in the document panel. This changes the workspace name, not a disk file's location. Renaming detaches a direct-save handle so the next Save asks for a destination. - Line numbers, current-line highlighting, bracket matching, automatic bracket closing, indentation, word wrap, font size, whitespace display, line editing, and comment commands are built in.
- Easy exposes the main workflow. Advanced additionally exposes indentation style/width, Tab-to-indent, and whitespace settings. All commands remain searchable in both modes. Changing modes never changes document content.
- Dark, paper-light, and high-contrast themes, panel sizes, and settings persist. Mobile supports viewing and basic editing; split view stacks vertically on narrow screens. The desktop split divider is adjustable by pointer or keyboard.
Closing a tab keeps its file in the project, including unsaved edits. Open it again from the tree, Quick Open, or Recently closed. Delete from workspace is a separate, confirmed action that moves a file to recoverable storage; it never deletes disk/server files. Recovery preserves text and, within the same session, undo history. Fresh Start permanently removes all project files, recovery copies, folders, and saved snippets from this browser workspace after confirmation, with an export option. It does not delete disk files.
Settings → Automatically close brackets is enabled by default. Typing (, [, or { inserts the corresponding closer; typing a closer at an automatically inserted one moves over it. Typing an opener around a selection wraps it. Backspace between a pair deletes both as a separate undo step. CodeMirror chooses when pairing is appropriate; it does not fix unmatched brackets already in a file. Quotes and < > are not auto-paired. Imports and pastes are not rewritten. Turn the setting off for literal entry. Enter between braces uses the existing indentation rules.
Split editor opens a second project file beside the current file. Choose any project file, including one with a closed tab. Each pane has a filename selector, independent selection, scroll position, and undo history. The accented pane heading identifies the target of Save, Download, Find, language selection, history, snippets, and other commands. Clicking a tab opens it in the focused pane; selecting a file already visible focuses its existing pane. A file is shown in at most one pane.
Use Focus other editor pane or Keep focused editor only in Commands, or the × in the second pane heading. The divider is draggable and keyboard adjustable with arrow keys. The layout, ratio, selected files, and focused pane survive refresh and workspace JSON. At narrow widths the panes stack vertically. Markdown's Source/Split/Preview controls refer to source-versus-preview: choosing Markdown preview returns to one code editor. Reopen Split editor when you want two source files again.
Multiple selections: Command/Ctrl+D selects the word at the cursor, then adds the next matching occurrence on each press, wrapping at the end. Command/Ctrl+Option/Alt+Up/Down adds cursors above/below; Alt+drag creates a rectangle. Typing, indentation, comments, line commands, and snippet insertion apply across selections, with grouped undo. Keep main selection only clears extra selections. The status bar shows the selection count and total UTF-16 characters selected. Multiple selections persist across refresh. Markdown formatting and Create snippet use the main selection; Find/Go to line replace the active selection as usual.
Folding: click a gutter arrow or use Fold/Unfold selection and Fold/Unfold all code in Commands. Folding hides text without deleting or changing exports. Tree-based modes use their bundled syntax parser's fold regions. Processing, Arduino, C, C#, Go, Ruby, shell, PowerShell, and TOML use an indentation-based fallback; flush-left blocks and same-line constructs may not fold, and unusual indentation can produce imperfect regions. Plain text has no folding rules. Fold positions remain in the in-session document state, but are not restored after refresh.
Local completions: Ctrl+Space (also Option+backtick or Option+I on Mac), or Show local completions, opens suggestions. Choose with arrows/Enter or click; Escape dismisses. Suggest code while typing is opt-in in Settings. Suggestions include bundled language-package completions where available, common Processing/Arduino names, and words near the cursor in the current file. The word scan covers at most 50,000 UTF-16 characters on each side, collects at most 2,000 distinct words, and omits words over 128 characters to keep long-line suggestions practical. These limits affect suggestions, never imports or stored text. Results do not inspect other project files, resolve types, validate board/library APIs, or contact a server. There is no AI service, compiler, language server, or semantic error checking.
The document panel is a nested folder tree. + Folder accepts a relative path such as Robot/src/drivers; missing parents are created. Folder disclosure buttons expand/collapse their contents. Expand all folders and Collapse all folders are in Commands. Sizes and collapsed states persist across reloads. Empty folders are kept in workspace JSON and document ZIPs.
Right-click a folder, use its ⋯ button, or press Shift+F10/Menu on its row to create a text/Markdown file, create a subfolder, import a folder inside it, rename/move the whole subtree, or delete the folder from the workspace. Enter a full relative destination path to move a file or folder. F2 renames the focused row. Arrow Up/Down and Home/End navigate visible rows; Right expands/enters a folder and Left collapses/goes to its parent. The tree also uses ordinary keyboard-focusable buttons.
Import folder opens the browser's directory chooser. You can also drop an entire folder onto the editor. Both recursively read text/code files, including nested Processing .pde and Arduino .ino sketches. A progress dialog offers cancellation and a downloadable report of skipped items, unreadable files, and collision renames. Cancel during scanning/reading adds nothing. Successful text files are committed together; unreadable or binary files are reported and skipped. Reimporting Robot creates Robot (2) rather than overwriting the first copy. New path operations prevent case-only or file/folder collisions that would make ZIP extraction ambiguous.
“Import” is local: nothing is uploaded to your server. The importer reads working copies, not a live link to the directory. Editing, renaming, moving, or deleting them does not mutate the original folder. Save individual files with the normal Save controls, export ZIP, or explicitly use Folder connection for write-back. Ordinary Import folder never connects disk access. There is no filesystem watcher. Renaming a subtree detaches any affected direct-save handles.
Folder import preserves supported encodings, BOMs, Unicode, whitespace, and line endings. Common binary formats (images, archives, executables, fonts, etc.) are skipped; strict text decoding also rejects invalid encodings and NUL-containing data. SVG and TypeScript are treated as source even when the browser reports an image/video MIME type. This is a text editor, so exported ZIPs contain the imported text documents and folders, not a complete binary-asset backup of the original project. Hidden/generated text files are not silently excluded; select an appropriate folder and review the report.
Folder pickers supply relative filenames but do not expose empty directories. Directory drops retain empty folders when the browser exposes them; + Folder can create them manually. Browsers that cannot expose directory contents cannot recursively import a drop. The app detects unsupported folder picking and keeps Open files available. Picker/drop behavior on mobile depends on the browser and operating system. Directory drops have been tested in Chromium; other browsers and real OS picker dialogs still need device qualification.
Deleting a folder requires confirmation and moves its documents and empty subfolders to Recently closed as a recoverable group. Restore the whole folder there, including after refresh, or restore individual documents. If its former path is occupied, restoration gives the group a new root name. Closing an individual tab keeps its file in the project. A folder import opens only its first file; the rest stay in the tree until needed. Ordinary multi-file Open explicitly opens the selected files as tabs.
Quick Open (Command/Ctrl+P) searches filenames and relative paths with fuzzy matching. Recently used files appear first when the query is empty. Use arrows and Enter to open a result; Escape closes the menu. The first 100 results are shown with Show more for the rest. Clicking the path above the editor opens Quick Open with its parent folder as the query.
Symbols in current file (Command/Ctrl+Shift+O) navigates declarations and Markdown headings. Supported modes are JavaScript, TypeScript, Python, Java, Processing, C, C++, Arduino, Rust, and Markdown. It uses bundled syntax parsers, including a Java wrapper for Processing sketches. This is a syntactic outline, not semantic go-to-definition, a cross-file index, or a compiler. Complex declarations/dialect extensions can be missed. Unsupported languages offer Find/Go to line instead. Symbol parsing runs in a worker with a five-second timeout.
Pin / unpin current tab keeps important tabs at the front. Close other/all unpinned tabs leaves pins open; explicitly closing a pinned tab still closes it. Tab order, pins, and the last 100 recently used/closed-tab entries persist. Closing every tab leaves all project files available. Only opened files get editor states; visited files retain their states for in-session undo. All source text remains in memory.
Select files / folders exposes tree checkboxes and Move/Delete controls. Command/Ctrl-click toggles a row; Shift-click selects a visible range; Space toggles in selection mode. Selecting a folder includes its descendants for move/delete, so selecting both parent and child does not duplicate the operation. Move chooses an existing destination folder or project root. New folders can be created beforehand. File/folder context menus also offer moves.
Drag a tree row or a selected group onto a folder, onto a file's parent, or onto the Drop here for project root target. The Move dialog provides the keyboard/touch alternative. Moves update browser paths together and reject collisions or cycles without partial changes. They detach direct-save handles for moved files. Undo move in the project panel restores the last move in this session if paths have not subsequently changed; source edits do not prevent that undo. Batch deletion is confirmed and recoverable. None of these actions changes the original disk directory.
The tree renders expanded branches only. This reduces hidden DOM work without restricting project file count. Expand all on a very large project may still be slow; keep unused branches collapsed.
Search project (Command/Ctrl+Shift+F) searches all project files, including closed tabs. Choose a folder scope and optional comma-separated include/exclude globs: * matches within a path segment, ** crosses directories, and ? matches one character. For example, include **/*.ino, **/*.pde or exclude **/generated/**. By default, search excludes **/node_modules/** and **/.git/**; this does not exclude them from imports or exports. Scope and path filters persist.
Match case, Unicode-aware whole word, and regular expressions are available. Regex replacement supports the same captures as document Find. Search / preview makes no edits: inspect before/after excerpts, use Line buttons to open matches, select the files to change, then Apply to selected files. Selection is per file, not per individual match. Results initially display 50 files and 20 matches per file, with Show more controls. Preview excerpts show at most 250 characters; open the source for full context.
Apply verifies every chosen file still matches its preview, stages all changes, then commits the batch. A stale preview changes nothing. Untouched whitespace and mixed line endings remain exact; inserted line breaks use each document's preferred ending. Opened files receive a grouped editor undo step. Roll back project replace restores the last successful batch in this session only if none of those files has since changed or disappeared. It refuses to overwrite subsequent edits. Refresh/import/Fresh Start clears batch rollback; this is not persistent version history or semantic rename.
Search runs in a cancelable local worker. It stops after five seconds without progress and limits a result set to 50,000 matches to bound replacement memory. A broad query reports the limit rather than silently applying a partial result; narrow the scope/query and retry. These are search guards, not document import-size/file-count restrictions. Source snapshots and previews consume memory, so very large searches can still be expensive.
Select text or code → Create snippet → style → copy or download. The button above the editor and Commands → Create snippet from selection both use the current selection. Select all if you want the entire document. Snippet creation never edits the source or changes its file-save status.
The preview inherits the document language and uses all 24 bundled modes, including Processing and Arduino. Syntax coloring parses the original document in a local worker, then crops to the selection, so a fragment of a multiline comment retains its color. Only the selected text and its color ranges are saved/exported; surrounding source is not included. No code runs. A slow parser stops after about 5 seconds and offers plain-text exports or a 15-second retry.
Choose dark, light, or high-contrast colors, font size, padding, tab width, background color or transparency, an optional filename caption, line numbers, and their starting value. Changes affect the snippet presentation only. A custom background can reduce contrast; reset the theme to return to its coordinated colors.
| Action | Output |
|---|---|
| Save PNG | Raster image at 1×, 2×, or 3×; transparency is supported. |
| Save SVG | Script-free vector image made of text and shapes, with no external fonts/resources or HTML foreign objects. |
| Save HTML | Standalone styled HTML with selectable code and a restrictive CSP. |
| Copy HTML embed | A self-contained <figure> fragment with inline colors for a website's HTML editor. |
| Copy Markdown | A language-tagged fenced block; fences expand safely around backticks in the selection. |
| Copy code | The selected source, with its original line endings before any platform clipboard normalization. |
Clipboard denial/unavailability opens a selected-text fallback with a download button. HTML escapes all source, so embedded scripts remain visible text. SVG contains no executable code or external resources. Third-party publishing systems may remove inline styles; use the standalone HTML or an image if necessary. Publishing a URL still requires uploading an exported file to your own server. There is no upload or hosted-sharing service.
Save snippet adds the snapshot to this workspace. Saved snippets, in the document panel and Commands, provides search, preview/export, Insert, Open as document, and confirmed deletion. You can save changes to an existing snippet or save a copy. Snapshots are independent of subsequent document edits. To edit a snippet's code, open it as a document, edit it, and create a new snippet. Deleting a saved snippet is confirmed and does not alter source documents.
In a snippet's Preview / export dialog, expand Reusable insertion template and enable it explicitly. Edit the insertion text, then Save changes. Use {{name}} or {{name:default}}, for example:
digitalWrite({{pin:13}}, {{level:HIGH}});
// Configure pin {{pin:13}}
Parameter names use ASCII letters/underscore followed by letters/digits/underscore. A repeated name must repeat the same default; defaults cannot contain braces or newlines. Write \{{ for a literal opener. Ordinary ${...} code remains literal. Malformed templates cannot be saved or imported.
Insert opens labeled value fields and a live plain-text preview. Enter values, review, and insert; Cancel changes nothing. Values are literal text, with no evaluation, shell expansion, escaping substitution, or network request. Repeated parameters receive the same value. This dialog uses normal Tab navigation; it does not create in-editor tab stops. The whole insertion is one undo step across all current selections. New line separators follow the destination document, without rewriting existing separators. Entered values are not saved back as defaults.
Templates are separate from the original selected source: image/HTML/Markdown/code exports and Open as document keep that original source. Disable template insertion to return to literal insertion. Saved templates are included in browser recovery and workspace JSON; earlier releases ignore this optional field.
Saved snippets share the workspace's browser autosave, failure warnings, and conflict protection. They are included in workspace JSON and recovery exports. They are not separate files in document ZIP exports: open them as documents to include them there. Inserting a snippet replaces every current selection as one undoable edit and uses the destination document's preferred line endings. Opening it as a document preserves its stored line endings and language, in a new UTF-8 working copy.
PNG export checks dimensions before allocating a canvas: at most 16,384 pixels per side and 32 million pixels total. If the requested image exceeds that budget, reduce scale/font size, select less code, or use SVG/HTML. This is an image-memory guard, not a file-size restriction. Very large previews and vector exports may still be slow. Lines do not wrap in snippet images; horizontal scrolling in the preview does not crop the export.
Images use fonts installed on the device. Missing Unicode glyphs may appear as boxes, and SVG text/font metrics can differ in another viewer; PNG fixes the current appearance. Textual outputs and saved source retain Unicode. Tabs expand visually in images; unrenderable XML control characters are replaced with the replacement character in SVG only. HTML and image presentations use logical line breaks; workspace snippets and Copy code retain the original selected text. Images are illustrations, not an exact-byte backup.
| Status/action | Meaning |
|---|---|
| Waiting to save / Saving in this browser | The working-copy write is pending. |
| Saved in this browser | The IndexedDB transaction completed. This is not a disk-file save. |
| No file copy yet | A new document has no imported/saved/downloaded baseline. |
| Matches opened file | Text and workspace name match the imported snapshot. |
| File copy has changes | Text or name differs from the latest file-copy baseline. |
| Saved to a file | A permission-based file write successfully closed. |
| Matches last download | Bytes were handed to the browser download mechanism; the app cannot verify that you kept them. |
Save uses a browser file picker when direct file saving is available. After selecting a destination, subsequent saves update that file for this session. Save as always asks for a destination. On browsers without the picker, Save downloads a copy. Download is always available separately and uses the document's native extension.
Commands → Open files with direct saving obtains handles that can be saved back to their original files. Ordinary Open files and drag/drop import copies. Direct-save handles are kept only in memory, never in workspace JSON or browser recovery; after refresh, Save asks again. Browser autosave never writes disk files.
Before saving through an existing handle, TYPEBENCH compares a hash of the disk file with its previous snapshot. If it changed outside the editor, you can cancel or explicitly approve replacing the disk edits. Same-origin native saves with the same filename are serialized where Web Locks are available. This does not lock out other programs: an external program could still change a file between the check and write. Use Save as to retain both copies when uncertain.
Permission denial or write failure leaves the working copy available and offers recovery export. Canceling a picker does not change the document. A direct-save operation captures a snapshot; any editing that occurs while that write is in progress remains marked as changed afterward.
Folder connection → Connect and import folder asks the browser for access to a directory, imports its supported text files, and maps the resulting project folder to that disk directory. It keeps the same text-only import rules and collision reporting as Import folder. Connecting never writes files. Only one directory is connected at a time; connecting another imports a separate project root. Unsupported browsers disable connection controls and retain normal imports/downloads/ZIP.
When connected, Save writes a file under the mapped project root to the corresponding relative disk path. New paths create directories/files as needed. Project renames/moves save as new copies; original disk paths remain. Deleting a project file/folder never deletes its disk counterpart. Empty project folders are included in ZIP but are not created by Save All unless needed for a file. There is no directory synchronization or external-file watcher.
The sidebar shows Connected or Reconnect. Directory handles and permissions are session-only: after refresh, Reconnect folder asks you to select the same directory and confirm the mapping. JSON stores its display name, project path, and file-content hashes, never a directory handle or permission. Names are not unique filesystem identities: choose the original directory. Saved hashes let later saves detect changed/removed files even after refresh. Reconnecting alone does not import outside changes or write files.
Change project mapping in the connection dialog selects which project folder maps to the disk root. Use it after renaming the project root. Applying a mapping writes nothing; future explicit saves use the selected relative paths. Disconnect releases the session handle while retaining mapping/baselines. Forget mapping removes that metadata and retains all working copies.
Before writing, TYPEBENCH checks the destination bytes. An individual Save asks before replacing changed/untracked existing content or recreating a removed file, then checks again immediately before writing. If bytes already match, no write is needed. Same-origin writes use Web Locks where available. Other applications are not locked; no browser-only implementation can promise an atomic compare-and-write against an external program. A failed new-file write can leave newly created empty directories or an empty file; working text remains available.
Save as selects a separate file destination. Its explicit handle takes precedence for this session and its folder opt-out persists after refresh. Commands → Use connected folder for current file opts that document back in if it is under the mapped root. This avoids silently returning to the original folder after saving a separate copy elsewhere.
Commands → Save all project files (Command+Option+S / Ctrl+Alt+S) opens a review/status dialog. Save to file destinations processes project files, including closed tabs, in filename order. It uses existing direct-file handles or the connected folder. Conflicts, missing destinations, and denied folder permission are skipped and reported; other failures are reported per file while remaining files continue. Files are separate writes, not an atomic project transaction. Stop after current file keeps completed writes and leaves the rest untouched. Download the report or use Download all as ZIP for an independent copy. Save All does not open a succession of Save as pickers.
Browser autosave remains browser-only. Pending or failed disk saves never disable native downloads or workspace recovery exports. There is no background disk autosave.
History, above the editor and in each file's context menu, compares the current working copy against a snapshot or its last opened/saved/downloaded file-copy baseline. That baseline is a remembered copy, not a live read of the disk file. Checkpoint current creates a named snapshot at any time.
Automatic history is on by default and can be disabled in Settings. It records the pre-edit source when editing begins, then at most once per two minutes of subsequent editing, plus snapshots before project replacements/rollbacks and after successful file saves. It is not a recording of every keystroke. The current working copy still uses the normal 400 ms browser autosave independently.
Per file, automatic retention keeps up to 20 snapshots and approximately 20 MiB of UTF-16 source text, retaining at least the newest even if that single snapshot exceeds the budget. Older automatic entries are pruned and the count is shown in History. Manual checkpoints are retained until explicitly deleted. Snapshot metadata and storage overhead are additional, and there is no global history quota: many files/checkpoints can exhaust browser storage. Disable automatic history or remove unneeded snapshots and export backups as appropriate. Retention does not restrict opening/editing file sizes.
The line comparison preserves Unicode, whitespace, and exact line endings. Added/removed rows use +/− and old/new line numbers; LF, CRLF, CR, and EOF are labeled. Long unchanged runs are compacted with context, and further changed lines load in batches. A bounded patience comparison runs in a five-second worker; repeated/large sections may appear as replacement blocks instead of a minimal edit sequence. If comparison times out, snapshot download remains available.
Restore content first saves the displaced working source as a manual checkpoint, then restores text in one undoable editor operation. It rejects a stale comparison if the working source has changed. It preserves the document's current filename, language, and encoding settings; it does not write disk files. Download selected version uses the snapshot's own filename basename and encoding/BOM. Deleting a snapshot requires confirmation and does not change the working document.
History survives refresh, tab closure, project deletion/recovery, and workspace JSON export/import. Duplicating a document copies its available history into the independent duplicate. Fresh Start removes history with its documents. Snapshot history is distinct from CodeMirror Undo, which remains session-only. Snapshots can retain text you have since deleted, so review history before sharing a workspace JSON. Native files and document ZIPs contain current source only; JSON is the backup that includes snapshots.
Export workspace creates a readable schema-1 JSON file containing all project and recoverable deleted documents, exact text, names, encoding/BOM metadata, languages and manual overrides, multiple selections and split editor layout, file-copy baselines, active/open/pinned/recent tabs, view modes, saved snippets, folder paths/collapse states, folder recovery groups, local snapshots, optional connection paths/hashes, per-file save opt-outs, and relevant settings. It excludes disk handles, undo history, and image permissions.
Import workspace validates the file before replacing the current workspace. Replacement requires confirmation, with an export-first action. Earlier schema-1 workspace files through v1.4.0 remain compatible. Snippets, folders, collapsedFolders, closedFolders, session, connection, and per-document history, historyPruned, and folderOptOut are optional schema-1 fields. v1.5 also adds optional editorLayout, per-document selections, snippet template, and completion/split settings; v1.4 ignores these additions. Without session, existing project documents migrate as open tabs. Older releases ignore fields they do not know; retain your v1.5 JSON backup before downgrading. v1.3 ignores connection/history metadata; releases before v1.3 also ignore tab metadata. Releases before v1.3 open all project documents as tabs and do not retain pin/recent-tab metadata. Relative document paths still allow parent folders to be reconstructed, but old versions do not preserve empty folders or grouped folder recovery.
Export workspace documents as ZIP, in Commands, exports all project files, including closed tabs, in their native encodings with their relative paths, plus directory entries including empty folders. A workspace containing only empty folders can also be exported. Deleted files remain in recovery and workspace JSON; restore any you want in the ZIP. Closing a tab does not exclude its file. ZIP export runs in a worker and can be canceled. Paths that collide as files/folders or differ only by letter case must be renamed before ZIP export, preventing ambiguous extraction on common filesystems.
Paths must be relative. Absolute paths, .., empty path segments, Windows-reserved names, reserved characters, and trailing dots/spaces are rejected. Individual downloads use the basename because browsers choose their destination; JSON and ZIP preserve supported relative paths.
Working copies autosave about 400 ms after a meaningful change. Undo history survives tab switches and close/reopen within a session; refresh restores content/settings/selection but starts a new undo history. A crash before autosave completes can lose the latest pending change. The status indicator and unload warning expose this boundary.
Browser storage is scoped by browser profile, origin, and application directory. Private browsing, clearing site data, eviction, a different host/port/path, or device loss can remove access to it. Export JSON and native files for independent backups. Browser persistence is not encrypted storage or cloud backup.
- Storage failure leaves the work in memory and shows Export recovery copy and Retry browser save. Export before closing the page.
- Competing app tabs use an atomic revision check. A stale tab's autosave is blocked instead of overwriting the other tab. Export the conflicting copy, then reload the saved workspace. There is no automatic merge.
- Invalid workspace imports leave existing work intact. Folder imports add successful text files and report skipped items; cancellation adds nothing. Ordinary error notices can be dismissed; recovery/conflict warnings remain visible until resolved.
UTF-8, with or without BOM, and BOM-marked UTF-16LE/BE are supported. File decoding is strict. Invalid encodings and NUL-containing files are rejected without changing existing work. Convert other encodings to UTF-8 externally first.
Unedited LF, CRLF, CR, and mixed line endings are retained, including trailing whitespace and final newlines. New line breaks use the document's most common existing ending (first encountered wins ties); new documents use LF. Pasted/replacement line breaks use that preference. Undo restores original separators. Moving lines keeps the existing separator sequence at the destination positions. New duplicated lines use the preferred ending. Markdown formatting preserves existing separators.
Unicode is not normalized. Changing highlighting, theme, wrap, indentation settings, or view does not reformat text. Indentation settings affect subsequent typing/commands only. Source position and selection counts use CodeMirror's UTF-16 offsets; an emoji can occupy multiple positions. Source character counts use Unicode code points, counting logical line breaks and whitespace. Word counts use letter/number groups, not language-specific linguistic segmentation.
| Language | Typical filenames |
|---|---|
| Plain text / Markdown | .txt, .text, .log / .md, .markdown, .mdown |
| HTML / CSS | .html, .htm / .css |
| JavaScript / TypeScript | .js, .mjs, .cjs, .jsx / .ts, .tsx, .mts, .cts |
| JSON / Python | .json, .jsonld / .py, .pyw |
| C / C++ | .c / .cpp, .cc, .cxx, .h, .hpp, .hxx |
| Java / C# | .java / .cs |
| Go / Rust / PHP | .go / .rs / .php, .phtml |
| Ruby | .rb, .rake, Gemfile, Rakefile |
| Bash / shell | .sh, .bash, .zsh, .bashrc, .bash_profile, .zshrc, .profile |
| PowerShell / SQL | .ps1, .psm1, .psd1 / .sql |
| YAML / TOML / XML | .yaml, .yml / .toml / .xml, .svg, .xsl, .xsd |
| Processing | .pde |
| Arduino | .ino |
Use the language selector to override detection. A manual choice survives renaming. Detect language from filename in Commands restores automatic detection. .h is ambiguous and defaults to C++; choose C manually when needed.
Processing uses Java-style lexical syntax plus common Processing functions, types, and constants, including setup, draw, size, PVector, drawing functions, PI, and canvas/mouse values. Arduino uses C++ lexical syntax plus setup, loop, pinMode, digitalWrite, analogRead, Serial, HIGH, LOW, and common types/constants. Comments, strings, numbers, and multiline comments are handled by the base modes; API names inside comments or strings are not separately colored.
Highlighting is presentation only. It does not compile, lint, validate types, check libraries, run Processing, or upload Arduino code. The sketch API lists are intentionally finite and do not cover every contributed library. Names can be colored even when shadowed by your own identifiers. Processing's color literals/preprocessor syntax and newer Java constructs may not receive ideal classification. Arduino board extensions/macros, SQL dialects, and newer C#/PowerShell/Ruby/TOML constructs may exceed the bundled lexical modes. TypeScript uses JSX-capable parsing for both .ts and .tsx; ambiguous angle-bracket assertions may be colored as JSX. Choose Plain text if a parser is distracting; the source remains unchanged.
Choose Source, Split, or Preview. Preview updates after a brief typing pause. The divider size, view choice, and optional proportional synchronized scrolling persist. Sync is based on scroll percentage, not exact paragraph alignment.
Supported Markdown includes headings, emphasis, lists, blockquotes, links, images, rules, fenced code, GFM tables, task lists, and strikethrough. Fences use the same bundled languages as the editor, with aliases such as pde/processing, ino/arduino, js, ts, py, bash, and c++. Unknown fences remain plain text.
Formatting controls insert/remove readable source for headings, bold, italic, strike, lists, tasks, quotes, code, fences, and links. Each operation is undoable as one step. The heading outline navigates the document. Preview task checkboxes are read-only; edit their [ ]/[x] source. Formatting controls are tucked away in preview-only view. Find and go-to-line reveal source when necessary.
The outline uses parsed Markdown headings. Internal links use lowercase heading text with spaces changed to hyphens; punctuation is removed. Duplicate headings receive -2, -3, etc. The output prefixes IDs with tb- to avoid collisions with application controls. Exact GitHub heading-slug equivalence for all entity/punctuation combinations is not guaranteed.
Export HTML produces a standalone styled document with highlighted code and a restrictive content security policy. Images are omitted from exported HTML, leaving labeled placeholders, even if preview permission was granted. Download always exports the original source bytes in the document's selected filename/encoding. Print / PDF provides a clean document layout for browser printing or Save as PDF. Source-only/plain-code printing is also supported through the browser print command.
Raw embedded HTML is displayed as escaped source, not executed or rendered as arbitrary HTML. The generated Markdown is additionally sanitized with DOMPurify and a restrictive tag/attribute allowlist. Scripts, event handlers, frames, styles, forms, SVG, and embedded objects are not allowed. Unsafe links are stripped. Ordinary HTTP(S)/email links open only when clicked.
Images begin as Load buttons. Clicking one shows the exact resource address and asks for approval before any request. Permission is limited to that open document view and is cleared on tab change or refresh; Block images clears it immediately. Only HTTP(S) and base64 PNG/JPEG/GIF/WebP image data are eligible. SVG image data is not loaded. Relative image URLs resolve against the app's current directory; local sibling images on your disk are not automatically accessible. Browsers may block HTTP images on an HTTPS page.
Image requests can disclose your IP address to the image host. They use no-referrer policy and are not added to the offline app cache. No image requests occur by default. Preview parsing and highlighting run in a local worker; slow previews stop after roughly 5 seconds with a retry option that allows 15 seconds. This is a processing budget, not a file-size cap. Your source remains available if preview fails.
Find searches the active document. It supports case sensitivity, Unicode-aware whole-word matching, visible matches, counts, wrapping previous/next navigation, Replace, and Replace all. Enter advances; Shift+Enter goes backward. Replace acts on the selected match or the next match. Replace all is one undoable operation. Editing or switching documents invalidates stale results.
Under Advanced in Find, enable JavaScript regular expressions. Anchors are multiline and Unicode matching is enabled; case sensitivity follows Match case. Enter the pattern without /.../ delimiters. Regex replacements support $1–$99, $<name>, $&, $$, JavaScript prefix/suffix substitutions, and \n, \r, \t, \\. Literal mode leaves those characters literal. Whole-word boundaries treat Unicode letters, numbers, combining marks, and underscore as word characters. No Unicode normalization is applied.
Search runs in a worker with cancellation and a roughly 2.5-second time budget. Invalid or timed-out searches never replace text. Very large replacements or document rendering can still pause the main thread.
| Action | Mac | Windows/Linux |
|---|---|---|
| Local completions | Ctrl+Space / Option+I | Ctrl+Space |
| Select next occurrence | Command+D | Ctrl+D |
| Add cursor above/below | Command+Option+↑/↓ | Ctrl+Alt+↑/↓ |
| Fold/unfold selection | Ctrl+Shift+[ / ] | Ctrl+Shift+[ / ] |
| New | Command+Option+N | Ctrl+Alt+N |
| Open | Command+O | Ctrl+O |
| Save / Save as | Command+S / Command+Shift+S | Ctrl+S / Ctrl+Shift+S |
| Undo / redo | Command+Z / Command+Shift+Z | Ctrl+Z / Ctrl+Shift+Z or Ctrl+Y |
| Find / commands | Command+F / Command+Shift+P | Ctrl+F / Ctrl+Shift+P |
| Save All | Command+Option+S | Ctrl+Alt+S |
| Quick Open | Command+P | Ctrl+P |
| Symbols in file | Command+Shift+O | Ctrl+Shift+O |
| Search project | Command+Shift+F | Ctrl+Shift+F |
| Close tab | Command+W | Ctrl+W |
| Go to line | Command+G | Ctrl+G |
| Indent / outdent | Command+] / Command+[ | Ctrl+] / Ctrl+[ |
| Toggle comment | Command+/ | Ctrl+/ |
| Move lines | Option+Up / Down | Alt+Up / Down |
| Duplicate lines | Option+Shift+Down | Alt+Shift+Down |
| Rename | F2 (sometimes Fn+F2) | F2 |
| Leave editor | Escape, then Tab | Escape, then Tab |
Tab switches focus by default. Advanced Tab key indents enables indentation; Escape then Tab still leaves the editor. Tabs use Left/Right/Home/End; Delete closes the focused tab while its file stays in the project. The command menu uses Up/Down/Enter. Dividers support arrow keys. Dialogs use native modal focus handling. Visible focus, reduced-motion handling, labeled controls, and keyboard-focusable code blocks are included.
Print / PDF is available in Commands and the Markdown toolbar. Command/Ctrl+P opens Quick Open in the app.
- Open the complete deployment over HTTPS or localhost while the static files are available.
- Wait for Offline ready. The app's scoped service worker installs only after the full runtime shell is cached and then controls the page.
- Reopen that same address while offline. Editing, all bundled highlighting, Markdown preview, document/project find/replace, quick file/symbol navigation, native downloads, JSON/ZIP exports, local folder import, and snippet creation/image/HTML exports, local history, and comparison work locally. Connected-folder saving also works offline where the browser provides directory access and permission. Direct-save support still depends on the browser and permission. Remote images are not made available by app installation.
The shell includes HTML, CSS, bundled application code, document/project search, navigation, preview, snippet, diff, and ZIP workers, favicon, README, and top-level license notices. Documents are separately stored in IndexedDB, never in the service-worker cache or on the hosting server. Both cache and browser storage can be evicted; exports remain essential.
Commands → Check for offline app update checks the server. A ready update shows a notice. Save/export, close all app windows, then reopen. No forced reload interrupts an editing session. New caches are scoped to the application URL. When a new worker activates after the old clients close, it removes older caches in that same scope. Legacy v0.1/v0.2 caches are conservatively retained because they were not scope-namespaced; they are unused by current releases. Clearing site data also clears documents, so export first.
No telemetry, analytics, remote fonts, accounts, cloud synchronization, or routine external service calls are present. Loading the app/updating its shell contacts your chosen host; manually approved images and clicked links contact their destinations.
See docs/TEST-REPORT.md and the machine-readable result files for the release checks, measured sizes, and screenshots. Qualification used headless Chromium on Linux. Mac key conventions and mobile widths were emulated, not tested on physical Mac/iOS/Android devices. Firefox, Safari, Windows, real assistive technology, and native operating-system picker dialogs have not been manually qualified in this environment. Direct-write tests use actual browser FileSystemFileHandles with a substituted chooser.
The editor has no arbitrary file-size gate. Text imports larger than 1 MiB show opening feedback. Practical limits depend on device memory, browser storage quota, parser complexity, line length, preview DOM size, and replacement count. Plain text at 100 KiB, 1 MiB, 5 MiB, and 10 MiB, plus a 1 MiB single line, is covered by the qualification suite. This is not a guarantee of smooth editing for every similarly sized source file. ZIP is a conventional in-memory archive; multi-gigabyte archives/ZIP64 are not supported or tested. Export huge collections in smaller workspaces. Qualification includes a 126-file real directory drop (including repeated 100-entry browser batches), a 1,000-file directory-picker import, nested empty folders, and mixed-encoding ZIP round trips. The v1.3 1,000-file import test opened one tab and exported/restored all files; the release report distinguishes current reruns from historical checks. Source and file baselines remain in memory; editor states are created on demand and retained after use. There is no arbitrary file-count limit, but large trees/import commits can pause the UI. Import progress and cancellation cover scanning and reads; the final commit is synchronous. Internal tree dragging was checked with browser drag events, not a physical pointer/device gesture; the equivalent Move dialog is tested end to end.
Known intentional boundaries: no code execution, semantic validation, linting service, Processing playback, Arduino upload, terminal, server file management, collaboration, cloud sync, Git, binary editor, ZIP import, automatic directory synchronization, disk deletion/move, or rich HTML editing. Undo history and disk handles are session-only; explicit snapshots persist with the workspace. Remote image content is not embedded in HTML exports. These boundaries keep TYPEBENCH focused on reliable text editing.
src/: application, editor, language registry, safe preview, snippets, folder tree/import/path logic, context menus, connected saving, local history/diff, project/session model, file/symbol navigation, project search/replace, file I/O, worker code, optional examplesassets/: deployable local bundles and stylesscripts/: optional build/package helperstests/: core, browser workflow, security, encoding, upgrade, and accessibility checksdocs/: test report, result files, screenshots, and print samplelicenses/: full notices for bundled dependencies
To rebuild or run tests as a maintainer (requires Node.js/npm and development dependencies):
npm ci
npm run build
npm test
npm run test:editing
npm run test:languages
npm run test:browser
npm run test:editing-browser
npm run test:markdown
npm run test:files
npm run test:accessibility
npm run test:release
npm run test:snippets
npm run test:snippets-browser
npm run test:context-brackets
npm run test:snippet-boundaries
npm run test:folders
npm run test:folders-browser
npm run test:folder-boundaries
npm run test:project-data
npm run test:project-tabs
npm run test:project-search
npm run test:project-tree
npm run test:project-boundaries
npm run test:coding-data
npm run test:coding
npm run test:assistance
npm run test:split-boundaries
npm run test:history-data
npm run test:connected
npm run test:history
npm run test:saving-history-boundariesThe test runners accept CHROMIUM_EXECUTABLE=/absolute/path/to/chromium; otherwise they use the development-only Chromium package. OS compatibility of that package differs from the browser app. Browser tests start ephemeral localhost servers. The optional upgrade test accepts TYPEBENCH_BASELINE_DIR pointing at an unpacked earlier release. The supplied production assets already work without any of these commands. Development dependency installation uses the package registry; everyday app use does not.
Architecture references: CodeMirror, Lezer highlighting, Marked, DOMPurify, fflate, File System Access, directory picker paths, directory drop entries.
Copyright © 2026 Green Shoe Garage. The application is licensed GNU GPL v3 only (GPL-3.0-only); the complete license is in LICENSE. Bundled dependencies retain their own licenses and notices in THIRD-PARTY-NOTICES.md and licenses/. No proprietary runtime services are required.
v1.5 adds two-file split editing, multiple selections, folding, local suggestions, and explicit parameterized snippet insertion. Connected-folder saving, Save All, persistent history, comparison/restoration, nested imports, and image/embed sharing remain included. Further work can prioritize project-wide semantic navigation, opt-in formatting/linting, and expanded file watching, each as a separately scoped release. Broader browser/device qualification, large-preview performance, recovery, and syntax-mode updates remain maintenance priorities. A compiler, cloud service, terminal, and full filesystem manager are outside this product's scope.