Commit Graph
2127 Commits
Author SHA1 Message Date
Juan Denis bba2019720 feat(operations): absorb service log sessions 2026-08-21 10:31:40 -04:00
Juan Denis 5db3595fe3 feat(operations): replace deploy pill with dock 2026-08-21 10:24:29 -04:00
Juan Denis 5a29dd5a6d feat(operations): add shell state provider 2026-08-21 10:15:23 -04:00
Juan Denis ee58075fae feat(operations): add generalized run stream 2026-08-21 10:09:02 -04:00
Juan Denis 0062a1a9f5 feat(remote-access): add paired resource pickers 2026-08-21 10:02:21 -04:00
Juan Denis 86c2ce5530 feat(templates): add capability-aware server picker 2026-08-21 09:58:52 -04:00
Juan Denis 988cb659d0 feat(new-service): add scoped resource selection 2026-08-21 09:54:56 -04:00
Juan Denis 652d1ab409 feat(frontend): adopt resource picker for targets 2026-08-21 09:51:50 -04:00
Juan Denis 29a95236fd feat(dashboard): add transactional editing session 2026-08-21 09:46:06 -04:00
Juan Denis 08315c99d7 feat(frontend): add editing controls and shortcut scope 2026-08-21 09:36:02 -04:00
Juan Denis c823c8dc15 feat(frontend): add editing session history 2026-08-21 09:28:20 -04:00
Juan Denis cfcc49adc2 feat(frontend): add resource options adapter 2026-08-21 09:24:29 -04:00
Juan Denis ee152eb1bd feat(search): add resource picker contract 2026-08-21 09:20:54 -04:00
Juan Denis 5fb29f14ef fix: preserve ErrorState SDK compatibility [skip version] 2026-08-21 01:35:07 -04:00
Juan Denis f1c7d1f44e refactor: consolidate error state component [skip version] 2026-08-21 01:30:58 -04:00
Juan Denis 5377a1f3e6 fix: prevent tab overflow route crashes 2026-08-21 01:20:43 -04:00
Juan Denis bf8633b239 fix: close alternate release entry points [skip version] 2026-08-21 00:28:54 -04:00
Juan Denis 4abb50efbb Merge main into dev and preserve locale parity 2026-08-21 00:22:07 -04:00
github-actions[bot] 291b44dd93 chore: bump version to 1.7.124 [skip ci] 2026-08-21 04:17:00 +00:00
Juan Denis f9103c8239 Merge pull request #110 from jhd3197/fix/release-promotion-guards
Make releases immutable and enforce dev-to-main promotions
2026-08-21 00:16:48 -04:00
Juan Denis 92e1862f32 fix: make releases immutable and gate main promotions 2026-08-21 00:15:52 -04:00
Juan Denis fd31f7f8ec Merge pull request #109 from jhd3197/feature/localization-bundles
Expand ServerKit with ten localized language bundles
2026-08-20 23:48:56 -04:00
Juan Denis 2d95e186aa fix: align backend locale order with manifest 2026-08-20 23:42:15 -04:00
github-actions[bot] 71d3a8da55 chore: bump version to 1.7.123 [skip ci] 2026-08-21 03:23:18 +00:00
Juan Denis 10307b4a4f Merge branch 'dev' of https://github.com/jhd3197/ServerKit into dev 2026-08-20 23:23:06 -04:00
Juan Denis 4a31f2a8a4 feat: add second-wave locale bundles 2026-08-20 23:22:32 -04:00
github-actions[bot] 1693a47759 chore: bump version to 1.7.122 [skip ci] 2026-08-21 03:02:12 +00:00
Juan Denis 2a25ff839d fix: restore appearance settings rendering 2026-08-20 23:01:37 -04:00
Juan Denis 240ce23765 feat: add next localization batch 2026-08-20 22:50:06 -04:00
Juan Denis 5481057f95 feat: add five localized language bundles 2026-08-20 22:34:34 -04:00
Juan Denis 13c0782fae Merge pull request #107 from jhd3197/dev
Key the last 8,034 literals and complete the Spanish locale
v1.7.121
2026-08-20 22:05:07 -04:00
github-actions[bot] cffa4cdcb8 chore: bump version to 1.7.121 [skip ci] 2026-08-21 02:01:00 +00:00
Juan Denis cc7178f994 fix(i18n): address Copilot review — render keyed labels, un-split sentences
- QuickCreate menu items carried labelKey but rendered item.label
- DataTable and the ColumnsMenu/GroupMenu/SortMenu/SortChipBar helpers
  rendered column.header while 66 files declare headerKey; all now
  resolve through translateLabel when the header is a string
