turbo-js — summary
A snapshot of the present. Edited in place; the history is in history.md.
What this is
A Turbo C-style terminal IDE for JavaScript on Node.js, written in Go, built on turbo-core — the library Turbo Go, Turbo Rust, Turbo Python, Turbo MoonBit and Turbo Golo already share. This repository holds the command, the profile that says the editor is for JavaScript, a JavaScript scanner that replaces the one the library ships, and a JSON scanner. Everything else — the event loop, the windows, the dialogs, the themes, the LSP client, the terminal emulator, the project tree, the snippets, tools and agent machinery — is the library's, and none of it is copied here.
JavaScript is the language; Node.js is the runtime. The docs say "JavaScript" for the language and its files, "Node" for the runtime and its toolchain (node, npm, npx), and never "interpreter".
Module rickub.com/turbo-editors/turbo-js, requireing turbo-core v0.8.0 from the module proxy with no active replace. The commented-out replace at the bottom of go.mod documents the escape hatch without being one; 01-release.tag.sh refuses to tag a release whose go.mod carries a live one. Remote: ssh://git@rickub.com/turbo-editors/turbo-js.git.
Layout
main.go |
flags (-theme, -list-themes, -no-lsp, -version), the terminal, jslang.Register(), the profile, the loop |
internal/jslang/jslang.go |
Name, Slug, Language (= syntax.LanguageJavaScript), LanguageJSON, Profile(), Register(), ServerArgs(), ServerDirs() and the five directory helpers |
internal/jslang/scan.go |
the JavaScript dispatcher, the two carries (block comment, template literal), the per-line state that decides whether / divides or opens a regular expression, hashbang, private names, decorators |
internal/jslang/literals.go |
template literals across lines, numbers (1_000, 0xFF, 0o17, 0b1010, 1.5e-3, .5, 10n) |
internal/jslang/words.go |
keywords, constants, the globals (standard library + Node), the property-after-dot rule, the contextual keywords, the naming conventions |
internal/jslang/json.go |
the JSON scanner: keys as attributes, values as strings, comments tolerated |
internal/jslang/*.toml.tmpl |
the four starter files, embedded by templates.go |
internal/jslang/*_test.go |
profile, scanner, JSON, templates, agents file, the editor assembled on a SimulationScreen, nine tests against a real typescript-language-server, and reference_test.go holding docs/*/reference/languages.md to the scanners |
diagram_test.go |
holds docs/diagrams/packages.drawio to go list |
docs/{en,fr}/ |
37 pages each (README included), Diátaxis; docs/README.md selects the language |
demos/greeter/ |
a class with a private field and a regex, an ES module importing it, a node --test file; npm start and npm test both run |
scripts/, Makefile, 01–04-release*.sh, release.env |
adapted from turbo-rust; the installer checks the server by running it |
How to build, test and measure
make check # fmt, vet, then the whole suite — what a commit should pass
make build # into bin/turbo-js, then scripts/check-version.sh on the binary
make install # build, install onto PATH, report what it found (--with-server installs the language server with npm)
go test ./... # the real-server tests skip themselves without typescript-language-server on PATH, and under -short
go test -short ./... # also skips the installer tests, which compile the whole editor
Quality gate, separate from the tests:
python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace .
To build against a turbo-core you have changed but not released: go work init . ../turbo-core, then go list -f '{{.Dir}}' rickub.com/turbo-editors/turbo-core/app must not answer a path under pkg/mod. go.work and go.work.sum are gitignored.
Decisions in force
- The JavaScript scanner replaces the library's, under the library's own name. turbo-core colours JavaScript for every editor (README fences, web pages) and its scanner deliberately refuses regular expressions. An editor for JavaScript owes its user regexes, Node's globals, the hashbang, the name after
function/class,#privatenames and@decorators, soRegister()registersLanguage = syntax.LanguageJavaScriptand the later registration wins — whichsyntax.Registerdocuments as intended. Snippets sayinglanguages = ["javascript"]and ```js fences reach this scanner with no library change. A test proves the override took (a.jsfile opened throughapp.Newhas its regex coloured), because forgettingRegister()would otherwise leave an editor that still colours JavaScript, wrongly, with every test green. - A regular expression is coloured with the
charclass. The class set is closed and JavaScript has no character literal; a regex is the other kind of quoted literal. Inturbo-classicsyntax.charis the string green, so they share a colour there; other themes separate them. Documented in the reference. - The slash rule is bounded by the line. After a value (
name, number, string,),])/divides; after an operator,(,[,{,},,,;,:, a keyword or at line start it opens a literal — but only if a closing/exists on the same line (a regex may not contain a newline), otherwise it divides. That bound is what makes the previous-token heuristic acceptable where the library declined it: a wrong guess costs one line, never the file. - A word after
.or?.is a property, never a keyword (map.get(k),options.default,promise.catch(), and a contextual keyword (get set static of from as) before(is a function.asyncis excluded from that rule (async (x) => …). - A leading capital is a class, even before
((new Greeter());MAX_SIZEis coloured as one too — documented limitation, held byreference_test.go. - Only block comments and template literals cross a line. Strings stop at their line (the language forbids a newline in them); a backtick inside
${}ends the template early;${…}is not coloured as code. All three documented as refusals. - JSON is registered here (
json,.json,.jsonc):package.jsonis the manifest and the root marker, and turbo-core does not colour JSON. Keys → attribute, values → string,//and/* */tolerated (tsconfig.json). If turbo-core learns JSON later, this registration still wins by ordering and can then be dropped. - The language server is
typescript-language-server --stdiowith TypeScript 6, not TypeScript 7's own. Both were measured against the same nine end-to-end tests. TypeScript 7 (typescript@7.0.2, the native port) ships notsserver.js, sotypescript-language-server6.0.0 installed beside it fails atinitialize("Could not find a valid TypeScript installation"); the install hint therefore pinstypescript@6(6.0.3 tested). TypeScript 7's native server (tsc --lsp --stdio, "typescript-go 7.0.2") answers all eight requests, four times faster, and publishes no diagnostics — it offers pull diagnostics (textDocument/diagnostic), which turbo-core does not request — so the gutter stays blank on a broken file: exactly the failure this family designs against. Recorded injslang.go's comment, the docs and turbo-core's memory; the day the library pulls, two profile fields switch it. - All nine of turbo-core's questions are answered for plain JavaScript, and each is driven against the real server by a test: completion of a buffer-only declaration, definition, references (declaration + two calls), implementations (the two subclasses of a class), type definition (
new Circle()→ the class), hover (the JSDoc above a declaration), document symbols, workspace symbols, and diagnostics forfunction main( {. Diagnostics on.jsare syntax errors; type errors need// @ts-checkorcheckJs. RootMarkersis["package.json"]— the nearest one going up, so a monorepo's package is the server's root. Settings and the tree stay in the working directory (turbo-core's rule).ServerDirs():$NVM_BIN, then$NPM_CONFIG_PREFIX/binor~/.npm-global/bin,$PNPM_HOME,$VOLTA_HOME/binor~/.volta/bin, then/usr/local/binand/opt/homebrew/bin. Empty variables contribute nothing; nvm's default alias is not resolved.- The toolchain menu is
~J~avaScript,Alt-J. J is free (fixed menus: F, E, S, R, C, O, W, N, A, H). Named after the language, not afternpmornode, for the family's reason: the menu holds whatever the project's tools file says. - Seven starter tools: Install (
npm install, first, because a fresh clone runs it first), Format (npx prettier --write .), Lint (npx eslint .), Test (node --test), Run (node {{script, e.g. main.js}}, terminal, the one placeholder — a Node project has no single entry point), Start (npm start, terminal), and Echo in aToolsmenu. No Build: JavaScript has no compile step;node --checkis left to the project. Every command was run against Node 24.19.0 / npm 11.17.0 / Prettier 3.9.7. - Snippets indent with two spaces (Prettier's default); a test holds every body to it. Seven JavaScript snippets, one JSON (
scripts), plus General and Markdown. Bodies are TOML basic strings. - A
nodeshebang is claimed (#!/usr/bin/env node, alsoenv -S node …): a CLI tool is a.js-less file whose first line names node, and Node strips the line; the scanner colours it as a comment on line 1 only. autosave = truein the starter settings file,falseinsettings.Default(). Two statements in two places on purpose.- The starter templates are embedded files, not Go constants, with the
.tmplsuffix becausesettings.toml.tmplholdstheme = %q. - Agent windows are turbo-core's; what belongs here is the starter file, which names the
javascript /js fence and the ```json one, and teaches the window's keyboard. - Not coloured, on purpose: TypeScript, JSX/TSX, CSS. The server serves
.tsfiles anyway; the scanner is for JavaScript.
State as of 2026-09-16
- Complete and green, uncommitted.
go build,go vet,gofmt -lclean; the whole suite passes withtypescript-language-server6.0.0 +typescript6.0.3 installed globally in the sandbox (/usr/local/share/npm-global/bin). The repository has no commits yet; everything is untracked onmain. - Falsified by mutation: fourteen mutations over the scanner, the templates, the diagram and
main.go; eleven went red at once, three stayed green and were fixed (0xE + 1never reached the hexadecimal guard because the space ends the number —0xE+1does; the snippets language-list test searched the whole file where alanguageskey satisfied it — it now reads the header comment; nothing noticedmain.godroppingRegister()—TestMainRegistersTheLanguageExplicitlydoes). All fourteen are red now. - Quality gate: PASS, run #3 (run #1 failed on one
return-statementssmell inclassOfWord, split into three functions; run #2 passed at 108). 0 errors, 0 warnings, 0 smells; total complexity 109, worst filescan.goat 32 against a limit of 60. 154 top-level tests pass. - Documentation: 37 pages × EN + FR,
README.mdat the root,demos/README.md,docs/diagrams/packages.drawiochecked againstgo list. Twenty-six pages per language were adapted from turbo-golo and read whole by six subagents against a fact sheet (/tmp/tj/FACTS.md); the eleven language-specific pages were written from scratch in English and translated by three more. - Verified in a real pty (
/tmp/tj/tut.raw, replayed with/tmp/tj/screen.py): the barFile Edit Search Run Code Options Window Snippets Agent JavaScript Help;→five times reaches Options and nine JavaScript;Alt-Jshows Create/Open tools file, then the six starter commands with aToolsmenu appearing between JavaScript and Help;Runasksscript, e.g. main.js:and a terminal window titlednode 'main.js'prints the three greetings; afunction … greet(3;line gets×in the gutter and⚠ ')' expected.on the status bar;F1ongreetopens a Symbol box beginning(method) Greeter.greet(times?: number);F12jumps to line 12. Colours read back as SGR inturbo-classic: keyword97;44;1, type96;44, function93;44;1, builtin96;44;1, identifier93;44, string and regex92;44, number and constant95;44, comment38;2;143;143;143, operator and punctuation37;44. - The editor repeats the previous line's indentation on
Enter. Typing an indented listing verbatim doubles the indentation; the tutorial says to type only the change, and the pty driver does the same. - Registered in the family. turbo-core's
README.md, both doc READMEs, both workspace how-tos,profile/profile.go's comment and.memory/summary.mdcount six editors; turbo-python's, turbo-moonbit's and turbo-golo's architecture pages (EN + FR) say "all six editors", turbo-golo's FR design-decisions too. turbo-core's.gostrings were swept for hardcoded language names against v0.8.0: every hit innocent. No library change needed.
Known boundaries
lsp.Client.DidOpensendslanguageId: "go"for every editor (a turbo-core quirk); tsserver decides by extension, so it does not matter here. Recorded in turbo-core's memory as a library cycle.- Diagnostics are pushed only. A pull-only server leaves the gutter blank — the reason TypeScript 7's server is not used.
- Cross-file
F12is neither claimed nor denied in the docs: the end-to-end tests open a singlemain.js. npx prettier/npx eslintdownload on first use when the project does not depend on them; ESLint 9 needs aneslint.config.js.
Not yet established
- Agent windows have never been opened in this editor. The starter file is covered by tests that create it, load it back and check it names this editor's fences; nobody has run
turbo-js, pressedAlt-Aand talked to an agent from it. - Never run on macOS or Windows. Everything here was verified on Linux/arm64.
/tmp/tj/turbo-jsis an ELF built for the pty run and lives outside the tree on purpose;bin/is empty. - No CI. No pipeline configuration in the repository.
- Never released. No tag exists;
release.envsaysTAG="v0.1.0".01–04have only ever been read here, never run.turbo-js.token.envdoes not exist yet (gitignored by*.env). - No
LICENSEfile. The siblings are MIT (Copyright (c) 2026 turbo-editors); adding one is the owner's decision, and the root README says so. - The real-server tests have only run against typescript-language-server 6.0.0 + typescript 6.0.3. A newer server that starts with TypeScript 7 would make the
@6pin unnecessary; a test does not detect that. - Performance on a large file is unmeasured.
- Nothing checks
demos/greeterautomatically. It was run by hand under Node 24.19.0.
State as of 2026-09-19 — moved to Rickub, released by a workflow
- Module path
rickub.com/turbo-editors/turbo-js, depending onrickub.com/turbo-editors/turbo-core v1.0.0— the first turbo-core version published under that path (v0.9.0on the proxy still declares the Codeberg path and cannot be required asrickub.com/…). Every import, the Makefile'sVERSION_PKG,scripts/install.sh, the README and the docs sayrickub.com.GOWORK=off make checkgreen. The repository on this side is a freshgit initwithoriginatssh://git@rickub.com/turbo-editors/turbo-js.gitand no commit yet;01-release.tag.shmakes the first one. - Releases are one script and one workflow, modelled on turbo-core's and identical to turbo-go's.
01-release.tag.shrunsmake checkunderTURBO_JS_RELEASING=1, refuses a tag taken locally or on origin (bump, never move), refuses areplaceingo.mod, commits, pushes the current branch, then tags and pushes the tag. That push starts.github/workflows/release.yml:go testwithTURBO_JS_RELEASING=1,./02-build-releases.sh "${GITHUB_REF_NAME}", release notes from the tag message, a run artifact, thensoftprops/action-gh-release@v2attachingturbo-js-*,SHA256SUMSandREADME.mdwith the job's ownGITHUB_TOKEN— the only credential Rickub's release API accepts.02-release.publish.shand04-release.upload-binaries.share gone; the build script is now02-build-releases.sh, takes the tag as$1(CI has norelease.env), validates it, refuses areplace, and starts from an emptyrelease/${TAG}/.release.envholds onlyTAGandABOUT;turbo-js.token.envis read by nothing. release_test.goruns01for real against a throwaway bare remote (publishes, then refuses the same tag), runs02alone to see it refusev0.o.0, and asserts the workflow's trigger,contents: write,./02-build-releases.sh,fail_on_unmatched_files, docs linked at the tag, nosecrets., andTURBO_JS_RELEASING. The copy of the module for the clone leaves out.git,bin,release,kits,demo,demos,*.envandgo.work*; children run withGOWORK=off.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
|