Closing -- shipped in spikersoft-angular. Summary of the implementation against the parity targets in the ticket:
Approach
Library: prettier@3.x moved from devDependencies to dependencies so it ships at runtime. We dynamically import('prettier/standalone') plus the babel, typescript, and estree parser plugins on the FIRST format request -- the heavy module never enters the initial chunk. Cached in a private field after first load so the second Format Document is synchronous.
Monaco wiring: MonacoPrettierFormatterService.attach(monaco) registers a DocumentFormattingEditProvider for both javascript and typescript model languages, returning a single full-document TextEdit covering the model's full range. Format Document (Shift+Alt+F) and the editor context menu both go through this provider.
Per-learner config: PrettierConfigStore (signals + localStorage) holds a typed PrettierLearnerOptions shape covering printWidth, tabWidth, useTabs, semi, singleQuote, trailingComma, bracketSpacing, arrowParens, endOfLine. Persistence is imperative inside set/patch/reset so it's testable without flushing effects. Defaults match Prettier's canonical defaults exactly so a learner who never touches the panel sees vanilla Prettier behavior.
Visual config UI: JsFormattingOptionsPanelComponent (form controls bound directly to typed setNumber/setBoolean/setTrailingComma setters) hosted inside a CDK Overlay popover triggered from a new gear icon in the JS playground's debugger toolbar (JsFormattingOptionsButtonComponent). Includes a Format Now button that fires the formatter against the active model, and a Reset to defaults button.
.prettierrc in advanced sandbox mode: a synthetic .prettierrc virtual file is seeded into the file panel when the JS playground is in advanced sandbox mode. Bidirectional binding between the file's content and PrettierConfigStore -- visual panel toggles write canonical JSON to the file; user edits to the JSON parse back into the store. Loop-prevented via tracked-last-write comparison. Beginner mode and lesson mode are excluded. The synthetic file is filtered out of the runtime payload (snapshotFiles() drops dotfiles), and the file-panel guards (setEntryFile/startRename/deleteFile) refuse to act on synthetic files so it can't be set as entry, renamed, or deleted -- the UI hides those buttons and the handlers no-op as defense-in-depth.
Parity targets
Formatter matches Prettier defaults -- PRETTIER_DEFAULTS mirrors Prettier's published defaults; learners can override per-option via the panel or by editing .prettierrc. No opinionated overrides.
Handles parse errors -- MonacoPrettierFormatterService.format() catches Prettier exceptions, returns null (so Monaco preserves the editor content unchanged), and surfaces the error message via the toolbar's transient banner. The .prettierrc file watcher silently ignores invalid JSON mid-edit (store keeps last-good value) so the UI doesn't whiplash on every keystroke.
Tree-shaken plugins only -- only prettier/plugins/babel, typescript, and estree are imported. No CSS/markdown/yaml/etc. parsers. Lazy-loaded so they aren't in the initial bundle.
Worker offload -- left as the optional milestone called out in the ticket. Format times on representative learner files (a few hundred lines) are imperceptible on the main thread; can revisit if larger curricula appear.
libraries/tools/src/index.ts -- new barrel exports
package.json -- prettier moved to dependencies
Monaco migration parent (#24) and Format toolbar parent (#35) are both satisfied for JS/TS through this work.
Closing -- shipped in `spikersoft-angular`. Summary of the implementation against the parity targets in the ticket:
## Approach
- **Library**: `prettier@3.x` moved from `devDependencies` to `dependencies` so it ships at runtime. We dynamically `import('prettier/standalone')` plus the `babel`, `typescript`, and `estree` parser plugins on the FIRST format request -- the heavy module never enters the initial chunk. Cached in a private field after first load so the second `Format Document` is synchronous.
- **Monaco wiring**: `MonacoPrettierFormatterService.attach(monaco)` registers a `DocumentFormattingEditProvider` for both `javascript` and `typescript` model languages, returning a single full-document `TextEdit` covering the model's full range. `Format Document` (`Shift+Alt+F`) and the editor context menu both go through this provider.
- **Per-learner config**: `PrettierConfigStore` (signals + `localStorage`) holds a typed `PrettierLearnerOptions` shape covering `printWidth`, `tabWidth`, `useTabs`, `semi`, `singleQuote`, `trailingComma`, `bracketSpacing`, `arrowParens`, `endOfLine`. Persistence is imperative inside `set/patch/reset` so it's testable without flushing effects. Defaults match Prettier's canonical defaults exactly so a learner who never touches the panel sees vanilla Prettier behavior.
- **Visual config UI**: `JsFormattingOptionsPanelComponent` (form controls bound directly to typed `setNumber`/`setBoolean`/`setTrailingComma` setters) hosted inside a CDK Overlay popover triggered from a new gear icon in the JS playground's debugger toolbar (`JsFormattingOptionsButtonComponent`). Includes a `Format Now` button that fires the formatter against the active model, and a `Reset to defaults` button.
- **`.prettierrc` in advanced sandbox mode**: a synthetic `.prettierrc` virtual file is seeded into the file panel when the JS playground is in advanced sandbox mode. Bidirectional binding between the file's content and `PrettierConfigStore` -- visual panel toggles write canonical JSON to the file; user edits to the JSON parse back into the store. Loop-prevented via tracked-last-write comparison. Beginner mode and lesson mode are excluded. The synthetic file is filtered out of the runtime payload (`snapshotFiles()` drops dotfiles), and the file-panel guards (`setEntryFile`/`startRename`/`deleteFile`) refuse to act on synthetic files so it can't be set as entry, renamed, or deleted -- the UI hides those buttons and the handlers no-op as defense-in-depth.
## Parity targets
- [x] **Formatter matches Prettier defaults** -- `PRETTIER_DEFAULTS` mirrors Prettier's published defaults; learners can override per-option via the panel or by editing `.prettierrc`. No opinionated overrides.
- [x] **Handles parse errors** -- `MonacoPrettierFormatterService.format()` catches Prettier exceptions, returns `null` (so Monaco preserves the editor content unchanged), and surfaces the error message via the toolbar's transient banner. The `.prettierrc` file watcher silently ignores invalid JSON mid-edit (store keeps last-good value) so the UI doesn't whiplash on every keystroke.
- [x] **Tree-shaken plugins only** -- only `prettier/plugins/babel`, `typescript`, and `estree` are imported. No CSS/markdown/yaml/etc. parsers. Lazy-loaded so they aren't in the initial bundle.
- [ ] **Worker offload** -- left as the optional milestone called out in the ticket. Format times on representative learner files (a few hundred lines) are imperceptible on the main thread; can revisit if larger curricula appear.
## Test coverage
- `prettier-config.store.spec.ts` -- defaults, `patch`, `reset`, normalization, `toJson`/`fromJson` round-trip, malformed JSON handling, persistence across re-instantiation.
- `js-formatting-options-panel.component.spec.ts` -- typed setters, NaN input handling, enum setters, Reset button, Format Now event emission, error display.
- `language-runner-prettierrc.spec.ts` (12 cases) -- seeding, mode/level gating, store->file and file->store propagation, invalid-JSON handling, runtime snapshot filtering, file-panel guards (`setEntryFile`, `startRename`, `deleteFile` all refuse synthetic files), `isSyntheticFile` predicate, deletion re-seed safety net.
- Full `tools` (373 passing) and `spikersoft` (2033 passing) suites green; production build clean.
## Touched files
- `libraries/tools/src/services/prettier/{prettier-config.store.ts, monaco-prettier-formatter.service.ts, index.ts}` -- new
- `libraries/tools/src/components/js-formatting-options/*` -- new (panel + popover button)
- `libraries/tools/src/components/js-debugger-toolbar/js-debugger-toolbar.component.{ts,html,scss}` -- gear icon + format-error surface
- `libraries/tools/src/components/javascript-runner/javascript-runner.ts` -- registers the formatter, wires `formatNowRequested`
- `libraries/tools/src/services/language-runner/language-runner-config.ts` -- new `injectPrettierRc` flag (true on JS config)
- `libraries/tools/src/components/language-runner/language-runner.{ts,html}` -- bidirectional `.prettierrc` binding, dotfile filter on `snapshotFiles()`, `isSyntheticFile` predicate, file-panel guards
- `libraries/tools/src/index.ts` -- new barrel exports
- `package.json` -- `prettier` moved to `dependencies`
Monaco migration parent (#24) and Format toolbar parent (#35) are both satisfied for JS/TS through this work.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Implement actual formatting for JavaScript (and TypeScript if exposed in Monaco with matching language id) in the Monaco playground.
Approach — Prettier
prettier/standalone+prettier/parser-babel(extend withtypescript/babel-tsparsers as needed for TS curricula).registerDocumentFormattingEditProvider('javascript', …)(duplicate registration fortypescriptif distinct model id).Parity targets
.prettierrc-style presets for learners.Dependency
#24.spikerj referenced this issue2026-04-28 23:49:11 +00:00
Closing -- shipped in
spikersoft-angular. Summary of the implementation against the parity targets in the ticket:Approach
prettier@3.xmoved fromdevDependenciestodependenciesso it ships at runtime. We dynamicallyimport('prettier/standalone')plus thebabel,typescript, andestreeparser plugins on the FIRST format request -- the heavy module never enters the initial chunk. Cached in a private field after first load so the secondFormat Documentis synchronous.MonacoPrettierFormatterService.attach(monaco)registers aDocumentFormattingEditProviderfor bothjavascriptandtypescriptmodel languages, returning a single full-documentTextEditcovering the model's full range.Format Document(Shift+Alt+F) and the editor context menu both go through this provider.PrettierConfigStore(signals +localStorage) holds a typedPrettierLearnerOptionsshape coveringprintWidth,tabWidth,useTabs,semi,singleQuote,trailingComma,bracketSpacing,arrowParens,endOfLine. Persistence is imperative insideset/patch/resetso it's testable without flushing effects. Defaults match Prettier's canonical defaults exactly so a learner who never touches the panel sees vanilla Prettier behavior.JsFormattingOptionsPanelComponent(form controls bound directly to typedsetNumber/setBoolean/setTrailingCommasetters) hosted inside a CDK Overlay popover triggered from a new gear icon in the JS playground's debugger toolbar (JsFormattingOptionsButtonComponent). Includes aFormat Nowbutton that fires the formatter against the active model, and aReset to defaultsbutton..prettierrcin advanced sandbox mode: a synthetic.prettierrcvirtual file is seeded into the file panel when the JS playground is in advanced sandbox mode. Bidirectional binding between the file's content andPrettierConfigStore-- visual panel toggles write canonical JSON to the file; user edits to the JSON parse back into the store. Loop-prevented via tracked-last-write comparison. Beginner mode and lesson mode are excluded. The synthetic file is filtered out of the runtime payload (snapshotFiles()drops dotfiles), and the file-panel guards (setEntryFile/startRename/deleteFile) refuse to act on synthetic files so it can't be set as entry, renamed, or deleted -- the UI hides those buttons and the handlers no-op as defense-in-depth.Parity targets
PRETTIER_DEFAULTSmirrors Prettier's published defaults; learners can override per-option via the panel or by editing.prettierrc. No opinionated overrides.MonacoPrettierFormatterService.format()catches Prettier exceptions, returnsnull(so Monaco preserves the editor content unchanged), and surfaces the error message via the toolbar's transient banner. The.prettierrcfile watcher silently ignores invalid JSON mid-edit (store keeps last-good value) so the UI doesn't whiplash on every keystroke.prettier/plugins/babel,typescript, andestreeare imported. No CSS/markdown/yaml/etc. parsers. Lazy-loaded so they aren't in the initial bundle.Test coverage
prettier-config.store.spec.ts-- defaults,patch,reset, normalization,toJson/fromJsonround-trip, malformed JSON handling, persistence across re-instantiation.js-formatting-options-panel.component.spec.ts-- typed setters, NaN input handling, enum setters, Reset button, Format Now event emission, error display.language-runner-prettierrc.spec.ts(12 cases) -- seeding, mode/level gating, store->file and file->store propagation, invalid-JSON handling, runtime snapshot filtering, file-panel guards (setEntryFile,startRename,deleteFileall refuse synthetic files),isSyntheticFilepredicate, deletion re-seed safety net.tools(373 passing) andspikersoft(2033 passing) suites green; production build clean.Touched files
libraries/tools/src/services/prettier/{prettier-config.store.ts, monaco-prettier-formatter.service.ts, index.ts}-- newlibraries/tools/src/components/js-formatting-options/*-- new (panel + popover button)libraries/tools/src/components/js-debugger-toolbar/js-debugger-toolbar.component.{ts,html,scss}-- gear icon + format-error surfacelibraries/tools/src/components/javascript-runner/javascript-runner.ts-- registers the formatter, wiresformatNowRequestedlibraries/tools/src/services/language-runner/language-runner-config.ts-- newinjectPrettierRcflag (true on JS config)libraries/tools/src/components/language-runner/language-runner.{ts,html}-- bidirectional.prettierrcbinding, dotfile filter onsnapshotFiles(),isSyntheticFilepredicate, file-panel guardslibraries/tools/src/index.ts-- new barrel exportspackage.json--prettiermoved todependenciesMonaco migration parent (#24) and Format toolbar parent (#35) are both satisfied for JS/TS through this work.