- ExtensionBoundary failure card glued 'The extension' + slug + 'could
  not be loaded' from two fragments; now one Trans key with a component
  slot so translators can reorder
- LogFileList/IconReferenceTab empty states split 'No matches for "'
  around the query; now single keys with a {{query}} placeholder

es.json synced: orphans pruned, changed keys retranslated.
2026-08-20 22:00:55 -04:00
github-actions[bot] 4f77884129 chore: bump version to 1.7.120 [skip ci] 2026-08-21 01:42:32 +00:00
Juan Denis be130be5c4 chore(i18n): ratchet literal ceiling from 8034 to 0
All user-visible literals are now keyed; the ratchet prevents regressions.
2026-08-20 20:23:42 -04:00
Juan Denis 6424fec1d2 feat(i18n): complete Spanish locale — 7,969 keys
Full informal-tú Latin American Spanish translation of every en.json
key, merged over the existing 183-key locale (existing entries kept).
Mechanically verified: exact key-set parity, {{placeholder}} and <N>
tag parity, no empty values. Adversarial 3-reviewer QA pass applied
(28 fixes: glossary consistency, SDK gate/severity/rate-limit terms,
{{action}} quoting, register and punctuation).

check-i18n: no orphans, no placeholder drift, 0 untranslated.
2026-08-20 20:23:41 -04:00
Juan Denis 549a02f73b feat(i18n): regenerate en.json with 7,969 extracted keys
Generated by scripts/extract-i18n.mjs from the inline t() defaults —
was 183 keys before the mass conversion.
2026-08-20 20:23:41 -04:00
Juan Denis cc9c3fb70d feat(i18n): convert all remaining user-visible literals to translation keys
Mass conversion of ~8,000 English literals across 320+ files to
t('app.<file>.<key>', 'English default') calls, plus hand-conversion of
the 43 literals the codemod refused (concatenated toasts, module-level
helpers, class components) using bare t() from i18n/t or restructured
{{placeholder}} keys. Marketplace unsigned-release dialog now translates
the Install/Update verb inside the placeholder.

ESLint: 0 errors. Literal checker: 0 remaining.
2026-08-20 20:23:29 -04:00
Juan Denis f7d421797a fix(settings): use path-based settings routes in AI drawer and docker gate
DrawerHeader navigated to /settings?tab=ai and RequiresDocker linked to
/settings?tab=system, but settings routes are path-based — the query-tab
URLs landed on the wrong screen. Now /settings/ai and /settings/system.
Both files also converted to i18n keys as part of the mass conversion.
2026-08-20 20:23:19 -04:00
Juan Denis 555be9d7f8 Merge pull request #106 from jhd3197/dev
Localize the panel and give extensions the same i18n
v1.7.119
2026-08-20 19:11:10 -04:00
github-actions[bot] 4510143386 chore: bump version to 1.7.119 [skip ci] 2026-08-20 23:04:37 +00:00
Juan Denis 2825c536a8 fix(i18n): address review — reset locale readiness, typed error for bad language, correct parity test reference
- LocaleContext: reset ready on each language change so non-default
  locale bundles trigger a rerender once active
- auth PUT /me: raise typed ValidationError (auth.unsupported_language)
  instead of a hand-shaped error body; error-shape ceiling 1145 -> 1144
- i18n.py docstring: point at the real parity test
  (test_user_language_preference.py)
