* admin: parsed JSONC settings editor with form view (#7603, #7666)
Takes over #7666 / closes#7603. Squashed rebase of 32 commits onto
current develop (which has since absorbed admin design rework #7716
and admin i18n fixes#7736 — granular history preserved on
takeover/7666-admin-settings-editor before this squash, see PR
description for the original commit log).
Highlights:
- New parsed JSONC settings editor under
admin/src/components/settings/ — FormView, ModeToggle,
ParseErrorBanner, JsoncNode dispatcher, leaf widgets (string,
number, bool, null, env pill), and pure helpers (comments,
envPill, jsoncEdit, labels, templateComments).
- ${VAR:default} env placeholders render as editable inline inputs
that round-trip through the raw textarea (env-pill spec asserts
this; docker-template spec protects against form-view degradation
on env-heavy configs).
- Schema-driven help text sourced from settings.json.template,
inlined at build time via vite (drops the runtime fs.allow
widening that earlier iterations needed).
- ModeToggle switches between FormView and raw textarea on
/admin/settings; parse errors surface in a non-blocking banner.
- jsonc-parser dep added; pure helpers wrap modify() for stable
edits that preserve key order and trailing comments (stops at
end-of-line so trailing-comment trains don't bleed into the next
property).
- i18n keys added for form mode, parse error, env pill,
default_label, and input aria.
- Playwright specs cover form view, env pill, parse error banner,
raw round-trip, and form-mode regressions called out in #7666
review (stable React keys from AST offsets, save-toast on server
ack only, NumberInput draft sync, parse-error flash during
initial load, .settings CSS conflict resolution, focus retention
via rAF, IconButton type defaulting to 'button').
Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(admin): stabilise React keys to prevent focus loss in settings editor
Switch React keys in JsoncNode and FormView from byte offsets to stable
JSON paths (`getNodePath(...).join('.')`). Byte offsets shift on every
keystroke because the edit changes the surrounding character count,
which forces React to remount inputs and lose focus mid-typing.
- Object children key on the property path.
- Array elements key on their JSON path index.
- Add a Playwright regression test pinning focus stability for array
element edits.
Co-authored-by: John McLear <john@mclear.co.uk>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(admin): stop trailing /* */ comments from bleeding into next key's label (#7740)
In the parsed settings form view, each key's row was rendering its label
as the previous keys' source lines concatenated together. Root cause:
findLeading() in admin/src/components/settings/comments.ts treated any
line ending in `*/` as a comment continuation, so a JSON line like
"altF9": true, /* focus on the File Menu and/or editbar */
was absorbed into the next sibling's leading comment block, and then
each subsequent key picked up an even longer accumulation.
- Tighten findLeading's isComment check to only match structural comment
lines (`//`, `/*`, or a `*`-prefixed continuation/close), so JSON code
with a trailing block comment no longer matches.
- Surface leading and trailing comments separately from the template
map. Leaf rows with only a trailing same-line comment now render the
humanized key as the row label and the comment as the help text below
the control, matching settings.json.template's convention (and #7740's
recommendation that "helper text should be below").
- Add unit tests pinning the regression and the JSDoc/`//` leading
styles, plus a Playwright spec that asserts altC's row carries a
clean label and the "focus on the Chat window" help text.
Closes#7740.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ayushi Gupta <ayushigupta36881@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(admin): restore i18n on /admin by copying locales to the right path
The admin SPA fetches `/admin/locales/<lang>.json`. Building with
vite-plugin-static-copy and `src: '../src/locales'` was placing the
115 core locale files at `src/templates/admin/src/locales/` (the
plugin's `dirClean` strips a leading `../` but keeps the remaining
parent path). The express admin handler 404'd those fetches, fell
back to serving `index.html`, JSON.parse silently failed, and every
`<Trans>` rendered its raw key — see #7586.
Replace the plugin with a small inline build/dev plugin: at build
time copy `src/locales/*.json` to `<outDir>/locales/`; in dev serve
the same files via middleware so `vite dev` also works. Drop the
now-unused `vite-plugin-static-copy` dependency.
Add regression coverage that none of the existing admin specs had:
- backend HTTP test for GET /admin/locales/{en,de}.json
- Playwright admin i18n spec asserting translated <h1> renders for
the default locale and for ?lng=de, plus a request-level check
that the response is JSON, not the SPA fallback.
Closes#7586
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(admin): bundle locales via import.meta.glob, drop copy plugin
The first pass at #7586 replaced vite-plugin-static-copy with a
custom build/dev plugin that copied src/locales/*.json into the
admin output and served them in dev. That works, but the
vite-plugin-static-copy README explicitly recommends the public
directory or a JS import for this case, and the import path is
strictly cleaner: no copy step, no /admin/locales/* express route,
no SPA-fallback-shaped failure mode.
Use import.meta.glob in admin/src/localization/i18n.ts so each
language ships as its own hashed JSON chunk and is lazy-loaded on
demand. The vite config goes back to just react + base + outDir.
The plugin namespaces (e.g. ep_admin_pads) keep their existing
admin/public/<ns>/<lang>.json layout.
Tests:
- Drop tests/backend/specs/adminLocales.ts — it asserted on a
/admin/locales/<lang>.json route that this approach no longer
uses; the regression mechanism it pinned doesn't exist anymore
and the test required the admin frontend to be built before the
backend test runs (which CI doesn't do).
- Keep tests/frontend-new/admin-spec/admini18n.spec.ts (rendered
<h1> in default and ?lng=de). Verified red→green: reverting just
the loader to the pre-fix /admin/locales fetch makes both specs
fail; restoring makes them pass.
Also update pnpm-lock.yaml to drop the now-unused
vite-plugin-static-copy entries — fixes ERR_PNPM_OUTDATED_LOCKFILE
that was failing every CI install upfront.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>