The Extensions settings pane (Gallery / Installed / Diagnostics) was the
last large user-facing surface still rendering hardcoded English:
- static/index.html carried the section meta, both loading placeholders,
the Copy diagnostics button, and the whole Trust-model explainer block
as plain HTML with no data-i18n attributes.
- static/panels.js built the runtime / diagnostics cards from template
literals with 14 untranslated row labels, a separate installed card,
asset and warning card titles, sidecar card fields (Origin / Health
path / Health URL / Proxy / Proxy path), the Enabled/Disabled and
true/false badges, and every empty-state string (None, No warnings,
No extension directory is configured., No manifest extensions are
installed in the configured bundle., No loopback sidecars declared.)
inline in JS.
Every one of those call sites now routes through t() and the plain
strings live in the locale bundles. data-i18n is used for the static
HTML (the same mechanism the other selects already use, per #7582
triage), and esc(t(...)) for the template-literal DOM built in
panels.js so no injected text can introduce markup.
Locale keys are added to all 15 shipped locale bundles (en, ru, it, ja,
es, de, zh, zh-Hant, pt, ko, fr, cs, tr, pl, vi), following the same
key-parity contract that the #7602 triage enforced for the
sessions_source keys: inserted right after each locale's identity
(_label:_speech_) line so the brace-bounded locale extraction still
finds the whole block, matching the same pattern upstream used to fix
the sessions_source parity. No locale falls back to English at runtime
because t() resolves every bundle.
tests/test_extensions_settings_panel.py: assertions that used to match
the inline English strings now match the corresponding locale keys, and
the state_unknown_ids hint asserts both the key is used and the raw
English is gone.
Verified:
- python3 -m pytest tests/ -k "i18n or extension or parity or japanese"
-q -> 496 passed
- node --check on the three touched static assets -> all valid
Fixes#7581. Same surgical slice as #7602 (linked from #7579).
* Add scoped extension Configure hook
* docs: fail-closed optional chaining in Configure consumer example + old-Core regression
The documented consumer used ext?.settings.registerConfigure(...), which throws
TypeError on an older E0 Core where settings exists but registerConfigure does not.
Use full optional chaining (ext?.settings?.registerConfigure?.()) and add a Node
regression proving the consumer survives a Core lacking the capability.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
---------
Co-authored-by: Frank Song <franksong2702@gmail.com>
Co-authored-by: n <a@n>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
Renders generic registry post_install guidance + lifecycle requirements in the
Settings extensions gallery card (manifest-driven, no vendor special-casing).
Fixes#4959. Rebased onto current master (squash-applied #4964's net diff so it
coexists with the #4979 sidecar-runtime-status feature that landed in v0.51.697).
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
Codex gate finding on #4979: loadExtensionsPanel({preserveExisting:true}) on the
diagnostics tab treated the 'Loading extension diagnostics…' placeholder as real
content, so a failed refresh returned early (preserveExisting && innerHTML.trim())
and left the panel stuck on 'Loading…' instead of rendering the error. Exclude the
.extensions-loading/.extensions-error placeholders from the preserve check + add a
regression assertion.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
Keep the Diagnostics tab content visible while status refreshes on repeat visits, and reject malformed or far-future sidecar runtime timestamps before rendering relative age text.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* stage #4660 v2 (franksong2702): full-screen PWA mobile sidebar drawer — REBUILT on current master
REBUILD: the prior stage was cut before tonight's #4597/#4584/#4583 ships, so its 3-dot
diff falsely appeared to revert them (Codex CORE/SILENT flags). Rebuilt on current master
(authoritative head a035b1343): clean apply, tonight's fixes confirmed INTACT (_apply_sidebar_
state_db_overrides, bottomDistance, overflow-x:hidden all present). #4660's own change is
additive — full-screen drawer width, edge-guard, mobile close button, overlay->display:none;
does NOT touch the scroll/perf/mobile-pan lines. Nathan concept-greenlit + visual signed off.
71 mobile-layout+swipe tests pass.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
* fix#4660 edge-guard regression: defer preventDefault until horizontal intent + touch-action:pan-y; update 2 tests to assert corrected behavior
* fix#4660 edge-guard hit-test interception (Codex CORE): pointer-events:none + drop guard listener
Deeper fix than the prior preventDefault change: the #pwaSidebarEdgeGuard was a fixed
interactive element (z-index 198, no pointer-events:none) that intercepted hit-testing for
taps/vertical-scrolls starting in the left 24px strip (since .messages is the real scroller
under body:overflow:hidden). Now guard is pointer-events:none with NO dedicated touch
listener; the existing window-level CAPTURE handlers still catch the edge-swipe-to-open.
Removed the orphaned _onPwaSidebarEdgeGuardStart fn. Updated 2 tests to assert the new
non-interactive design. 71 mobile tests pass.
---------
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
UI enable/disable of installed manifest extensions via a bounded fail-safe
extension-overrides.json state file. Safe boundary: no manifest edits, no asset
fetches, no sidecar proxy, no extension-owned routes. /api/extensions/toggle is
auth+CSRF gated; anchored _EXTENSION_ID_RE (no path traversal); atomic UTF-8 write
under _EXTENSION_STATE_LOCK; 32KiB/512-id DoS bounds; disabled override suppresses
asset/sidecar injection end-to-end. Nathan greenlit the extensions-phase-2 concept.
Code byte-faithful from PR head 8769b4a3902e. 49 own tests pass.
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* stage #4612 (santastabber): surface loopback sidecar diagnostics + CHANGELOG
Settings -> Extensions surfaces manifest-declared loopback sidecar companion services
(type:loopback, http(s) on 127.0.0.1/localhost/::1 only, sanitized origin+health_path)
as read-only health cards w/ a browser-side credentials-omitted health probe. Backend
hard-whitelists scheme + loopback host, rejects userinfo/path/query/traversal, rebuilds
output from parsed components (no echo of rejected input); frontend esc()'s every field,
whitelists badge status, fetch never reads body. Purely additive to GET /api/extensions/status.
Code applied byte-faithful from PR head 7080929f3727. 33 own tests pass.
Nathan concept-approved (loopback-companion direction).
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* fix#4612 Codex gate findings: reject encoded query/fragment in health_path + align CSP loopback allowlist
Codex SHIP-WITH-FIXES (2 reproduced SILENT findings):
1. _normalize_sidecar_health_path percent-decoded AFTER the raw query/fragment ban,
so /health%3Ftoken=abc -> ?token=abc survived into the probed URL. Now re-rejects
decoded ? and # + regression test (%3F/%23 skipped with sidecar_health_path_rejected).
2. The origin validator accepts https:// and [::1], but CSP connect-src only listed
http 127.0.0.1/localhost -> accepted https/IPv6 loopback sidecars would be CSP-blocked.
Aligned _CSP_CONNECT_BASE (added https v4/localhost + http(s) [::1]) + updated the 4
exact-string CSP tests + a regression test asserting all loopback forms are allowed.
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
* Release v0.51.564 — Release TW (loopback sidecar diagnostics; #4612)
* fix(#4612): drop browser-invalid [::1]:* CSP source (browser-smoke fail)
The CSP-loopback-alignment fix added http(s)://[::1]:* to connect-src, but CSP
host-source grammar can't express a port wildcard on a bracketed IPv6 literal —
Chromium rejects '[::1]:*' as an invalid source, which browser-smoke flags as a
console error (CI fail). Keep the valid https://127.0.0.1:*/localhost:* additions;
drop the IPv6 wildcard entries. IPv6 [::1] sidecars are still accepted by the
validator (displayed in the card); their browser health probe surfaces as
'blocked' under CSP unless an operator adds a specific-port [::1]:<port> via
HERMES_WEBUI_CSP_CONNECT_EXTRA. Tests updated to match + assert [::1]:* absent.
---------
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>