fix(tinyplace): wire handle transfer end-to-end (Closes #4929)#4998
Conversation
The SDK exposed registry.transfer (POST /registry/names/{name}/transfer)
but nothing above it reached the method: no core controller, no client
method, no UI. Handle transfer was unreachable from the app (tinyhumansai#4929).
Bridge every layer:
- core: handle_tinyplace_registry_transfer resolves the recipient @handle
to its cryptoId + publicKey via registry.get, calls registry.transfer,
and read-back-confirms the returned identity is now owned by the
recipient (fails closed on unresolved recipient / missing key material /
mismatch). Registered via schemas.rs + the controller registry. No PII
logged (status-only debug lines).
- client: apiClient.registry.transfer(name, recipient) + TransferHandleResult.
- UI: a per-handle Transfer action on non-primary owned handles in the
Profiles card opens TransferHandleModal, a destructive-confirm dialog
that requires a recipient + explicit confirm and fails closed on error.
- i18n: seven agentWorld.transferHandle.* keys across en + all 13 locales
(no em dashes).
- tests: TransferHandleModal (wiring, recipient-gating, fail-closed),
ProfilesSection (Transfer opens the modal), and a Rust handler test for
the name/recipient validation branches.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds end-to-end handle transfer support: a validated and registered Rust RPC, an SDK client method, a localized profile modal for non-primary handles, success/error handling, and frontend/core tests. ChangesHandle transfer flow
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 143512ca05
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/agentworld/components/TransferHandleModal.tsx`:
- Around line 55-59: Update the catch block in the transfer handler around the
debug call to log only a fixed failure-state message, removing String(err) and
any raw error details from logging. Preserve the existing setError(String(err))
UI handling and setSubmitting(false) behavior unchanged.
In `@app/src/agentworld/pages/ProfilesSection.tsx`:
- Around line 379-389: Update the server-side
handleTransfer/handle_tinyplace_registry_transfer handler to identify whether
the requested name is a primary handle and reject the request before calling
registry.transfer. Preserve transfers for non-primary handles, and add a test
covering rejection of a primary-handle transfer through the JSON-RPC path.
In `@app/src/lib/i18n/bn.ts`:
- Around line 467-468: Update the Bengali translation for
agentWorld.transferHandle.warning so the recipient clause uses “প্রাপক একমাত্র
মালিক হয়ে যাবেন।” instead of the grammatically awkward wording, while
preserving the rest of the warning.
In `@app/src/lib/i18n/pl.ts`:
- Around line 476-483: Update the Polish ownership-transfer translations in the
agentWorld.transferHandle entries to use “Przekaż handle” and “Przekazanie
handle’a...” wording consistently for the action, title, warning, and
confirmation labels; leave the recipient, submitting, and required-field
messages unchanged.
In `@src/openhuman/tinyplace/manifest.rs`:
- Around line 5362-5382: The test transfer_requires_name_and_recipient only
covers input validation; extend the transfer test coverage around
handle_tinyplace_registry_transfer to include name "@" normalization, mocked
recipient key resolution, SDK transfer failure, successful confirmation, and
ownership mismatch. Add assertions for each outcome while keeping network and
destructive operations mockable.
- Around line 2513-2578: Move handle_tinyplace_registry_transfer from
manifest.rs into schemas.rs, keeping it as the RPC boundary and changing its
return type to RpcOutcome<T>. Extract recipient lookup, transfer, and read-back
confirmation into a dedicated ops.rs operation, then have the handler validate
RPC parameters, call that operation, and serialize its result without retaining
business logic in the controller.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9b0dd2c0-b461-4153-befd-ceac0b5f512f
📒 Files selected for processing (21)
app/src/agentworld/components/TransferHandleModal.test.tsxapp/src/agentworld/components/TransferHandleModal.tsxapp/src/agentworld/pages/ProfilesSection.test.tsxapp/src/agentworld/pages/ProfilesSection.tsxapp/src/lib/agentworld/invokeApiClient.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tssrc/openhuman/tinyplace/manifest.rssrc/openhuman/tinyplace/schemas.rs
…4929-tinyplace-handle-transfer
- schemas.rs: restore rustfmt wrapping of the registry_transfer 'name' input (fixes the Rust Quality + Frontend Checks fmt lanes). - TransferHandleModal: drop String(err) from the failure debug log so no backend/SDK detail is logged (the UI still shows it via setError). - manifest.rs: reject transferring a PRIMARY handle server-side, not just in the UI — a direct JSON-RPC caller could otherwise move the wallet's active identity. Also cover the '@' name-normalization case in the test. - i18n: fix the Bengali warning grammar; use ownership-transfer wording (Przekaż/Przekazanie) in Polish.
|
Pushed 2c089f2 (rebased onto current main). Summary of changes: Red lanes — the sole cause of Frontend Checks, Rust Quality (fmt, clippy), and the PR CI Gate rollup was one rustfmt diff: CodeRabbit review (all 6 addressed):
Codex — fallback-profile reachability noted as a follow-up (the reverse fallback only carries the primary handle, which isn't transferable). @coderabbitai review — re-review after fmt fix + server-side primary-handle guard + PII-safe logging. |
|
✅ Action performedReview finished.
|
…4929-tinyplace-handle-transfer
|
@coderabbitai review Rebased onto current upstream/main (clean merge, only touched tinyflows/caps.rs). All prior CodeRabbit findings are resolved on this head:
No code changes since your last pass — this is a main-sync + re-review request to refresh the stale CHANGES_REQUESTED verdict. |
|
✅ Action performedReview finished.
|
graycyrus
left a comment
There was a problem hiding this comment.
@M3gA-Mind hey! the code looks good to me — this is a solid, careful piece of work. Wiring registry.transfer through core → client → UI with a resolve-then-verify pattern (recipient resolved via registry.get before signing, read-back confirmation after, fail-closed on every unhappy path) is exactly the right amount of caution for a destructive/irreversible action. The follow-up commit adding the server-side primary-handle guard (so a direct JSON-RPC caller can't orphan the wallet's active identity by bypassing the UI check) was a good catch on your own part. PII-safe logging, i18n across all 13 locales, and the fail-closed modal all check out.
Two very minor things, not blocking:
handle_tinyplace_registry_transfernow makes three sequential network round-trips (get-own-identity for the primary check, get-recipient, then transfer). There's a small TOCTOU window between the primary check and the actual transfer call — low real-world impact since only the owning wallet's own signature can execute the transfer, but worth being aware of if this pattern gets reused elsewhere.- In
TransferHandleModal.tsx, thedebug()calls already run under theagentworld:identitynamespace, so the[agentworld:identity]prefix baked into each message string is redundant. Cosmetic only.
On the test-coverage gap CodeRabbit flagged (transfer execution / confirmation-failure paths not covered by the current unit test) — I saw you and CodeRabbit already agreed to defer that to mock-backend JSON-RPC E2E coverage tracked under #4776, so I won't re-litigate it here.
CI is still showing Rust Core Coverage and Frontend Checks as pending — I'll come back once those are green. Nice work on this one.
|
@coderabbitai review All review threads are now resolved on the current head (96bb836) — the earlier CHANGES_REQUESTED is fully addressed:
No code changes needed since your last pass; requesting a re-review to refresh the stale verdict. |
|
✅ Action performedReview finished.
|
oxoxDev
left a comment
There was a problem hiding this comment.
Transfer is correctly authorized end-to-end. Authz rests on the owner's signature over the canonical identity.transfer payload (SDK registry.rs), not on any caller-supplied param — there's no way to transfer a handle the active wallet doesn't own. Recipient is resolved and fail-closed if unregistered or missing key material; the post-transfer read-back rejects on a crypto_id mismatch; the server-side primary-handle lock closes the direct-JSON-RPC bypass of the UI guard. Modal fails closed (keeps the dialog open on error, close disabled in-flight). i18n 14/14.
Non-blocking:
- Rust coverage only exercises the input-validation branch; the security-relevant branches (primary-lock, recipient resolution, read-back mismatch) are uncovered because the inline module can't mock the client. Deferred to #4776 — reasonable, but worth an E2E on a destructive path.
- Nit: a "type the handle to confirm" step would harden the irreversible action beyond the free-text recipient + danger button.
LGTM.
Addresses the maintainer reviews on tinyhumansai#4998: - oxoxDev (confirmation-UX hardening for the irreversible action): add a 'type the handle to confirm' step to TransferHandleModal — the danger button stays disabled until the user re-types the exact handle (case- and @-insensitive), and submit() fails closed if it doesn't match. Adds agentWorld.transferHandle.confirmLabel / confirmMismatch across en + all 13 locales, plus test coverage for the gate. - graycyrus (cosmetic): the debug() calls already run under the 'agentworld:identity' namespace, so drop the redundant '[agentworld:identity]' prefix baked into each message string.
8fc1e71
|
Thanks @graycyrus and @oxoxDev — addressed the review feedback in 8fc1e71: Fixed
Acknowledged, no change
@graycyrus — could you take another look when the re-run is green? This should clear your two notes. |
…4929-tinyplace-handle-transfer
|
@coderabbitai review |
✅ Action performedReview finished.
|
sanil-23
left a comment
There was a problem hiding this comment.
Review: request changes
Thanks for this — the layering is right and a lot of the care shows. Controller registration follows the documented checklist exactly (schema fn, import, all_tinyplace_controller_schemas, RegisteredController, no ad-hoc cli.rs/jsonrpc.rs branch), the logging discipline is clean (status markers only; the String(err) leak is properly fixed in 2c089f22b), and i18n parity is genuinely complete — I verified 9 keys in all 14 locales by hand, real translations, no English placeholders. The frontend fail-closed behaviour is correct and directly tested.
Drift / supersession
Checked, since this is the oldest open PR of the batch: not drifted. git merge-base upstream/main <head> == upstream/main@2df98d858, so main is fully merged in. #4996 (ProfilesSection.tsx) and #4994 (manifest.rs) are both already absorbed with no conflict, and none of #4988–#4997 or #5015 touches registry.transfer — nothing here is superseded.
Where it needs work
The gap is the server-side ownership-transition story. Stating the reassuring part first: the handle cannot end up owned by nobody or by two parties. The backend signature check is the real authority and it is single-owner by construction; the client-side gates added here are advisory. So the ownership invariant holds.
What doesn't hold is the app's reporting of that ownership. What the handler calls a "read-back confirmation" is validation of the transfer POST's own response body, not a read-back — and on mismatch it returns an error asserting the handle was not reassigned, which is a fact the code cannot know, because the POST has already returned 2xx by that point. On an irreversible on-chain action, telling a user they still own a handle they just transferred away is the worst available wrong answer, and it drives them straight into a retry that fails with an unmapped backend error.
That's the blocker (B1 inline). It's a contained fix — replace response-body validation with an actual registry.get, and soften the error text to "submitted but unconfirmed". M1 (retry idempotency) and M2 (unchecked available/status on recipient resolution) fold into the same reorder.
Blocker: B1 — read-back isn't a read-back; failure message asserts an unknowable fact.
Major: M1 retry after a lost response misreports · M2 recipient resolution ignores available/status · M3 primary handles are permanently non-transferable with no path out and no explanation (unassignPrimary isn't exposed in invokeApiClient.ts).
Minor: unnormalized handle sent to the RPC · destructive-path tests stop at input validation, so every path in B1/M1/M2 is untested.
Questions
- Was blocking primary-handle transfer deliberate, or a conservative default? #4929's use case is "gift / account move", and an account move is precisely moving your active handle. If deliberate, M3's disabled-with-explanation is the fix; if not, exposing
unassignPrimaryfirst is the real unblock. - Does
POST /registry/names/{name}/transferreturn the full post-transferIdentitywith the recipient'scryptoId, or an echo of the request? B1's severity turns on this — if it's ever an echo, the current check passes vacuously and confirms nothing. The PR notes this is untestable headless, which is exactly the argument for not depending on the response body. - Is the transfer endpoint replay-protected, or does
sign_fresh_canonical_payload's freshness cover it? Determines whether M1 needs real client-side idempotency or just better error reporting.
Happy to re-review quickly once B1 is addressed — the rest is small.
…4929-tinyplace-handle-transfer
…tinyhumansai#4998) Address sanil-23's CHANGES_REQUESTED on the irreversible on-chain handle transfer (Closes tinyhumansai#4929). B1 — the "read-back" validated the transfer POST's own response body, and on mismatch asserted "the handle was not reassigned" — a fact the handler cannot know once the POST returned 2xx (Identity::crypto_id is #[serde(default)], so an enveloped/renamed body deserializes to "" and falsely fails a transfer that landed). Replace with an actual registry.get read-back AFTER the POST; an unconfirmed read-back now reports "submitted but could not be confirmed; do not retry until you have checked the current owner" and never claims non-reassignment. M1 — hoist the read-only recipient + own-handle reads above signing. If the handle already reads back as the recipient (a retry after a lost-but-applied POST), return the existing state with alreadyTransferred:true instead of re-signing a second valid transfer. Also reject a self-transfer before any network/signing. M2 — recipient resolution now consults AvailabilityResponse.available and Identity.status: an available (released) or expired record is refused rather than transferred to a stale owner. A nonexistent own-handle gets its own diagnosis instead of the misleading "mark it non-primary". M3 — a primary (active) handle now renders a disabled Transfer button with a tooltip explaining the path out (make another handle active first) instead of silently omitting the control. New i18n key in en + 13 locales. Minor — TransferHandleModal sends the normalized handleClean over the wire. Tests — 11 new unit tests over the extracted pure policy helpers (validate_recipient_availability, preflight_own_handle, classify_transfer_readback) plus a self-transfer handler test, covering the B1/M1/M2 paths.
|
@sanil-23 thanks for the careful review — the ownership-reporting framing was exactly right. Addressed all items in What changedThe transfer handler is reordered to read-only preflight → sign+POST → real read-back, with the three decisions extracted into pure, unit-tested helpers:
11 new Rust unit tests over the pure helpers + a self-transfer handler test; Answers to your questionsQ1 — blocking primary transfer: deliberate. It mirrors a real backend invariant, not just a conservative default: Q2 — the response body can't be depended on. Q3 — freshness does NOT provide idempotency; M1 needs the client-side check (done). Ready for re-review. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/lib/i18n/bn.ts (1)
490-490: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse the imperative form for the Follow button.
অনুসরণreads like a noun here;অনুসরণ করুনis the natural button label.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/lib/i18n/bn.ts` at line 490, Update the Bengali translation for the `agentWorld.profileViewer.follow` key to use the imperative button label `অনুসরণ করুন` instead of the noun form, while leaving surrounding translations unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/lib/i18n/pl.ts`:
- Around line 540-541: Update the translation for
agentWorld.transferHandle.primaryLocked to use the established handle and
ownership-transfer terminology, replacing the inconsistent uchwyt/przenieść
wording with the surrounding handle/przekaż phrasing while preserving the
existing guidance.
In `@src/openhuman/tinyplace/manifest.rs`:
- Around line 2604-2627: Retain the Identity extracted from the initial readback
instead of discarding it while deriving confirmed_owner. In the
TransferReadback::Confirmed branch, return that retained identity directly in
the response and remove the second client.registry.get call, preserving the
confirmed: true result with the same read-back data.
---
Outside diff comments:
In `@app/src/lib/i18n/bn.ts`:
- Line 490: Update the Bengali translation for the
`agentWorld.profileViewer.follow` key to use the imperative button label `অনুসরণ
করুন` instead of the noun form, while leaving surrounding translations
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 51ef75e3-f96c-4278-96d0-75ebcb4d9ecf
📒 Files selected for processing (18)
app/src/agentworld/components/TransferHandleModal.tsxapp/src/agentworld/pages/ProfilesSection.test.tsxapp/src/agentworld/pages/ProfilesSection.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tssrc/openhuman/tinyplace/manifest.rs
🚧 Files skipped from review as they are similar to previous changes (14)
- app/src/lib/i18n/ar.ts
- app/src/lib/i18n/hi.ts
- app/src/lib/i18n/zh-CN.ts
- app/src/lib/i18n/pt.ts
- app/src/lib/i18n/en.ts
- app/src/lib/i18n/es.ts
- app/src/lib/i18n/it.ts
- app/src/lib/i18n/fr.ts
- app/src/lib/i18n/ko.ts
- app/src/lib/i18n/id.ts
- app/src/agentworld/pages/ProfilesSection.test.tsx
- app/src/lib/i18n/ru.ts
- app/src/agentworld/pages/ProfilesSection.tsx
- app/src/agentworld/components/TransferHandleModal.tsx
…18n polish (tinyhumansai#4998) - manifest.rs: the read-back Confirmed branch did a second registry.get and discarded the first read-back's identity. Retain the identity from the same read-back we confirmed against and drop the redundant GET. - pl.ts: primaryLocked used "uchwyt/przenieść" — realign to the block's established "handle/przekaż" terminology. - bn.ts: profileViewer.follow uses the imperative button label "অনুসরণ করুন".
|
@coderabbitai I've gone through your CHANGES_REQUESTED review (Run Fixed
Already addressed / withdrawn (no further action)
Validation: |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== Commit summary and changed files =='
git show --stat --oneline --decorate 5d0480d01
echo
echo '== Targeted diff =='
git show --format=fuller --find-renames -- \
src/openhuman/tinyplace/manifest.rs \
app/src/lib/i18n/pl.ts \
app/src/lib/i18n/bn.ts \
5d0480d01
echo
echo '== Transfer handler outline =='
ast-grep outline src/openhuman/tinyplace/manifest.rs \
--items all --type function --match 'handle_tinyplace_registry_transfer|classify_transfer_readback|preflight_own_handle'
echo
echo '== Registry reads in transfer-handler vicinity =='
rg -n -C 5 'handle_tinyplace_registry_transfer|registry\.get|classify_transfer_readback|alreadyTransferred|confirmed' \
src/openhuman/tinyplace/manifest.rs
echo
echo '== Translation entries =='
rg -n -C 3 'transferHandle\.primaryLocked|profileViewer\.follow' \
app/src/lib/i18n/pl.ts app/src/lib/i18n/bn.tsLength of output: 30942
The targeted findings are addressed. ✅ |
…ed-primary (tinyhumansai#4998) M3 made a primary handle render a disabled Transfer control with an explanation instead of omitting the button, so the old test found two "Transfer" buttons and failed (Frontend Checks: Vitest changed-files coverage). Update the destructive transfer suite to the intended behavior: - non-primary handle: pick the ENABLED Transfer control, assert it opens the modal; - primary handle: assert a DISABLED Transfer control carrying the primaryLocked explanation, and that clicking it does NOT open the destructive modal; - unknown (absent primary flag): no Transfer control at all.
|
CI fix ( @coderabbitai review |
|
✅ Action performedReview finished.
|
Summary
registry.transferbut nothing above it reached the method, so a user could not transfer a@handlefrom the app at all.tinyplace_registry_transfer→invokeApiClientregistry.transfer(name, recipient)→ a per-handle Transfer action in the Profiles card that opens a destructive-confirm modal.registry.transfer, and verifies the returned Identity is now owned by the recipient before reporting success. It fails closed on an unresolved recipient, missing key material, or a read-back mismatch.agentWorld.transferHandle.*useT()keys added toen.ts+ all 13 locales (no em dashes); grep-friendlyagentworld:identity/registry_transferlogging with no PII.Problem
The capability existed only in the vendored SDK and was un-bridged at every layer:
vendor/tinyplace/sdk/rust/src/api/registry.rs:254—pub async fn transfer(&self, name, IdentityTransferRequest) -> Result<Identity>(POST/registry/names/{name}/transfer).handle_tinyplace_registry_transferinmanifest.rs, not registered inschemas.rs.registry.transferininvokeApiClient.ts, no transfer action in the UI.Result: transfer could not be initiated from the app. (Established while auditing #4920 —
registry.transferexists, unlike the seller-side marketplace methods, so this is a fixable in-repo wiring gap, not a contract block.)Solution
src/openhuman/tinyplace/manifest.rs):handle_tinyplace_registry_transfer(name, recipient)resolvesrecipientviaregistry.get("@…")(the proven availability path), extracts the recipient'scryptoId+publicKey, buildsIdentityTransferRequest, and callsclient.registry.transfer(&name, req). The SDK attaches the owning wallet's signature, so a caller can only transfer a handle their own wallet owns. Read-back: if the returned identity'scryptoIdis not the recipient's, it returns an error instead of a false success. Registered the controller inschemas.rs(schema + import + all-schemas vec +RegisteredController) — no ad-hoccli.rs/jsonrpc.rsbranch.invokeApiClient.ts):registry.transfer(name, recipient)+TransferHandleResult.ProfilesSection.tsx+ newcomponents/TransferHandleModal.tsx): non-primary owned handles get a Transfer button (a primary handle is locked from transfer). The modal states the action is permanent, requires an explicit recipient and confirm click, disables close while in-flight, and fails closed — on error it keeps the dialog open with the message and never callsonTransferred. Treated with the care of the Emergency-Stop work in feat(safety): Emergency Stop for desktop automation (#4255) #4600.Submission Checklist
TransferHandleModal.test.tsx(confirm callsregistry.transfer('alpha','bravo')+ closes on success; fails closed on error),ProfilesSection.test.tsx(Transfer opens the confirm modal), and a Rusttransfer_requires_name_and_recipienthandler test.schemas.rsregistration by the existingall_controller_schemastests.pnpm test:coverage/test:rustnot run locally (machine limit); CI authoritative.## Related— N/A: no matrix feature ID for this action.registry.transferonly.Closes #NNNin the## Relatedsection.Impact
Related
registry.transfer.)AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/GH-4929-tinyplace-handle-transfer143512ca05b915323dd97ea57cd01d19e6a297ddValidation Run
pnpm --filter openhuman-app format:check— N/A: ran targetedprettier --writeon touched TS files; Rust formatting matched against sibling handlers (cargo fmt not runnable locally, fmt lane authoritative).pnpm typecheck— clean.pnpm i18n:checkandpnpm i18n:english:checkrun locally → both exit 0.registry_*handlers, fmt/clippy lanes authoritative.Validation Blocked
command:cargo test/pnpm test:coverageerror:not run — machine constraint (local Rust/JS builds fill disk).impact:unit, coverage, fmt, and clippy verified by CI.Behavior Changes
@handleto another tiny.place identity from the Profiles card.Parity Contract
Duplicate / Superseded PR Handling
Summary by CodeRabbit
@handlenormalization and requires type-to-confirm for irreversible transfer.