2026-08-20 19:04:09 -04:00
github-actions[bot] 7af31c3331 chore: bump version to 1.7.118 [skip ci] 2026-08-20 22:50:28 +00:00
Juan Denis ae95ac4478 docs: regenerate MIGRATION_INVENTORY to match measured state 2026-08-20 18:41:14 -04:00
github-actions[bot] a38cdc07b4 chore: bump version to 1.7.117 [skip ci] 2026-08-20 21:31:34 +00:00
Juan DenisandClaude Fable 5 1cb09b9987 fix(i18n): verified es fixes — 2FA term, cron nav, scheduling ambiguity, display probe, RAE comma
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 11:02:36 -04:00
Juan DenisandClaude Opus 5 821eccdb3b feat(i18n): run the locale check against any extension repo
`--locales <dir>` points the four assertions at another tree's bundles, so an
extension repo verifies its own translations with the panel's check instead of
copying it -- copying is how the vendor shims drifted. The languages.json
cross-check is skipped there, since an extension ships bundles rather than a
language list.

It also now reports keys present in en.json but absent from a translation.
Those render English rather than failing, so nothing was catching a bundle that
was quietly half-finished -- which is exactly the state a translation pass
starts in and needs to be able to see.

Used to verify all 13 extension Spanish bundles (2,073 keys) at full parity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 10:47:13 -04:00
Juan DenisandClaude Opus 5 7de4d10eda feat(i18n): codemod for the long tail; census fixes; Intl component options
Built to convert the WordPress extension (1,099 strings across 44 files) and
kept because the same job remains for 12 more extension repos and the panel's
own 8,034-literal tail.

i18n-codemod.mjs keys the same four shapes the census counts, using the same
isCopy rule, so what it converts is exactly what the ratchet measures. It
REFUSES rather than guesses: a hit outside a component needs `t` threaded
through a helper's arguments, which is a design decision, not a rewrite.
Five bugs it had, all caught by building the result rather than by reading it:

- multi-line JSX text became a string literal containing raw newlines. JSX
  collapses that whitespace at render, so the default must too.
- a literal already containing \n (an SSH key placeholder) was emitted raw and
  produced an unterminated string.
- `confirm({ title: 'x' })` matched BOTH the data-object rule and the copy-call
  rule; applying both corrupted the file. Innermost edit wins -- for a call,
  t() is right because nothing resolves a `titleKey` there.
- copy hides in `err.message || 'Failed…'` and `x ? 'A' : 'B'` as often as it
  sits directly in a call, so argument recursion matches the census.
- that recursion then had to stop at nested JSX, or it "translated" a
  `rel="noopener noreferrer"`.

Two census corrections, both of which were inflating the numbers:

- check-i18n-literals recursed into nested JSX inside a copy prop and counted
  the nested element's own attributes. `rel` is not copy; the ratchet was
  unreachable on correct code.
- check-intl-door walked dist/, so an extension repo's bundle double-counted
  every call in it. WordPress reported 56 where the source had 28.

Both censuses and the extractor now take --root (and --out) so an extension
repo runs the panel's own checks instead of copying them -- copying is how the
vendor shims drifted. --update refuses a foreign root: those ceilings are the
panel's.

The format door takes `parts`, passing Intl.DateTimeFormat component options
through for what a named style cannot express -- chart ticks, month labels.
That was the actual reason callers reached for toLocaleDateString([], {...}),
which silently follows the browser; now they keep the format and the locale.

Ceilings re-seeded to the corrected numbers: literals 8034, intl door 123.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 08:50:13 -04:00
Juan DenisandClaude Opus 5 9c0ae9e957 feat(extensions): enforce the SDK gate at install; localize the GUI builtin
Follow-up audit of the extension surface, across all of them rather than one.

