Skip to content

fix: staging bug sweep batch 2 — messaging, payments, activity, bounty, i18n, social, moonpay#137

Closed
oxoxDev wants to merge 14 commits into
tinyhumansai:mainfrom
oxoxDev:fix/staging-bug-sweep-2
Closed

fix: staging bug sweep batch 2 — messaging, payments, activity, bounty, i18n, social, moonpay#137
oxoxDev wants to merge 14 commits into
tinyhumansai:mainfrom
oxoxDev:fix/staging-bug-sweep-2

Conversation

@oxoxDev

@oxoxDev oxoxDev commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Second batch of staging.tiny.place bug fixes — 8 issues, one micro-commit each. Independent of #123 (no file overlap).

Fixes

Messaging

  • DMs by handle/key — peer lookup was non-deterministic: a base64 messaging key whose chars were base58-safe was misclassified as a cryptoId and 404'd on directory.getAgent. Now classifies input unambiguously (handle → base64 raw key → base58 cryptoId), and resolveEncryptionAddress no longer silently falls back to the wallet identity key (which has no Signal key bundle → send 404s later) — it throws an actionable error at resolve time. (SDK + website)
  • Inbox "failed to load" — the inbox signed requests as the raw wallet cryptoId when no @handle had resolved, so the backend rejected the directory-as request (401/403/404). Now uses the agent-auth path when there's no handle, holds the query until owned-identities settles, and treats 401/403/404 alike as the actionable connect-a-handle state.

Payments

  • Buy domain "transaction reverted" — the payer-signed x402 TransferChecked for registration was built against the SDK's hardcoded mainnet USDC mint while the app runs on devnet, so the on-chain payment reverted (the mint account + its derived ATAs don't exist on the wrong cluster). The staging backend advertises the devnet USDC mint (4zMMC9…DncDU) via GET /payments/supported. Set NEXT_PUBLIC_SOLANA_USDC_MINT to it and make publicUsdcMint() fall back to the devnet mint by active cluster when unset, so a devnet build never silently settles against the mainnet mint; deduped use-wallet-balances onto the same resolver.

Activity

  • Shows actor, not action — rows showed only the actor for any kind the describe() switch didn't list, notably the backend's ledger.<TYPE> fallback kinds and canonical group.fee/arbitration.fee/fee/event.refund. Extracted one shared formatter (used by the Activity list + header marquee) that normalizes ledger.<TYPE> to its canonical kind, adds the missing cases, and humanizes unknown kinds.

Marketplace

  • Submit bounty buggy — the budget was free-text sent unvalidated, but posting escrows real funds, so a malformed amount produced a confusing escrow failure. Made the budget a numeric input and validate title + positive amount + present wallet before mutating, surfacing the reason inline.

i18n

  • Spanish "doesn't work" — the language toggle changed only a handful of feed/home/poker strings because the high-traffic chrome (primary nav, sidebar links, the Need-an-Agent CTA, and the Settings page itself) rendered hardcoded English and never went through t(). Added nav + settings namespaces to the en/es resources and routed those surfaces through t() (section labels, Docs/Feedback/Settings links, menu aria-labels, Settings headings + theme names). Brand proper nouns (Discord/X/GitHub) stay literal. Remaining deep-view strings are a tracked follow-up.

UI / perf

  • Social link — X pointed at an x.com/intent/follow popup; now the canonical https://x.com/tinyhumansai profile.
  • MoonPay slow load — preconnect the MoonPay buy/sell origins (dns-prefetch sandbox) from the document head so the hosted redirect + embedded off-ramp widget skip cold DNS/TLS setup.

Testing

  • pnpm --filter @tinyplace/website lint / build — green
  • New unit tests: activity-describe.test.ts (ledger.* normalization), SDK encryption-discovery.test.ts (resolveEncryptionAddress throw)
  • SDK rebuilt (discovery.ts change) before website typecheck
  • docs: finalize integration phase tracker + ignore tsc artifact #8 root cause confirmed against live GET https://staging-api.tiny.place/payments/supported (Solana USDC mint = 4zMMC9…DncDU)
  • Pre-existing local SubtleCrypto Ed25519 test failures (auth-payment/session-wallet/OnboardWizard) are a local-Node env issue, unrelated to this diff; CI on Node 22 passes.

Note

Contains an sdk/typescript/ change (discovery.ts) — merging to main will trigger the TS SDK publish workflow.

Follow-up

Summary by CodeRabbit

  • Bug Fixes

    • Encrypted messaging key resolution now validates advertised encryption keys and returns a clearer “not enabled” error when needed.
    • Inbox now waits for owner resolution and treats authorization-related failures consistently.
    • Job creation form now validates inputs before submit.
  • New Features

    • Activity feed now uses consistent icons and human-readable descriptions across the site.
    • Direct messages accept multiple recipient input formats (handles, raw keys, and addresses).
  • Improvements

    • Budget entry is now numeric with stronger validation and inline errors.
    • Sidebar and settings UI are now translated; external social link updated.

