| 📦 Turbo JS 91999d1 k33g 12h ago | 1 | # turbo-js — summary |
| 2 | |
| 3 | A snapshot of the present. Edited in place; the history is in `history.md`. |
| 4 | |
| 5 | ## What this is |
| 6 | |
| 7 | A Turbo C-style terminal IDE for **JavaScript on Node.js**, written in Go, built on **[turbo-core](https://rickub.com/turbo-editors/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. |
| 8 | |
| 9 | **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". |
| 10 | |
| 11 | Module `rickub.com/turbo-editors/turbo-js`, `require`ing 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`. |
| 12 | |
| 13 | ## Layout |
| 14 | |
| 15 | | | | |
| 16 | | --- | --- | |
| 17 | | `main.go` | flags (`-theme`, `-list-themes`, `-no-lsp`, `-version`), the terminal, `jslang.Register()`, the profile, the loop | |
| 18 | | `internal/jslang/jslang.go` | `Name`, `Slug`, `Language` (= `syntax.LanguageJavaScript`), `LanguageJSON`, `Profile()`, `Register()`, `ServerArgs()`, `ServerDirs()` and the five directory helpers | |
| 19 | | `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 | |
| 20 | | `internal/jslang/literals.go` | template literals across lines, numbers (`1_000`, `0xFF`, `0o17`, `0b1010`, `1.5e-3`, `.5`, `10n`) | |
| 21 | | `internal/jslang/words.go` | keywords, constants, the globals (standard library + Node), the property-after-dot rule, the contextual keywords, the naming conventions | |
| 22 | | `internal/jslang/json.go` | the JSON scanner: keys as attributes, values as strings, comments tolerated | |
| 23 | | `internal/jslang/*.toml.tmpl` | the four starter files, embedded by `templates.go` | |
| 24 | | `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 | |
| 25 | | `diagram_test.go` | holds `docs/diagrams/packages.drawio` to `go list` | |
| 26 | | `docs/{en,fr}/` | 37 pages each (README included), Diátaxis; `docs/README.md` selects the language | |
| 27 | | `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 | |
| 28 | | `scripts/`, `Makefile`, `01`–`04-release*.sh`, `release.env` | adapted from turbo-rust; the installer checks the server by **running** it | |
| 29 | |
| 30 | ## How to build, test and measure |
| 31 | |
| 32 | ```bash |
| 33 | make check # fmt, vet, then the whole suite — what a commit should pass |
| 34 | make build # into bin/turbo-js, then scripts/check-version.sh on the binary |
| 35 | make install # build, install onto PATH, report what it found (--with-server installs the language server with npm) |
| 36 | go test ./... # the real-server tests skip themselves without typescript-language-server on PATH, and under -short |
| 37 | go test -short ./... # also skips the installer tests, which compile the whole editor |
| 38 | ``` |
| 39 | |
| 40 | Quality gate, separate from the tests: |
| 41 | |
| 42 | ```bash |
| 43 | python3 ~/.claude/skills/quality/scripts/quality_report.py --workspace . |
| 44 | ``` |
| 45 | |
| 46 | 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. |
| 47 | |
| 48 | ## Decisions in force |
| 49 | |
| 50 | - **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`, `#private` names and `@decorators`, so `Register()` registers `Language = syntax.LanguageJavaScript` and the later registration wins — which `syntax.Register` documents as intended. Snippets saying `languages = ["javascript"]` and ```js fences reach this scanner with no library change. **A test proves the override took** (a `.js` file opened through `app.New` has its regex coloured), because forgetting `Register()` would otherwise leave an editor that still colours JavaScript, wrongly, with every test green. |
| 51 | - **A regular expression is coloured with the `char` class.** The class set is closed and JavaScript has no character literal; a regex is the other kind of quoted literal. In `turbo-classic` `syntax.char` is the string green, so they share a colour there; other themes separate them. Documented in the reference. |
| 52 | - **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. |
| 53 | - **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. `async` is excluded from that rule (`async (x) => …`). |
| 54 | - **A leading capital is a class**, even before `(` (`new Greeter()`); `MAX_SIZE` is coloured as one too — documented limitation, held by `reference_test.go`. |
| 55 | - **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. |
| 56 | - **JSON is registered here** (`json`, `.json`, `.jsonc`): `package.json` is 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. |
| 57 | - **The language server is `typescript-language-server --stdio` with 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 no `tsserver.js`, so `typescript-language-server` 6.0.0 installed beside it fails at `initialize` ("Could not find a valid TypeScript installation"); the install hint therefore pins `typescript@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 in `jslang.go`'s comment, the docs and turbo-core's memory; the day the library pulls, two profile fields switch it. |
| 58 | - **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 for `function main( {`. Diagnostics on `.js` are **syntax errors**; type errors need `// @ts-check` or `checkJs`. |
| 59 | - **`RootMarkers` is `["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). |
| 60 | - **`ServerDirs()`**: `$NVM_BIN`, then `$NPM_CONFIG_PREFIX/bin` or `~/.npm-global/bin`, `$PNPM_HOME`, `$VOLTA_HOME/bin` or `~/.volta/bin`, then `/usr/local/bin` and `/opt/homebrew/bin`. Empty variables contribute nothing; nvm's default alias is not resolved. |
| 61 | - **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 after `npm` or `node`, for the family's reason: the menu holds whatever the project's tools file says. |
| 62 | - **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 a `Tools` menu. **No Build**: JavaScript has no compile step; `node --check` is left to the project. Every command was run against Node 24.19.0 / npm 11.17.0 / Prettier 3.9.7. |
| 63 | - **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. |
| 64 | - **A `node` shebang is claimed** (`#!/usr/bin/env node`, also `env -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. |
| 65 | - **`autosave = true` in the starter settings file, `false` in `settings.Default()`.** Two statements in two places on purpose. |
| 66 | - **The starter templates are embedded files, not Go constants**, with the `.tmpl` suffix because `settings.toml.tmpl` holds `theme = %q`. |
| 67 | - **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. |
| 68 | - **Not coloured, on purpose**: TypeScript, JSX/TSX, CSS. The server serves `.ts` files anyway; the scanner is for JavaScript. |
| 69 | |
| 70 | ## State as of 2026-09-16 |
| 71 | |
| 72 | - **Complete and green, uncommitted.** `go build`, `go vet`, `gofmt -l` clean; the whole suite passes with `typescript-language-server` 6.0.0 + `typescript` 6.0.3 installed globally in the sandbox (`/usr/local/share/npm-global/bin`). The repository has **no commits yet**; everything is untracked on `main`. |
| 73 | - **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 + 1` never reached the hexadecimal guard because the space ends the number — `0xE+1` does; the snippets language-list test searched the whole file where a `languages` key satisfied it — it now reads the header comment; nothing noticed `main.go` dropping `Register()` — `TestMainRegistersTheLanguageExplicitly` does). All fourteen are red now. |
| 74 | - **Quality gate: PASS**, run #3 (run #1 failed on one `return-statements` smell in `classOfWord`, split into three functions; run #2 passed at 108). 0 errors, 0 warnings, 0 smells; total complexity 109, worst file `scan.go` at 32 against a limit of 60. 154 top-level tests pass. |
| 75 | - **Documentation**: 37 pages × EN + FR, `README.md` at the root, `demos/README.md`, `docs/diagrams/packages.drawio` checked against `go 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. |
| 76 | - **Verified in a real pty** (`/tmp/tj/tut.raw`, replayed with `/tmp/tj/screen.py`): the bar ` File Edit Search Run Code Options Window Snippets Agent JavaScript Help`; `→` five times reaches Options and nine JavaScript; `Alt-J` shows Create/Open tools file, then the six starter commands with a `Tools` menu appearing between JavaScript and Help; `Run` asks `script, e.g. main.js:` and a terminal window titled `node 'main.js'` prints the three greetings; a `function … greet(3;` line gets `×` in the gutter and `⚠ ')' expected.` on the status bar; `F1` on `greet` opens a **Symbol** box beginning `(method) Greeter.greet(times?: number)`; `F12` jumps to line 12. Colours read back as SGR in `turbo-classic`: keyword `97;44;1`, type `96;44`, function `93;44;1`, builtin `96;44;1`, identifier `93;44`, string and regex `92;44`, number and constant `95;44`, comment `38;2;143;143;143`, operator and punctuation `37;44`. |
| 77 | - **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. |
| 78 | - **Registered in the family.** turbo-core's `README.md`, both doc READMEs, both workspace how-tos, `profile/profile.go`'s comment and `.memory/summary.md` count 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 `.go` strings were swept for hardcoded language names against v0.8.0: every hit innocent. **No library change needed.** |
| 79 | |
| 80 | ## Known boundaries |
| 81 | |
| 82 | - **`lsp.Client.DidOpen` sends `languageId: "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. |
| 83 | - **Diagnostics are pushed only.** A pull-only server leaves the gutter blank — the reason TypeScript 7's server is not used. |
| 84 | - **Cross-file `F12` is neither claimed nor denied** in the docs: the end-to-end tests open a single `main.js`. |
| 85 | - **`npx prettier` / `npx eslint` download on first use** when the project does not depend on them; ESLint 9 needs an `eslint.config.js`. |
| 86 | |
| 87 | ## Not yet established |
| 88 | |
| 89 | - **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`, pressed `Alt-A` and talked to an agent from it. |
| 90 | - **Never run on macOS or Windows.** Everything here was verified on Linux/arm64. `/tmp/tj/turbo-js` is an ELF built for the pty run and lives outside the tree on purpose; `bin/` is empty. |
| 91 | - **No CI.** No pipeline configuration in the repository. |
| 92 | - **Never released.** No tag exists; `release.env` says `TAG="v0.1.0"`. `01`–`04` have only ever been read here, never run. `turbo-js.token.env` does not exist yet (gitignored by `*.env`). |
| 93 | - **No `LICENSE` file.** The siblings are MIT (`Copyright (c) 2026 turbo-editors`); adding one is the owner's decision, and the root README says so. |
| 94 | - **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 `@6` pin unnecessary; a test does not detect that. |
| 95 | - **Performance on a large file is unmeasured.** |
| 96 | - **Nothing checks `demos/greeter` automatically.** It was run by hand under Node 24.19.0. |
| 97 | |
| 98 | ## State as of 2026-09-19 — moved to Rickub, released by a workflow |
| 99 | |
| 100 | - **Module path `rickub.com/turbo-editors/turbo-js`**, depending on `rickub.com/turbo-editors/turbo-core v1.0.0` — the first turbo-core version published under that path (`v0.9.0` on the proxy still declares the Codeberg path and cannot be required as `rickub.com/…`). Every import, the Makefile's `VERSION_PKG`, `scripts/install.sh`, the README and the docs say `rickub.com`. `GOWORK=off make check` green. The repository on this side is a fresh `git init` with `origin` at `ssh://git@rickub.com/turbo-editors/turbo-js.git` and **no commit yet**; `01-release.tag.sh` makes the first one. |
| 101 | - **Releases are one script and one workflow**, modelled on turbo-core's and identical to turbo-go's. `01-release.tag.sh` runs `make check` under `TURBO_JS_RELEASING=1`, refuses a tag taken locally or on origin (bump, never move), refuses a `replace` in `go.mod`, commits, pushes the current branch, then tags and pushes the tag. That push starts `.github/workflows/release.yml`: `go test` with `TURBO_JS_RELEASING=1`, `./02-build-releases.sh "${GITHUB_REF_NAME}"`, release notes from the tag message, a run artifact, then `softprops/action-gh-release@v2` attaching `turbo-js-*`, `SHA256SUMS` and `README.md` with the job's own `GITHUB_TOKEN` — the only credential Rickub's release API accepts. **`02-release.publish.sh` and `04-release.upload-binaries.sh` are gone**; the build script is now `02-build-releases.sh`, takes the tag as `$1` (CI has no `release.env`), validates it, refuses a `replace`, and starts from an empty `release/${TAG}/`. `release.env` holds only `TAG` and `ABOUT`; `turbo-js.token.env` is read by nothing. |
| 102 | - **`release_test.go`** runs `01` for real against a throwaway bare remote (publishes, then refuses the same tag), runs `02` alone to see it refuse `v0.o.0`, and asserts the workflow's trigger, `contents: write`, `./02-build-releases.sh`, `fail_on_unmatched_files`, docs linked at the tag, no `secrets.`, and `TURBO_JS_RELEASING`. The copy of the module for the clone leaves out `.git`, `bin`, `release`, `kits`, `demo`, `demos`, `*.env` and `go.work*`; children run with `GOWORK=off`. |