THE COMPATIBILITY HOLE. Two places documented an install-time `sdk_version`
check as existing -- app/utils/sdk.py's docstring ("checked at install (manifest
lint) and at load") and the runtime loader's comment, which named
`_assert_manifest_sdk_compatible` -- but `sdk_version_satisfies` had ZERO
production callers; only its own unit test. Consequences split by extension
kind: a runtime ESM extension installed cleanly and failed later at the load
gate, while a builtin/in-repo extension never passes that gate at all, so its
range went unchecked everywhere. The function now exists and runs at install,
beside the min/max_panel_version check, and refuses with what the operator
needs to act: what the extension wants, what this panel offers. Fail-open on a
missing range, matching the load gate's 'grace'. Proved both directions.

This matters now because SDK 1.3.0 added the localization surface and made
i18next/react-i18next shared singletons: an extension that externalizes them
needs a panel at 1.3.0+. Pinning `^1.3.0` is what turns a white screen on an
older panel into a readable message. The bump is additive, so anything pinned
to ^1.2.0 keeps installing and loading. Documented as a version table.

THE AUDIT. Measured, not assumed -- both censuses now take `--root` so they can
run against any extension repo (and refuse `--update` there, since the ceilings
belong to the panel):

  2,105 untranslated literals across 13 extension repos (WordPress alone 1,099
  in 44 files), plus 78 direct toLocale*/Intl calls that follow the BROWSER's
  locale (WordPress 56). None of it was visible to any check before, because
  every census stopped at frontend/src.

serverkit-gui -- the one builtin with real UI -- is converted as the worked
example, importing useTranslation/useFormat from 'serverkit-sdk' exactly as a
third-party extension would. That also closes its own toLocaleTimeString.
Ceiling 8055 -> 8034, intl door 124 -> 123.

The probe now asserts the claim the SDK surface rests on: an extension
resolving i18next through the shared vendor gets the HOST's INITIALISED
instance, and an extension-style t() returns Spanish. A second instance would
be uninitialised and every t() in that extension would render English forever
with nothing reporting it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 00:31:42 -04:00
Juan DenisandClaude Opus 5 cff35cc651 feat(sdk): share i18next with extensions; generate the vendor shims
SDK 1.2.0 -> 1.3.0 (additive).

Auditing the extension surface for plan 79 found the localization gap I
expected and two pre-existing bugs I did not.

The gap: extensions had no way to translate anything. The SDK now re-exports
the HOST's translation surface -- useTranslation, Trans, t, useLocale,
useLabel, useFormat and the intl door -- and i18next/react-i18next join the
shared vendor singletons. That sharing is the load-bearing part: an extension
bundling its own i18next would get a separate, uninitialised instance, so
every t() in it would render its English default forever and nothing would
report it. Exactly the "two React copies" hazard vendorShare already documents,
and exactly the failure mode this plan hit twice in core. They also join
dedupe/optimizeDeps for the same reason React is there.

The bugs, both caused by the same duplication:

- The import map listed `react` and `react-router-dom`, but no shim existed
  for either. A runtime extension importing bare `react` 404'd at load.
- The serverkit-sdk shim re-exported 26 of the SDK's 71 exports. An extension
  importing Button, Modal, EmptyState, formatBytes or timeAgo from the SDK got
  `undefined`. The shim's own comment claimed parity was "asserted by the
  frontend build + backend test_sdk_contract"; nothing asserted it.

Three files independently restated the same module list -- the import map, the
namespace capture, and one hand-written shim per module -- so they drifted.
Now vendorManifest.js is the list, generate-vendor-shims.mjs derives every
shim's exports (from the real package's namespace, or from the SDK entry's own
export statements -- never typed by hand), the import map is built from the
same manifest, and `--check` fails the build on drift. 26 -> 85 exports on the
SDK shim, 6 -> 8 shims, all present.

Manifest contributions gain an optional `labelKey` beside `label` for nav, tabs
and command-palette entries, resolved at render by the same translateLabel core
uses -- data cannot call t(), and a label resolved at import would translate
once and never follow a switch. Omitting it renders `label` verbatim, which is
what every existing extension does, so nothing breaks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 00:16:04 -04:00