LanguageRunner.ngOnInit (libraries/tools/src/components/language-runner/language-runner.ts), the two effect() blocks gated on config.injectPrettierRc that implement the bidirectional .prettierrc ↔ PrettierConfigStore binding (Gitea #36).
Behavior
In the JavaScript playground (advanced sandbox mode), when the user edits the synthetic .prettierrc file in the editor:
User opens .prettierrc containing '{"tabWidth":4}' (the canonical form last written by the store→file effect).
User pretty-prints it manually: '{"tabWidth":4,\n "semi":false}'. Valid JSON, intentionally hand-formatted.
The file→store effect fires. prettierStore.fromJson(...) succeeds. lastPrettierRcContent is set to the literal user content (per the in-code comment, intentionally — to keep the panel→file canonicalization path working).
The store changes ({tabWidth:4} → {tabWidth:4, semi:false}).
The store→file effect re-fires. prettierStore.toJson() returns canonical JSON (different from the user's literal). existing.content !== json is true; existing.content === lastPrettierRcContent is true (we just set them equal). So the file is upserted with the canonical form.
The user sees their hand-formatted JSON replaced with canonical one-line JSON immediately on the keystroke that completed the parse. Monaco's writeValue re-runs setValue which moves the cursor to the start of the model.
Net effect: any successful .prettierrc edit is auto-reformatted under the user's cursor.
Why this exists
The author was aware of the loop-prevention problem and chose to track the literal user content (rather than the canonical) so the panel→file path keeps working. The trade-off was conscious — see the comment block at lines ~488–506 of language-runner.ts. But the trade-off has a UX cost that may not have been considered: panel-driven canonicalization happens NOT only on the next panel push, but ALSO immediately on the very same keystroke that just succeeded in parsing.
Suggested fix
Replace the equality-based loop-prevention with an explicit source-of-change tag:
privatelastWriteOrigin:'panel'|'file'|'init'='init';// store→file effect:
if(this.lastWriteOrigin==='file'){this.lastWriteOrigin='panel';// reset for next iteration
return;}this.lastWriteOrigin='panel';this.upsertPrettierRcFile(json);// file→store effect:
if(this.lastWriteOrigin==='panel'){this.lastWriteOrigin='file';return;}this.lastWriteOrigin='file';constok=this.prettierStore.fromJson(file.content);
This lets the user's hand-formatted JSON survive intact in the file. The store stays in sync via fromJson. The panel-driven path still canonicalizes because the panel push originates with lastWriteOrigin='panel' from outside both effects.
Severity
Yellow — the binding works correctly; this is a UX surprise, not a bug. Power users expect their typed JSON to survive editing. Consider this as a follow-up to #36 once the binding has shipped and we have user feedback on the editing experience.
Acceptance
A user can hand-format .prettierrc JSON (multi-line, custom whitespace, reordered keys) and have it survive successful parses.
The visual options panel still rewrites .prettierrc to canonical JSON when used.
A spec proves: typing valid JSON into .prettierrc does not change the file's content beyond what the user typed.
## Surface
`LanguageRunner.ngOnInit` (`libraries/tools/src/components/language-runner/language-runner.ts`), the two `effect()` blocks gated on `config.injectPrettierRc` that implement the bidirectional `.prettierrc` ↔ `PrettierConfigStore` binding (Gitea #36).
## Behavior
In the JavaScript playground (advanced sandbox mode), when the user edits the synthetic `.prettierrc` file in the editor:
1. User opens `.prettierrc` containing `'{"tabWidth":4}'` (the canonical form last written by the store→file effect).
2. User pretty-prints it manually: `'{"tabWidth":4,\n "semi":false}'`. Valid JSON, intentionally hand-formatted.
3. The file→store effect fires. `prettierStore.fromJson(...)` succeeds. `lastPrettierRcContent` is set to the **literal user content** (per the in-code comment, intentionally — to keep the panel→file canonicalization path working).
4. The store changes (`{tabWidth:4}` → `{tabWidth:4, semi:false}`).
5. The store→file effect re-fires. `prettierStore.toJson()` returns canonical JSON (different from the user's literal). `existing.content !== json` is true; `existing.content === lastPrettierRcContent` is true (we just set them equal). So the file is **upserted with the canonical form**.
6. The user sees their hand-formatted JSON replaced with canonical one-line JSON immediately on the keystroke that completed the parse. Monaco's `writeValue` re-runs `setValue` which moves the cursor to the start of the model.
Net effect: any successful `.prettierrc` edit is auto-reformatted under the user's cursor.
## Why this exists
The author was aware of the loop-prevention problem and chose to track the literal user content (rather than the canonical) so the panel→file path keeps working. The trade-off was conscious — see the comment block at lines ~488–506 of `language-runner.ts`. But the trade-off has a UX cost that may not have been considered: panel-driven canonicalization happens NOT only on the next panel push, but ALSO immediately on the very same keystroke that just succeeded in parsing.
## Suggested fix
Replace the equality-based loop-prevention with an explicit source-of-change tag:
```ts
private lastWriteOrigin: 'panel' | 'file' | 'init' = 'init';
// store→file effect:
if (this.lastWriteOrigin === 'file') {
this.lastWriteOrigin = 'panel'; // reset for next iteration
return;
}
this.lastWriteOrigin = 'panel';
this.upsertPrettierRcFile(json);
// file→store effect:
if (this.lastWriteOrigin === 'panel') {
this.lastWriteOrigin = 'file';
return;
}
this.lastWriteOrigin = 'file';
const ok = this.prettierStore.fromJson(file.content);
```
This lets the user's hand-formatted JSON survive intact in the file. The store stays in sync via `fromJson`. The panel-driven path still canonicalizes because the panel push originates with `lastWriteOrigin='panel'` from outside both effects.
## Severity
Yellow — the binding works correctly; this is a UX surprise, not a bug. Power users expect their typed JSON to survive editing. Consider this as a follow-up to #36 once the binding has shipped and we have user feedback on the editing experience.
## Acceptance
- A user can hand-format `.prettierrc` JSON (multi-line, custom whitespace, reordered keys) and have it survive successful parses.
- The visual options panel still rewrites `.prettierrc` to canonical JSON when used.
- A spec proves: typing valid JSON into `.prettierrc` does not change the file's content beyond what the user typed.
Fixed in spikersoft-angular PR #56. Replaced the equality-based loop-prevention's failure mode with a one-shot prettierRcStoreChangeFromFile flag: the file→store effect raises it on a successful user-edit parse, and the store→file effect consumes it to skip the canonical rewrite, preserving the user's hand-formatted text. Panel-driven changes leave the flag false, so panel→file canonicalization is unchanged. Two new specs cover (a) hand-formatted JSON surviving a successful parse and (b) a later panel change still canonicalizing. Will close once PR #56 merges.
Fixed in `spikersoft-angular` PR #56. Replaced the equality-based loop-prevention's failure mode with a one-shot `prettierRcStoreChangeFromFile` flag: the file→store effect raises it on a successful user-edit parse, and the store→file effect consumes it to skip the canonical rewrite, preserving the user's hand-formatted text. Panel-driven changes leave the flag false, so panel→file canonicalization is unchanged. Two new specs cover (a) hand-formatted JSON surviving a successful parse and (b) a later panel change still canonicalizing. Will close once PR #56 merges.
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.
Surface
LanguageRunner.ngOnInit(libraries/tools/src/components/language-runner/language-runner.ts), the twoeffect()blocks gated onconfig.injectPrettierRcthat implement the bidirectional.prettierrc↔PrettierConfigStorebinding (Gitea #36).Behavior
In the JavaScript playground (advanced sandbox mode), when the user edits the synthetic
.prettierrcfile in the editor:.prettierrccontaining'{"tabWidth":4}'(the canonical form last written by the store→file effect).'{"tabWidth":4,\n "semi":false}'. Valid JSON, intentionally hand-formatted.prettierStore.fromJson(...)succeeds.lastPrettierRcContentis set to the literal user content (per the in-code comment, intentionally — to keep the panel→file canonicalization path working).{tabWidth:4}→{tabWidth:4, semi:false}).prettierStore.toJson()returns canonical JSON (different from the user's literal).existing.content !== jsonis true;existing.content === lastPrettierRcContentis true (we just set them equal). So the file is upserted with the canonical form.writeValuere-runssetValuewhich moves the cursor to the start of the model.Net effect: any successful
.prettierrcedit is auto-reformatted under the user's cursor.Why this exists
The author was aware of the loop-prevention problem and chose to track the literal user content (rather than the canonical) so the panel→file path keeps working. The trade-off was conscious — see the comment block at lines ~488–506 of
language-runner.ts. But the trade-off has a UX cost that may not have been considered: panel-driven canonicalization happens NOT only on the next panel push, but ALSO immediately on the very same keystroke that just succeeded in parsing.Suggested fix
Replace the equality-based loop-prevention with an explicit source-of-change tag:
This lets the user's hand-formatted JSON survive intact in the file. The store stays in sync via
fromJson. The panel-driven path still canonicalizes because the panel push originates withlastWriteOrigin='panel'from outside both effects.Severity
Yellow — the binding works correctly; this is a UX surprise, not a bug. Power users expect their typed JSON to survive editing. Consider this as a follow-up to #36 once the binding has shipped and we have user feedback on the editing experience.
Acceptance
.prettierrcJSON (multi-line, custom whitespace, reordered keys) and have it survive successful parses..prettierrcto canonical JSON when used..prettierrcdoes not change the file's content beyond what the user typed.Fixed in
spikersoft-angularPR #56. Replaced the equality-based loop-prevention's failure mode with a one-shotprettierRcStoreChangeFromFileflag: the file→store effect raises it on a successful user-edit parse, and the store→file effect consumes it to skip the canonical rewrite, preserving the user's hand-formatted text. Panel-driven changes leave the flag false, so panel→file canonicalization is unchanged. Two new specs cover (a) hand-formatted JSON surviving a successful parse and (b) a later panel change still canonicalizing. Will close once PR #56 merges.