oxoxDev added 6 commits June 19, 2026 00:10
Activity rows showed only the actor for any kind the describe() switch
didn't list — notably the backend's `ledger.<TYPE>` fallback kinds and the
canonical group.fee / arbitration.fee / fee / event.refund kinds — because
they hit the bare-actor default. Extract one shared formatter used by both
the Activity list and the header marquee: normalize `ledger.<TYPE>` to its
canonical kind, add the missing cases, and humanize unknown kinds so the
feed always says what happened, not just who.
The X social link used an x.com/intent/follow popup URL; point it at the
canonical https://x.com/tinyhumansai profile instead.
Posting a bounty escrows real funds, but the budget was a free-text input
sent unvalidated, so a non-numeric or empty amount produced a confusing
escrow failure. Make the budget a numeric input and validate title +
positive amount (and a present wallet) before mutating, surfacing the
reason inline; send a normalized amount string.
The MoonPay hosted redirect and the embedded off-ramp sell widget opened a
cold DNS/TLS/TCP connection only when reached, adding round-trip latency
before any bytes loaded. Preconnect the buy/sell origins (and dns-prefetch
the sandbox hosts) from the document head so the connection is warm.
Searching for a peer by messaging key was non-deterministic: a base64 key
whose characters were base58-safe was misclassified as a cryptoId and 404'd
on directory.getAgent. Classify input unambiguously — handle, then base64
raw key, then base58 cryptoId. And stop resolveEncryptionAddress from
silently falling back to the wallet identity key (which has no Signal key
bundle, so the send 404s later); throw an actionable error at resolve time.
The inbox fell back to the wallet cryptoId as the directory-as actor when no
@handle had resolved yet, so the backend rejected it (401/403/404) and the
UI showed "failed to load inbox". Use the agent-auth path when there's no
handle, hold the query until owned-identities settles, and treat 401/403/404
alike as the actionable connect-a-handle state.
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

@oxoxDev must be a member of the Vezures team on Vercel to deploy.
- Click here to add @oxoxDev to the team.
- If you initiated this build, request access.

Learn more about collaboration on Vercel and other options here.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR tightens resolveEncryptionAddress to throw instead of falling back to card.publicKey, translates missing-bundle 404 errors to clearer messages, routes raw base64 Ed25519 keys in useDirectMessages around directory lookup, extracts a shared activity-describe module replacing duplicates in two components, gates the useInbox query until owner resolves, expands inbox auth-error detection, adds PostJob form validation, configures SOLANA_USDC_MINT by cluster environment, and adds i18n support to sidebar navigation and settings UI.

Changes

Encryption key strictness and error handling

Layer / File(s) Summary
resolveEncryptionAddress strict validation and tests
sdk/typescript/src/messaging/discovery.ts, sdk/typescript/tests/encryption-discovery.test.ts, website/src/common/peer-resolution.test.ts
resolveEncryptionAddress now throws when encryptionPublicKey metadata is absent or empty instead of falling back to card.publicKey; tests cover the success path, missing metadata, and empty-string metadata via a shared makeCard helper; resolveDirectoryPeer tests are updated to expect the new error behavior and fallback to publicKey.
Bundle fetch 404 error translation
sdk/typescript/src/messaging/encryption.ts
fetchBundleForBootstrap wraps getBundle calls and catches TinyPlaceError with 404 status, translating it to a generic Error with the same "hasn't enabled encrypted messaging" message as resolveEncryptionAddress, providing consistent error messaging across the encryption flow.
useDirectMessages base64 key classification
website/src/hooks/use-direct-messages.ts
BASE64_ENCRYPTION_KEY_PATTERN is introduced and checked before the base58 pattern in resolveAndRecordPeer, so raw Ed25519 base64 keys bypass directory lookup and are stored directly as recognized recipient keys, preventing misclassification as directory-resolvable cryptoIds.

Shared activity-description module extraction

Layer / File(s) Summary
activity-describe module and tests
website/src/common/activity-describe.ts, website/src/common/activity-describe.test.ts
New module exports activityIcon and describeActivity with ledger.<TYPE> normalization, kind-to-emoji mapping, and a humanizeKind fallback for unknown kinds; tests cover canonical kinds, ledger normalization, and unknown-kind handling.
Component migration to shared helpers
website/src/components/ActivityMarquee.tsx, website/src/components/explore/Activity.tsx
ActivityMarquee and Activity remove their local KIND_ICONS, iconFor, shortName, amountLabel, and describe helpers and import activityIcon/describeActivity from the shared module instead, simplifying rendering logic.

Inbox query gating and error handling

Layer / File(s) Summary
useInbox enabled option
website/src/hooks/use-inbox.ts
useInbox gains an options?: { enabled?: boolean } parameter wired into React Query's enabled flag, defaulting to true, allowing callers to defer the query until prerequisites are met.
Inbox query gating, error handling, and button disable
website/src/components/explore/Inbox.tsx
Inbox gates useInbox with enabled: !ownedIdentities.isLoading, derives owner only from inboxIdentity?.username, expands auth-error detection to 401/403/404, and bases all button disabled conditions on isInboxActorResolving instead of !owner.

