Skip to content

feat(dashboard/hands): GitHub/Codeberg registry source selector#6142

Merged
houko merged 4 commits into
mainfrom
feat/hands-codeberg-ui
Jun 17, 2026
Merged

feat(dashboard/hands): GitHub/Codeberg registry source selector#6142
houko merged 4 commits into
mainfrom
feat/hands-codeberg-ui

Conversation

@houko

@houko houko commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #6137.

The Hands ("开发团队") catalogue is pulled from a registry that defaulted to GitHub, with no way to switch to Codeberg from the dashboard — the backend registry.registry_host config (landed in #6095 / #6103 for the Codeberg-hosted registry) could only be changed by hand-editing config.toml. This surfaces it as a UI control, and hardens the backend host handling it relies on.

Change

A compact source selector in the Hands page header, wired entirely through the existing config data-layer hooks:

  • ReaduseFullConfig()registry.registry_host. Unset or an explicit github.com shows GitHub (the default); codeberg.org (any case / trailing slash) shows Codeberg; anything else shows Custom (host).
  • WriteuseSetConfigValue({ path: "registry.registry_host", value }). GitHub clears it to null; Codeberg sets the Codeberg base URL. The mutation already invalidates the config cache, so the control reflects the persisted value.
  • Disabled while the config is loading, refetching (post-write window — prevents the controlled <select> snapping back to the stale value), errored (prevents a stray click overwriting the real config with null), or a write is in flight.
  • A failed live reload (reload_error) toasts an error, not a misleading success; restart_required toasts the restart variant.

Backend root-cause fix

registry_sync::registry_urls treated any Some(host) as a Forgejo/Codeberg forge, so an explicit registry_host = "https://github.com" derived the Forgejo archive scheme (/archive/{branch}.tar.gz, prefix {repo}/) — which does not match GitHub's tarball layout (/archive/refs/heads/{branch}.tar.gz, prefix {repo}-{branch}/), so the sync silently failed. It now trims trailing slashes, drops an empty host, and folds an explicit github.com (any case) back to the GitHub branch. This is what lets the UI honestly treat github.com as GitHub instead of masking it as a broken value.

Conventions

Follows the dashboard rules: API access only through src/lib/queries / src/lib/mutations hooks, theme tokens (no hex), lucide-react icon, accessible name via <label htmlFor>, TypeScript strict (no any), loading/empty/error states handled.

Files

  • crates/librefang-runtime/src/registry_sync.rs — github.com host folding + test.
  • crates/librefang-api/dashboard/src/pages/HandsPage.tsxRegistrySourceSelector.
  • crates/librefang-api/dashboard/src/pages/HandsPage.test.tsx — config-hook mocks + selector tests.
  • crates/librefang-api/dashboard/src/locales/{en,zh,uk}.jsonhands.registry_source* strings.

Verification

  • Backend: cargo test -p librefang-runtime registry_urls (4/4, incl. the explicit-github-host case) and cargo clippy -p librefang-runtime -D warnings pass.
  • Dashboard: pnpm typecheck, eslint (0 errors), i18n-parity (all locales), and vitest run src/pages/HandsPage.test.tsx (20/20, incl. default GitHub, switch-to-Codeberg writes the host, switch-back clears to null, explicit github.com reads as GitHub, disabled on error / during refetch, reload_error → error toast, restart_required toast) pass.

Out of scope

The issue screenshot also shows the "开发团队" hand's own GitHub-specific settings (gh CLI, GitHub token). Making that hand operate against Codeberg is a change to the hand's HAND.toml, which lives in the external (Codeberg-hosted) registry, not this repo — so it is not addressable here.

Closes #6137.

The hand and skill registry source defaulted to GitHub with no way to switch to Codeberg from the UI — the backend `registry.registry_host` config (added in #6095 / #6103 for the Codeberg-hosted registry) was only reachable by hand-editing config.toml.

This adds a compact source selector to the Hands page header, wired through the existing config query/mutation hooks:
- Reads `registry.registry_host` via `useFullConfig()`; maps unset/`https://github.com` to GitHub and `https://codeberg.org` to Codeberg.
- Writes via `useSetConfigValue({ path: "registry.registry_host", value })` — GitHub clears it to null (the default), Codeberg sets the Codeberg base URL. The mutation already invalidates the config cache, so the control reflects the persisted value.
- A pre-existing custom forge URL (set out-of-band in config.toml) is shown as a read-only "Custom (host)" option so it is never silently misrepresented as GitHub/Codeberg; switching to GitHub/Codeberg from there still works.
- Surfaces `restart_required` from the set response in the success toast.

No backend changes — the field, the GET /api/config and POST /api/config/set routes, and the GitHub-vs-Forgejo URL handling in registry_sync all already exist.

i18n: new `hands.registry_source*` keys added to en, zh, uk (locale parity check passes).

Verification: dashboard `pnpm typecheck`, `eslint` (0 errors), `i18n-parity`, and `vitest run src/pages/HandsPage.test.tsx` (15/15, incl. 3 new selector tests: default GitHub, switch-to-Codeberg writes the host, switch-back clears to null) all pass.
@github-actions github-actions Bot added size/M 50-249 lines changed no-rust-required This task does not require Rust knowledge labels Jun 17, 2026
…mment, remove shadowing aria-label

Multi-line JSDoc on RegistrySourceSelector contained issue-number refs
and described what the code does — both prohibited by project comment
rules.  The two-line inline comment for the custom-host option exceeded
the one-line-max rule.  The aria-label on the <select> was redundant
because the <label htmlFor> already supplies the accessible name;
keeping both caused aria-label to silently shadow the programmatic
label association.

houko commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Pushed one follow-up commit (6052ec3) with three mechanical fixes against the project comment/accessibility rules:

1. Removed the 6-line JSDoc on RegistrySourceSelector — CLAUDE.md forbids multi-line comment blocks (one short line max) and prohibits referencing issue numbers inside source comments (#6095/#6103, #6137) since those rot as the codebase evolves and belong in the PR description, which is exactly where they already appear.

2. Collapsed the two-line inline comment (// A pre-existing custom forge URL is shown read-only...) to one line — same rule.

3. Removed the redundant aria-label on the <select> — the element is already correctly labelled via <label htmlFor="hands-registry-source">, which is the preferred HTML pattern. When both are present, aria-label silently overrides the programmatic label association, making the <label> element invisible to assistive technology. The tests continue to pass unchanged because @testing-library resolves accessible names from the <label> association as well.

No logic or test changes — the three new selector tests still cover the same cases.


Generated by Claude Code

Evan added 2 commits June 17, 2026 14:52
Addresses review findings on the registry-source selector, all in code this branch introduced.

- Explicit `registry_host = "https://github.com"` is no longer masked as the GitHub option. registry_sync treats any `Some(host)` as the Forgejo archive scheme (`/archive/{branch}.tar.gz`, prefix `{repo}/`), which does not match GitHub's tarball layout — so an explicit github.com is a broken value distinct from the `null` default. It now surfaces as `Custom (...)`, and picking GitHub clears it to `null` to fix it. Previously the dropdown showed "GitHub" as if healthy and `next === current` made the GitHub option a no-op, so the bad value was unfixable from the UI.
- The control is now disabled during the post-write refetch (`isFetching`) and on a config-read error (`isError`), not only on `isLoading`/`isPending`. This stops the controlled `<select>` snapping back to the stale value before the invalidated query resolves, and prevents a stray click from overwriting a real config with `null` when the current value could not be read.
- The read-only custom `<option>` is no longer `disabled`: a controlled `<select>` whose value points at a disabled option is undefined across browsers (and warns in React). Re-selecting custom is a no-op via the existing `handleChange` guard.
- A failed live reload (`reload_error` in the set response) now toasts an error instead of a misleading success — the value persisted but is not in effect, mirroring ConfigPage.

Tests: +5 (explicit-github→custom + clears to null, disabled on error, disabled during refetch, reload_error→error toast, restart_required toast). The test harness now uses a stable addToast spy and captures the mutation's onSuccess/onError so the toast branches are actually exercised. typecheck, eslint (0 errors), and vitest (20/20) pass.

Not changed: a mixed-case/trailing-slash custom Codeberg URL (e.g. "https://Codeberg.org/") reads correctly as Codeberg but is not rewritten to the canonical host — that is a backend host-normalization concern in registry_sync, out of scope for this selector.
…b default

Root-cause fix for the issue the dashboard selector was papering over: registry_urls treated ANY Some(host) as a Forgejo/Codeberg forge, so an explicit registry_host = "https://github.com" derived the Forgejo archive scheme (`/archive/{branch}.tar.gz`, prefix `{repo}/`) instead of GitHub's (`/archive/refs/heads/{branch}.tar.gz`, prefix `{repo}-{branch}/`). GitHub's tarball extracts to `{repo}-{branch}/`, so the prefix never matched and the sync silently failed to find the files.

registry_urls now trims trailing slashes, drops an empty host string, and folds an explicit github.com host (any case) back to the GitHub branch, so it can never reach the Forgejo path with an incompatible scheme. Codeberg and other forges are unchanged.

With the backend tolerant, the dashboard selector now shows an explicit github.com host as GitHub (it is equivalent to the unset default) rather than masking it as a broken "custom" value — the two layers now agree that github.com == GitHub.

Tests: registry_urls_explicit_github_host_uses_github_scheme covers "https://github.com", a trailing slash, mixed case, and the empty string. The HandsPage selector test asserts github.com reads as GitHub. cargo test -p librefang-runtime registry_urls (4/4) and clippy -D warnings pass; dashboard typecheck/eslint/vitest (20/20) pass.
@github-actions github-actions Bot added area/runtime Agent loop, LLM drivers, WASM sandbox size/L 250-999 lines changed and removed size/M 50-249 lines changed no-rust-required This task does not require Rust knowledge labels Jun 17, 2026
@houko
houko enabled auto-merge (squash) June 17, 2026 06:16
@houko
houko merged commit 9e8a4d1 into main Jun 17, 2026
35 checks passed
@houko
houko deleted the feat/hands-codeberg-ui branch June 17, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/runtime Agent loop, LLM drivers, WASM sandbox size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

开发团队支持选择 GitHub Codeberg

2 participants