Form validation, layout hints, and cluster-based configuration

Layer / File(s) Summary
PostJob client-side validation
website/src/components/explore/jobs/Jobs.tsx
submit validates trimmed title, positive finite budget amount, and agentId presence before calling create.mutate; the budget input becomes type="number" with decimal constraints; formError is rendered inline in red on validation failure.
Root layout MoonPay hints
website/app/layout.tsx
Root layout <head> adds preconnect/dns-prefetch link tags for MoonPay production (buy.moonpay.com, sell.moonpay.com) and sandbox origins to prime connectivity.
Solana cluster detection and USDC mint configuration
website/src/common/solana-rpc.ts, website/.env, website/src/common/delegated-payment.ts, website/src/hooks/use-wallet-balances.ts
New solanaCluster() helper resolves the active Solana cluster; publicUsdcMint() prioritizes NEXT_PUBLIC_SOLANA_USDC_MINT when set, falls back to a devnet-specific mint, and then to SDK defaults; wallet balances use publicUsdcMint() instead of a hardcoded constant.

Sidebar and settings internationalization

Layer / File(s) Summary
Sidebar navigation link translation
website/src/components/layout/Sidebar.tsx
Sidebar initializes useTranslation and introduces a NavKey type for nav section labels; section links render translated text via t(\nav.${section.key}`); external links accept optional labelKeyand translate viat(labelKey)with fallback to literal label; UI strings (Feedback, Settings, Need an Agent, Try OpenHuman, menu aria-labels) are translated viat("nav.*")`.
Settings theme and configuration internationalization
website/src/components/explore/Settings.tsx
Settings component uses useTranslation to render settings title/description and theme labels as translated strings; ThemeOption type is extended with nameKey field; each theme option displays its translated name via t(\settings.themes.${nameKey}`, fallback label)`.
Navigation and settings translation keys
website/src/assets/locales/en/translations.json, website/src/assets/locales/es/translations.json
English and Spanish translation files are extended with nav (home, feed, explore, etc., plus menu/feedback/settings UI) and settings (title, description, language, theme, custom, and themes sub-group with dark/light/green/darkGreen/darkBlue/mint/violet names) key groups.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tinyhumansai/tiny.place#86: Contains resolveDirectoryPeer flow and tests that previously expected a publicKey fallback from resolveEncryptionAddress, which this PR makes strict and now translates into clearer error messages.

Poem

🐇 Hop hop, no more fallback keys!
The encryption checks now strictly see —
No empty string shall slip on through,
Activity icons, shared and true.
Inbox waits 'til owners come to call,
A tidy warren, serving all! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the multi-part bug fix batch addressing messaging, payments, activity, marketplace, i18n, and UI concerns—directly matching the PR's actual scope and objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c6b1c7ae4

ℹ️ 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".

? advertised
: card.publicKey;
if (!address) {
if (typeof advertised !== "string" || advertised.length === 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the publicKey fallback for CLI messaging

When the peer is an SDK/CLI agent that has run tinyplace init/client.enableEncryption() but has not called the website-only publishEncryptionKey, its Signal bundle is still published at signer.publicKeyBase64 while its directory card only exposes publicKey. Rejecting such cards here makes website handle/cryptoId DM resolution and group key handoff fail before trying a bundle that exists; either keep card.publicKey as the fallback for these single-key agents or make SDK/CLI onboarding advertise metadata.encryptionPublicKey too.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in f59c831. resolveEncryptionAddress now falls back to card.publicKey when no encryptionPublicKey metadata is advertised, so SDK/CLI agents (bundle published under signer.publicKeyBase64 === publicKey, metadata never set) stay addressable for DMs and group key handoff. To preserve the original guard against website agents that never enabled encryption, the actionable "hasn't enabled encrypted messaging" error moved to send time: encryptEnvelope maps the bootstrap keys.getBundle 404 to it. So a real bundle is reachable, and a missing one still fails loudly instead of leaking a raw 404. Throw at resolve only when a card has neither key.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
website/src/components/explore/Activity.tsx (1)

22-39: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit return type annotation to relativeTime.

As per coding guidelines, all functions in TypeScript files must have explicit return types.

📝 Proposed fix
-function relativeTime(timestamp: string): string {
+function relativeTime(timestamp: string): string {

Wait, I see the return type is already there : string. Let me check the actual code again...

Looking at line 22, I see: function relativeTime(timestamp: string): string {

This already has the explicit return type. So this is actually compliant. My mistake.

🤖 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 `@website/src/components/explore/Activity.tsx` around lines 22 - 39, No fix is
needed. The relativeTime function already has the explicit return type
annotation `: string` declared on the function signature, which is compliant
with the TypeScript coding guidelines. The code is correct as-is.

Source: Coding guidelines

website/src/components/explore/Inbox.tsx (1)

93-112: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the auth-state copy to mention handles.

This branch now intentionally covers 401/403/404 cases where the wallet may be connected but no usable handle exists. The current text only says “Connect your wallet,” which can leave connected users without the actionable next step.

Proposed copy tweak
-					Connect your wallet to view your inbox
+					Connect your wallet and claim or select a handle to view your inbox
🤖 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 `@website/src/components/explore/Inbox.tsx` around lines 93 - 112, The copy
text "Connect your wallet to view your inbox" in the isAuthError conditional
block needs to be updated to account for cases where a wallet is connected but
no valid handle exists. Update the text content in the paragraph element (the
one with className containing "text-sm") to mention both connecting a wallet AND
owning or creating a handle as necessary steps, making it clear to users that
both are required to view the inbox.
🧹 Nitpick comments (4)
website/src/components/explore/jobs/Jobs.tsx (1)

205-240: ⚡ Quick win

Extract submit validation into a helper to satisfy the 20-line function limit.

submit now exceeds the repository’s max-function-length rule for TS/JS files. Please split validation/parsing into a small helper (e.g., return { parsedAmount, trimmedTitle } | error) and keep submit focused on orchestration.

Refactor sketch
+type PostJobValidationResult =
+  | { ok: true; trimmedTitle: string; parsedAmount: number }
+  | { ok: false; message: string };
+
+const validatePostJobInput = ({
+  title,
+  amount,
+  agentId,
+}: {
+  title: string;
+  amount: string;
+  agentId: string | null;
+}): PostJobValidationResult => {
+  const trimmedTitle = title.trim();
+  if (!trimmedTitle) return { ok: false, message: "Enter a title for the bounty." };
+  const parsedAmount = Number(amount.trim());
+  if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) {
+    return { ok: false, message: "Enter a budget greater than 0." };
+  }
+  if (!agentId) return { ok: false, message: "Connect your wallet to post a bounty." };
+  return { ok: true, trimmedTitle, parsedAmount };
+};
🤖 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 `@website/src/components/explore/jobs/Jobs.tsx` around lines 205 - 240, The
submit function exceeds the repository's max-function-length rule because it
contains multiple validation checks. Extract the validation and parsing logic
into a separate helper function that validates the trimmed title, parses the
amount, and checks the agentId, returning either an object with the parsed
values (parsedAmount, trimmedTitle) or an error message. Then modify the submit
function to call this helper and only proceed with the create.mutate call if
validation succeeds, keeping submit focused purely on orchestration and mutation
execution.

Source: Coding guidelines

website/src/common/activity-describe.ts (2)

82-127: ⚡ Quick win

Enhance JSDoc with parameter and return value documentation.

The exported describeActivity function has a JSDoc comment but is missing @param and @returns tags as per TypeScript documentation best practices.

📝 Enhanced JSDoc
 /**
  * A human-readable description of an activity event — the action performed, not
  * just who performed it. Unknown kinds humanize their name instead of collapsing
  * to a bare actor, so the feed always says what happened.
+ * 
+ * `@param` event - The activity event to describe
+ * `@returns` A human-readable sentence describing the activity
  */
 export function describeActivity(event: ActivityEvent): string {
🤖 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 `@website/src/common/activity-describe.ts` around lines 82 - 127, The JSDoc
comment for the describeActivity function is missing TypeScript documentation
best practices. Add a `@param` tag to document the event parameter of type
ActivityEvent and add a `@returns` tag to document that the function returns a
string. Update the existing JSDoc block that currently only contains a
description to include these tags documenting the function's parameters and
return type.

Source: Coding guidelines


54-57: ⚡ Quick win

Add JSDoc comment for exported function.

As per coding guidelines, all public functions and exported modules must include JSDoc comments. While there's a brief inline comment above, a proper JSDoc block would improve API documentation.

📝 Proposed JSDoc addition
-/** An emoji for an activity kind (resolves `ledger.<TYPE>` fallbacks too). */
+/**
+ * Returns an emoji icon for the given activity kind.
+ * 
+ * Normalizes `ledger.<TYPE>` fallback kinds to their canonical activity kind
+ * before looking up the icon. Returns a bullet point (•) for unknown kinds.
+ * 
+ * `@param` kind - The activity kind (e.g., "marketplace.purchase", "ledger.SALE")
+ * `@returns` An emoji string representing the activity type
+ */
 export function activityIcon(kind: string): string {
🤖 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 `@website/src/common/activity-describe.ts` around lines 54 - 57, The exported
function activityIcon needs a proper JSDoc comment block instead of the existing
inline comment to comply with coding guidelines. Replace the current comment
with a JSDoc block that includes a description of what the function does, a
`@param` tag documenting the kind parameter and its purpose, and a `@returns` tag
describing the returned emoji string. Preserve the existing information about
emoji resolution and ledger fallback behavior in the description.

Source: Coding guidelines

website/src/hooks/use-inbox.ts (1)

35-39: ⚡ Quick win

Document the new public enabled option.

useInbox is an exported hook with a changed public signature, but the new options.enabled contract is only described inside the implementation. Add JSDoc so callers know it is for deferring queries until the inbox owner is resolved.

Proposed documentation
+/**
+ * Loads inbox items for the resolved owner.
+ *
+ * Pass `options.enabled: false` to defer the query while the inbox owner is
+ * still resolving; when omitted, the query runs immediately.
+ */
 export function useInbox(
 	parameters?: InboxQueryParams,
 	owner?: string,
 	options?: { enabled?: boolean }

As per coding guidelines, “Always include JSDoc comments for public functions and exported modules.”

🤖 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 `@website/src/hooks/use-inbox.ts` around lines 35 - 39, The exported useInbox
function is missing JSDoc documentation for its public signature, particularly
the new options.enabled property. Add JSDoc comments above the useInbox function
that document all parameters including the InboxQueryParams, owner string, and
options object, with specific documentation for the enabled property explaining
it defers query execution until the inbox owner is resolved. Also document the
return type UseQueryResult<InboxListResult>.

Source: Coding guidelines

🤖 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 `@website/src/components/explore/Inbox.tsx`:
- Around line 76-82: The inbox mutations for actions like read, archive, and
delete are not gated by the same ownedIdentities.isLoading check that controls
the query, allowing them to execute with an undefined owner while the handle
resolves. Add the enabled condition with !ownedIdentities.isLoading to all
mutation calls in the Inbox component (including those at the line ranges
mentioned in the comment) to prevent mutations from firing until the
owned-identities lookup settles and owner is properly defined.

In `@website/src/components/explore/jobs/Jobs.tsx`:
- Around line 206-223: The button's disabled state logic does not match the
validation rules applied in the submit handler, causing the button to appear
enabled for invalid inputs like whitespace-only titles or non-positive amounts
until the user clicks and the submit validation fails. Update the disabled
condition on the submit button to use the same normalized validation checks as
the submit function: validate the trimmed title is not empty, validate the
parsed amount is finite and greater than 0, and check that agentId exists. This
ensures the button is only enabled when all validation conditions in the submit
handler would pass.

In `@website/src/hooks/use-direct-messages.ts`:
- Around line 31-36: The regex pattern BASE64_ENCRYPTION_KEY_PATTERN only
matches padded base64 keys ending with `=`, but unpadded 43-character base64
keys (without the `=`) are also valid messaging keys that should be caught
before the cryptoId pattern check. Update the BASE64_ENCRYPTION_KEY_PATTERN
regex to make the trailing `=` optional by using a quantifier, so it matches
both padded keys (43 chars + `=`) and unpadded keys (43 chars without `=`),
preventing valid messaging keys from being misclassified as cryptoIds.

---

Outside diff comments:
In `@website/src/components/explore/Activity.tsx`:
- Around line 22-39: No fix is needed. The relativeTime function already has the
explicit return type annotation `: string` declared on the function signature,
which is compliant with the TypeScript coding guidelines. The code is correct
as-is.

In `@website/src/components/explore/Inbox.tsx`:
- Around line 93-112: The copy text "Connect your wallet to view your inbox" in
the isAuthError conditional block needs to be updated to account for cases where
a wallet is connected but no valid handle exists. Update the text content in the
paragraph element (the one with className containing "text-sm") to mention both
connecting a wallet AND owning or creating a handle as necessary steps, making
it clear to users that both are required to view the inbox.

---

Nitpick comments:
In `@website/src/common/activity-describe.ts`:
- Around line 82-127: The JSDoc comment for the describeActivity function is
missing TypeScript documentation best practices. Add a `@param` tag to document
the event parameter of type ActivityEvent and add a `@returns` tag to document
that the function returns a string. Update the existing JSDoc block that
currently only contains a description to include these tags documenting the
function's parameters and return type.
- Around line 54-57: The exported function activityIcon needs a proper JSDoc
comment block instead of the existing inline comment to comply with coding
guidelines. Replace the current comment with a JSDoc block that includes a
description of what the function does, a `@param` tag documenting the kind
parameter and its purpose, and a `@returns` tag describing the returned emoji
string. Preserve the existing information about emoji resolution and ledger
fallback behavior in the description.

In `@website/src/components/explore/jobs/Jobs.tsx`:
- Around line 205-240: The submit function exceeds the repository's
max-function-length rule because it contains multiple validation checks. Extract
the validation and parsing logic into a separate helper function that validates
the trimmed title, parses the amount, and checks the agentId, returning either
an object with the parsed values (parsedAmount, trimmedTitle) or an error
message. Then modify the submit function to call this helper and only proceed
with the create.mutate call if validation succeeds, keeping submit focused
purely on orchestration and mutation execution.

In `@website/src/hooks/use-inbox.ts`:
- Around line 35-39: The exported useInbox function is missing JSDoc
documentation for its public signature, particularly the new options.enabled
property. Add JSDoc comments above the useInbox function that document all
parameters including the InboxQueryParams, owner string, and options object,
with specific documentation for the enabled property explaining it defers query
execution until the inbox owner is resolved. Also document the return type
UseQueryResult<InboxListResult>.
🪄 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: 4c4c59ba-68b7-49f2-9181-1914f349ffaa

📥 Commits

Reviewing files that changed from the base of the PR and between a642037 and 8c6b1c7.

📒 Files selected for processing (12)
  • sdk/typescript/src/messaging/discovery.ts
  • sdk/typescript/tests/encryption-discovery.test.ts
  • website/app/layout.tsx
  • website/src/common/activity-describe.test.ts
  • website/src/common/activity-describe.ts
  • website/src/components/ActivityMarquee.tsx
  • website/src/components/explore/Activity.tsx
  • website/src/components/explore/Inbox.tsx
  • website/src/components/explore/jobs/Jobs.tsx
  • website/src/components/layout/Sidebar.tsx
  • website/src/hooks/use-direct-messages.ts
  • website/src/hooks/use-inbox.ts

Comment thread website/src/components/explore/Inbox.tsx
Comment on lines +206 to 223
// The budget is escrowed on post, so a malformed amount must never reach
// the mutation — validate it client-side and surface the reason.
const trimmedTitle = title.trim();
if (!trimmedTitle) {
setFormError("Enter a title for the bounty.");
return;
}
const parsedAmount = Number(amount.trim());
if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) {
setFormError("Enter a budget greater than 0.");
return;
}
if (!agentId) {
setFormError("Connect your wallet to post a bounty.");
return;
}
setFormError(null);
create.mutate(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align button disabled logic with the same normalized validation rules used in submit.

The button can be enabled for whitespace titles or non-positive numeric strings, then fails only after click. Use trimmed title + parsed positive amount for disabled so the UI state matches submit behavior.

Suggested adjustment
+const trimmedTitle = title.trim();
+const parsedAmount = Number(amount.trim());
+const isAmountValid = Number.isFinite(parsedAmount) && parsedAmount > 0;
+
 <button
   className={primaryButtonClass()}
-  disabled={create.isPending || !title || !amount}
+  disabled={create.isPending || !trimmedTitle || !isAmountValid}
   type="button"
   onClick={submit}
 >

Also applies to: 321-325

🤖 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 `@website/src/components/explore/jobs/Jobs.tsx` around lines 206 - 223, The
button's disabled state logic does not match the validation rules applied in the
submit handler, causing the button to appear enabled for invalid inputs like
whitespace-only titles or non-positive amounts until the user clicks and the
submit validation fails. Update the disabled condition on the submit button to
use the same normalized validation checks as the submit function: validate the
trimmed title is not empty, validate the parsed amount is finite and greater
than 0, and check that agentId exists. This ensures the button is only enabled
when all validation conditions in the submit handler would pass.

Comment thread website/src/hooks/use-direct-messages.ts
oxoxDev added 2 commits June 19, 2026 01:08
Domain registration built the payer-signed x402 TransferChecked against the
SDK's hardcoded mainnet USDC mint while the app runs on devnet, so the on-chain
payment reverted (the mainnet mint account and its derived ATAs don't exist on
devnet). The staging backend advertises the devnet USDC mint
(4zMMC9...DncDU) via GET /payments/supported.

Set NEXT_PUBLIC_SOLANA_USDC_MINT to that mint and make publicUsdcMint() fall
back to the devnet mint by active cluster when unset, so a devnet build never
silently settles against the mainnet mint. Dedupe use-wallet-balances onto the
same resolver.
The language toggle changed only a handful of feed/home/poker strings because
the high-traffic chrome — primary nav, sidebar links, the Need-an-Agent CTA and
the Settings page itself — rendered hardcoded English and never went through
t(). Switching to Spanish appeared to do nothing.

Add nav + settings namespaces to the en/es resources and route those surfaces
through t() (section labels, Docs/Feedback/Settings links, menu aria-labels,
the Settings title/description/language/theme headings and theme names). Brand
proper nouns (Discord/X/GitHub) stay literal.
@oxoxDev oxoxDev changed the title fix: staging bug sweep batch 2 — messaging, activity, bounty, social, moonpay fix: staging bug sweep batch 2 — messaging, payments, activity, bounty, i18n, social, moonpay Jun 18, 2026
The peer-resolution test still asserted the old wallet-key fallback that the DM
fix (abc0bc2) deliberately removed. Update it to assert resolveDirectoryPeer now
surfaces an actionable error when the card advertises no encryption key, instead
of returning the wallet key that would 404 at keys.getBundle.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
website/src/components/explore/Settings.tsx (1)

118-123: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add JSDoc for exported Settings component.

Line 118 exports a public component without JSDoc.

✍️ Proposed fix
+/**
+ * Renders language and theme preferences for the current user interface.
+ */
 export const Settings = ({ isDark }: SettingsProperties): FunctionComponent => {
 	const flavor = useAppStore((state) => state.flavor);
 	const setFlavor = useAppStore((state) => state.setFlavor);

As per coding guidelines, "Always include JSDoc comments for public functions and exported modules."

🤖 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 `@website/src/components/explore/Settings.tsx` around lines 118 - 123, The
exported Settings component is missing JSDoc documentation. Add a JSDoc comment
block above the Settings component export that documents the component's
purpose, its props (including the isDark property from SettingsProperties), and
its return type as FunctionComponent. The JSDoc should provide a clear
description of what the Settings component does and how it's used.

Source: Coding guidelines

website/src/components/layout/Sidebar.tsx (1)

207-213: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add JSDoc for exported Sidebar component.

Line 207 exports a public component without JSDoc.

✍️ Proposed fix
+/**
+ * Renders the primary application sidebar for desktop and mobile navigation.
+ */
 export const Sidebar = ({
 	activeSection,
 	isDark,
 	sections,
 }: SidebarProps): FunctionComponent => {

As per coding guidelines, "Always include JSDoc comments for public functions and exported modules."

🤖 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 `@website/src/components/layout/Sidebar.tsx` around lines 207 - 213, The
exported Sidebar component at line 207 is missing JSDoc documentation. Add a
JSDoc comment block above the Sidebar component export that documents the
component's purpose, its props (activeSection, isDark, sections), the
SidebarProps type that defines these parameters, and the return type
(FunctionComponent). Ensure the JSDoc follows standard format with description,
`@param` entries for each prop, and `@returns` to document the return type.

Source: Coding guidelines

🤖 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 `@website/.env`:
- Line 8: The NEXT_PUBLIC_SOLANA_USDC_MINT environment variable in the .env file
currently has its value wrapped in double quotes, which triggers a
QuoteCharacter warning in dotenv-linter. Remove the double quotes around the
mint value "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU" so that the variable
assignment becomes
NEXT_PUBLIC_SOLANA_USDC_MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
without quotes, which will resolve the linter warning.

---

Outside diff comments:
In `@website/src/components/explore/Settings.tsx`:
- Around line 118-123: The exported Settings component is missing JSDoc
documentation. Add a JSDoc comment block above the Settings component export
that documents the component's purpose, its props (including the isDark property
from SettingsProperties), and its return type as FunctionComponent. The JSDoc
should provide a clear description of what the Settings component does and how
it's used.

In `@website/src/components/layout/Sidebar.tsx`:
- Around line 207-213: The exported Sidebar component at line 207 is missing
JSDoc documentation. Add a JSDoc comment block above the Sidebar component
export that documents the component's purpose, its props (activeSection, isDark,
sections), the SidebarProps type that defines these parameters, and the return
type (FunctionComponent). Ensure the JSDoc follows standard format with
description, `@param` entries for each prop, and `@returns` to document the return
type.
🪄 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: 72ad56f3-1322-4936-ad04-720e4229109f

📥 Commits

Reviewing files that changed from the base of the PR and between 8c6b1c7 and 99fd3da.

📒 Files selected for processing (8)
  • website/.env
  • website/src/assets/locales/en/translations.json
  • website/src/assets/locales/es/translations.json
  • website/src/common/delegated-payment.ts
  • website/src/common/solana-rpc.ts
  • website/src/components/explore/Settings.tsx
  • website/src/components/layout/Sidebar.tsx
  • website/src/hooks/use-wallet-balances.ts
✅ Files skipped from review due to trivial changes (2)
  • website/src/common/solana-rpc.ts
  • website/src/assets/locales/en/translations.json

Comment thread website/.env Outdated
oxoxDev added 3 commits June 19, 2026 01:48
Mark-read/archive/delete fired with owner undefined during the
owned-identities lookup window, acting as the agent-auth actor before a
real @handle resolved. Gate the buttons on the same isInboxActorResolving
flag the query already uses so they wait for the actor to settle.
Button enabled for whitespace titles and non-positive budgets, then
failed only on click. Derive canSubmit from the same trimmed-title +
positive-amount checks submit() runs.
Clears the dotenv-linter QuoteCharacter warning; the mint is a plain
base58 string needing no quoting.
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

oxoxDev added 2 commits June 19, 2026 01:58
resolveEncryptionAddress threw whenever a card lacked the website-only
encryptionPublicKey metadata. SDK/CLI agents publish their Signal bundle
under their wallet key (signer.publicKeyBase64 === card.publicKey) and
never set that metadata, so the throw made website->CLI DMs and group key
handoff to those members fail even though a bundle existed.

Restore the publicKey fallback so those peers stay addressable, and move
the actionable 'hasn't enabled encrypted messaging' signal to send time:
encryptEnvelope now maps the bootstrap keys.getBundle 404 to that error,
so agents that genuinely never enabled encryption still fail loudly
instead of leaking a raw 404. Throw at resolve only when a card has
neither an advertised key nor a publicKey.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@sdk/typescript/src/messaging/encryption.ts`:
- Around line 111-128: Add test coverage for the new `fetchBundleForBootstrap`
method in encryption.ts to verify its error translation logic. In
encrypted-messaging.test.ts, create two test cases: the first should verify that
when `keys.getBundle` receives a 404 error from a recipient without a published
bundle, it throws the user-friendly error message "Recipient hasn't enabled
encrypted messaging yet", and the second should verify that non-404 errors from
`keys.getBundle` are rethrown unchanged without translation. Leverage the
existing mock relay infrastructure that already supports returning 404 responses
(as shown at lines 67-68) to simulate the missing bundle scenario in the first
test case.
🪄 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: 1a4664e7-afc3-4bff-8a70-25048d50e30d

📥 Commits

Reviewing files that changed from the base of the PR and between ec024c3 and 6cc0217.

📒 Files selected for processing (7)
  • sdk/typescript/src/messaging/discovery.ts
  • sdk/typescript/src/messaging/encryption.ts
  • sdk/typescript/tests/encryption-discovery.test.ts
  • website/.env
  • website/src/common/peer-resolution.test.ts
  • website/src/components/explore/Inbox.tsx
  • website/src/components/explore/jobs/Jobs.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • sdk/typescript/src/messaging/discovery.ts
  • website/.env
  • website/src/components/explore/Inbox.tsx
  • website/src/components/explore/jobs/Jobs.tsx

Comment on lines +111 to +128
/**
* Fetch a recipient's key bundle to bootstrap the first X3DH session. A 404
* here means the address has no published Signal bundle — the agent never
* enabled encrypted messaging (their directory card may advertise a key, or we
* may have optimistically fallen back to their wallet publicKey, but no bundle
* was ever uploaded). Translate it into an actionable error instead of leaking
* the raw 404 to the send call site.
*/
private async fetchBundleForBootstrap(to: string): Promise<KeyBundle> {
try {
return await this.keys.getBundle(to);
} catch (error) {
if (error instanceof TinyPlaceError && error.status === 404) {
throw new Error("Recipient hasn't enabled encrypted messaging yet");
}
throw error;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Candidate encryption-related tests:"
fd -i 'encryption*test*.ts' sdk/typescript/tests

echo
echo "Search for assertions around missing-bundle messaging and bundle fetch failures:"
rg -n -C3 "hasn't enabled encrypted messaging|getBundle\\(|TinyPlaceError|status\\s*===\\s*404|encryptEnvelope\\(" sdk/typescript/tests

Repository: tinyhumansai/tiny.place

Length of output: 15487


🏁 Script executed:

# Check if there are any test files that import from encryption.ts
rg -l "from.*encryption|import.*encryption" sdk/typescript/tests --type ts

# Look at the encryption-discovery test to see its scope
cat -n sdk/typescript/tests/encryption-discovery.test.ts

# Search for any mocks or tests of fetchBundleForBootstrap or encryptEnvelope with error handling
rg -n "fetchBundleForBootstrap|mock.*getBundle|404|encryptEnvelope" sdk/typescript/tests --type ts -C2

Repository: tinyhumansai/tiny.place

Length of output: 7458


🏁 Script executed:

cat -n sdk/typescript/tests/encrypted-messaging.test.ts

Repository: tinyhumansai/tiny.place

Length of output: 7244


Add tests for the new bundle-fetch error translation in fetchBundleForBootstrap.

The new fetchBundleForBootstrap helper at lines 111–128 translates 404 errors from keys.getBundle into a user-friendly message. This error path is not covered by existing tests. While encrypted-messaging.test.ts provides mock relay infrastructure capable of returning 404 for missing bundles (line 67–68), no test case exercises sending a message to a recipient without a published bundle, which is the exact scenario fetchBundleForBootstrap handles.

Add at least two test cases:

  1. Sending to a recipient without a published bundle returns "Recipient hasn't enabled encrypted messaging yet"
  2. Non-404 errors from keys.getBundle are rethrown unchanged
🤖 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 `@sdk/typescript/src/messaging/encryption.ts` around lines 111 - 128, Add test
coverage for the new `fetchBundleForBootstrap` method in encryption.ts to verify
its error translation logic. In encrypted-messaging.test.ts, create two test
cases: the first should verify that when `keys.getBundle` receives a 404 error
from a recipient without a published bundle, it throws the user-friendly error
message "Recipient hasn't enabled encrypted messaging yet", and the second
should verify that non-404 errors from `keys.getBundle` are rethrown unchanged
without translation. Leverage the existing mock relay infrastructure that
already supports returning 404 responses (as shown at lines 67-68) to simulate
the missing bundle scenario in the first test case.

Source: Coding guidelines

@senamakel senamakel closed this Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants