-
Notifications
You must be signed in to change notification settings - Fork 20
Enable proposal space customization #1296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable proposal space customization #1296
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughConsolidates public “Spaces” into a unified SpacePageData model with centralized type guards and initial config sources. Refactors Profile, Token, and Proposal spaces to server-load data and client-augment editability and URLs. Updates TabBar, PublicSpace, stores, and APIs to use new space types, registration flows, and proposal/token ownership resolution. Removes legacy token/proposal wrappers. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Browser
participant Page as Space Page (Server)
participant Utils as *load*Space*Data utils
participant Provider as *Provider (Token/Proposal)
participant Client as Space Component (Client)
participant Public as PublicSpace
participant Store as Space Store/API
Browser->>Page: Request /spaces/{type}/...
Page->>Utils: load*Space*Data(params[, tabName])
Utils->>Store: Query registration (spaceId, identityPublicKey, fid, network)
Utils->>External: Fetch token/proposal/user data
Utils-->>Page: Omit<SpacePageData, 'isEditable'|'spacePageUrl'>
Page->>Provider: Wrap with relevant Provider(s)
Page->>Client: Render Space with spacePageData + tabName
Client->>Client: Compute spacePageUrl, isEditable (client identities)
Client->>Public: Render with composed SpacePageData
Public->>Store: Register/resolve space (if needed)
Public->>Public: Determine tabs, defaultTab, currentTab
Public-->>Browser: Render UI
note over Client,Public: isEditable leverages identity keys, FID, wallets
sequenceDiagram
autonumber
participant Public as PublicSpace
participant TabBar as TabBar
participant Store as Space Store
participant API as /api/space/registry
Public->>Store: register*Space*(...initialConfig...)
alt Existing registration
Store->>API: GET /space/registry?proposalId|contract+network|fid
API-->>Store: { spaceId, identityPublicKey, spaceType }
Store-->>Public: spaceId (existing)
else New registration
Store->>API: POST unsigned registration (with spaceType, proposalId)
API-->>Store: created spaceId
Store-->>Public: spaceId (new)
end
Public->>TabBar: Render with { spaceData, isEditable }
TabBar->>TabBar: Compute tabList, permissions, token header
TabBar-->>Public: User tab interactions (rename/remove)
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
cant see the customize button :( |
|
@CodeRabbit full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
src/common/data/queries/farcaster.ts (1)
165-183: LGTM! Consider adding error handling for invalid FID conversion.The hook implementation follows established patterns and correctly handles the response structure. The explicit
Number()conversion is good practice.Consider adding validation for the FID conversion to handle edge cases where the conversion might fail:
queryFn: async () => { if (!address) return undefined; const res = await axios.get( "/api/search/users", { params: { q: address, limit: 1 } }, ); const users = res.data?.value?.users; if (Array.isArray(users) && users.length > 0) { - return Number(users[0].fid); + const fid = Number(users[0].fid); + return isNaN(fid) ? undefined : fid; } return undefined; },src/app/(spaces)/p/[proposalId]/utils.ts (1)
198-216: Solid implementation with good error handling.The function properly handles database queries, errors, and edge cases. The use of
noStore()is appropriate for dynamic server-side data fetching.Consider improving the type safety by defining a proper interface for the query result:
+interface SpaceRegistration { + spaceId: string; +} const { data, error } = await createSupabaseServerClient() .from("spaceRegistrations") .select("spaceId") .eq("proposalId", proposalId) .order("timestamp", { ascending: true }) .limit(1); if (error) { console.error("Error fetching proposal space id:", error); return null; } - return data && data.length > 0 ? (data[0] as any).spaceId : null; + return data && data.length > 0 ? (data[0] as SpaceRegistration).spaceId : null;src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx (1)
10-11: Remove the empty interface declaration.The interface
ProposalPrimarySpaceContentPropsdoesn't add any properties beyond what's inherited fromProposalPageSpaceProps. This empty interface declaration is unnecessary.-interface ProposalPrimarySpaceContentProps extends ProposalPageSpaceProps {} - -const ProposalPrimarySpaceContent: React.FC<ProposalPrimarySpaceContentProps> = ({ +const ProposalPrimarySpaceContent: React.FC<ProposalPageSpaceProps> = ({src/common/data/stores/app/space/spaceStore.ts (1)
1051-1056: Address the TODO: Re-enable analytics tracking.Analytics tracking for proposal space registration is commented out. This should be re-enabled to maintain consistent analytics across all space types.
Would you like me to help implement the analytics tracking for proposal spaces or create an issue to track this task?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/app/(spaces)/PublicSpace.tsx(13 hunks)src/app/(spaces)/p/[proposalId]/ProposalDefinedSpace.tsx(3 hunks)src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx(1 hunks)src/app/(spaces)/p/[proposalId]/[tabname]/page.tsx(1 hunks)src/app/(spaces)/p/[proposalId]/utils.ts(2 hunks)src/common/components/molecules/ClaimButtonWithModal.tsx(1 hunks)src/common/components/molecules/ClaimModal.tsx(1 hunks)src/common/components/organisms/TabBar.tsx(3 hunks)src/common/data/queries/farcaster.ts(1 hunks)src/common/data/stores/app/space/spaceStore.ts(6 hunks)src/common/utils/spaceEditability.ts(4 hunks)src/pages/api/space/registry/[spaceId]/index.ts(2 hunks)src/pages/api/space/registry/index.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (2)
src/common/components/organisms/TabBar.tsx (1)
Learnt from: willyogo
PR: Nounspace/nounspace.ts#1118
File: src/common/fidgets/FidgetWrapper.tsx:160-175
Timestamp: 2025-06-05T19:55:02.560Z
Learning: In the Nounspace.ts codebase, fidget edit mode icons in FidgetWrapper.tsx require an extremely high z-index (999999) to prevent being covered by the tab bar. Lower z-index values like 1050 break this fix, indicating other UI elements use very high z-index values.
src/common/data/stores/app/space/spaceStore.ts (1)
Learnt from: j-paterson
PR: Nounspace/nounspace.ts#1279
File: src/app/(spaces)/Space.tsx:415-421
Timestamp: 2025-07-12T00:07:46.876Z
Learning: In the Nounspace.ts codebase, the team prefers to use internal assets hosted in the /images/ directory rather than external URLs for better performance and to avoid external dependencies. When suggesting external asset improvements, they proactively add files internally to the public/images folder.
🧬 Code Graph Analysis (5)
src/app/(spaces)/p/[proposalId]/ProposalDefinedSpace.tsx (1)
src/common/data/queries/farcaster.ts (1)
useFidFromAddress(165-183)
src/pages/api/space/registry/[spaceId]/index.ts (1)
src/common/data/database/supabase/serverHelpers.ts (1)
loadOwnedItentitiesForSpaceByFid(51-65)
src/common/components/molecules/ClaimButtonWithModal.tsx (1)
src/common/providers/TokenProvider.tsx (1)
useToken(100-106)
src/app/(spaces)/p/[proposalId]/[tabname]/page.tsx (1)
src/app/(spaces)/p/[proposalId]/utils.ts (1)
loadProposalSpaceId(198-216)
src/pages/api/space/registry/index.ts (1)
src/common/data/database/supabase/clients/server.ts (1)
createSupabaseServerClient(7-7)
🔇 Additional comments (22)
src/common/components/molecules/ClaimModal.tsx (1)
28-40: Excellent refactoring for improved flexibility.The extraction of
titleanddescriptioninto constants with conditional logic makes the component more reusable and maintains consistency. This supports both token-specific and generic claim scenarios effectively.src/app/(spaces)/p/[proposalId]/ProposalDefinedSpace.tsx (3)
9-9: LGTM! Proper integration of Farcaster identity lookup.The import is correctly added to support dynamic owner FID resolution.
26-26: Good replacement of hardcoded value with dynamic lookup.The dynamic
ownerFidlookup using the proposal owner's address is a significant improvement over the hardcoded value.
48-51: PublicSpace already guards against undefinedspaceOwnerFidI’ve verified that:
spaceOwnerFidis declared as an optional prop and always checked for truthiness before use.resolvedPageTypefalls back on the passed‐in"proposal"value, so missingspaceOwnerFidwon’t change page routing.createEditabilityCheckeronly acts on a definedspaceOwnerFid(and similarly forspaceOwnerAddress) and never dereferences it without a guard.- The profile‐and‐loading render paths also conditionally check for
spaceOwnerFid.No runtime errors or unguarded accesses were found. No changes required here.
src/app/(spaces)/p/[proposalId]/[tabname]/page.tsx (4)
7-8: LGTM! Correct imports for the new proposal space architecture.The imports align with the refactored component structure and utility functions.
14-14: Good integration of space ID loading.The async loading of
spaceIdusing the new utility function is properly implemented.
17-21: Clean props structure with proper data flow.The props are well-structured with explicit property names and proper data passing to the child component.
28-28: Appropriate component replacement for new architecture.The switch to
ProposalPrimarySpaceContentaligns with the enhanced proposal space handling capabilities.src/app/(spaces)/p/[proposalId]/utils.ts (1)
2-3: LGTM! Proper imports for database operations.The imports are correctly added for Supabase client and Next.js cache control.
src/pages/api/space/registry/[spaceId]/index.ts (2)
140-140: LGTM! Database query enhancement for proposal space support.The addition of
fidandidentityPublicKeyfields to the SELECT query properly supports the new proposal space functionality by enabling identity resolution through multiple mechanisms.
159-165: Well-implemented fallback chain for identity resolution.The conditional logic correctly handles different registration types:
- Contract-based spaces (existing)
- FID-based spaces (new)
- Identity public key-based spaces (new)
The fallback chain is logical and ensures comprehensive identity resolution for proposal spaces.
src/common/components/molecules/ClaimButtonWithModal.tsx (3)
15-15: Good addition of optional tokenSymbol prop.The optional
tokenSymbolprop enhances component flexibility by allowing explicit token symbol specification, which aligns with the proposal space customization requirements.
23-30: Excellent error handling for missing TokenProvider.The try-catch approach elegantly handles cases where the component is used outside a
TokenProvidercontext, making it more robust for proposal pages where token context may not be available.
32-36: Well-structured symbol resolution priority.The symbol resolution logic follows a logical priority order:
- Explicit
tokenSymbolprop (highest priority)- Clanker data symbol
- Gecko data symbol
- Empty string fallback
This ensures consistent behavior across different page types and token data availability.
src/pages/api/space/registry/index.ts (2)
255-260: Proper query parameter extraction for proposal support.The addition of
proposalIdto the destructuring correctly supports the new proposal space lookup functionality.
297-324: Well-implemented proposal space lookup logic.The proposal lookup implementation is solid:
- Proper query parameter validation
- Correct database query structure with
order("timestamp", { ascending: true }).limit(1)to get the earliest registration- Consistent error handling and response format
- Appropriate HTTP status codes (200 for success, 404 for not found, 500 for errors)
This enables the proposal space registration and retrieval functionality as intended.
src/common/components/organisms/TabBar.tsx (2)
36-36: Appropriate prop addition for proposal space support.The addition of
spaceId?: string | nullto the TabBarProps interface properly supports the proposal space functionality by allowing the component to determine claim button visibility based on space ownership status.
271-276: Excellent conditional logic for claim button visibility.The enhanced conditional rendering logic correctly handles both token and proposal pages:
- Shows claim button on token pages (existing functionality)
- Shows claim button on proposal pages when
spaceIdis falsy (new functionality)- Maintains all existing conditions (not initializing, not logged in, not mobile)
This aligns perfectly with the PR objective of enabling claim buttons on proposal pages for unclaimed spaces.
src/common/utils/spaceEditability.ts (3)
22-22: Appropriate context extension for proposal page support.The addition of
isProposalPage?: booleanto the EditabilityContext type properly extends the context to support proposal page editability checks.
68-79: Excellent case-insensitive address comparison improvement.The updated address comparison logic using
toLowerCase()is a significant improvement that ensures reliable wallet address matching regardless of case variations. This enhances the robustness of ownership verification.
96-104: Well-implemented proposal page editability logic.The proposal page editability check correctly:
- Validates that a
spaceOwnerAddressexists- Performs case-insensitive comparison with user's wallet addresses
- Returns appropriate editability status
This enables proposers to customize their proposal spaces when they own the associated wallet address.
src/app/(spaces)/PublicSpace.tsx (1)
147-155: LGTM!The page type resolution logic correctly handles all cases including the new proposal type.
src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx (1)
37-39: Improve error handling to distinguish between expected and unexpected errors.The current implementation silently swallows all errors, which could hide important issues like network failures or server errors. Consider logging unexpected errors while still handling the expected "space not found" case gracefully.
- } catch { - // ignore - space might not exist yet + } catch (error) { + if (axios.isAxiosError(error) && error.response?.status === 404) { + // Expected: space doesn't exist yet + } else { + console.error('Failed to fetch proposal space:', error); + }src/common/data/stores/app/space/spaceStore.ts (1)
1099-1099: Fix the type definition instead of using type casting.The
proposalIdproperty should be properly typed in theSpaceInfointerface rather than usinganycasting.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/app/(spaces)/PublicSpace.tsx(13 hunks)src/app/(spaces)/p/[proposalId]/ProposalDefinedSpace.tsx(3 hunks)src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx(1 hunks)src/app/(spaces)/p/[proposalId]/[tabname]/page.tsx(1 hunks)src/app/(spaces)/p/[proposalId]/utils.ts(2 hunks)src/common/components/molecules/ClaimButtonWithModal.tsx(1 hunks)src/common/components/molecules/ClaimModal.tsx(1 hunks)src/common/components/organisms/TabBar.tsx(3 hunks)src/common/data/queries/farcaster.ts(1 hunks)src/common/data/stores/app/space/spaceStore.ts(6 hunks)src/common/utils/spaceEditability.ts(4 hunks)src/pages/api/space/registry/[spaceId]/index.ts(2 hunks)src/pages/api/space/registry/index.ts(2 hunks)
🧰 Additional context used
🧠 Learnings (3)
src/common/components/organisms/TabBar.tsx (1)
Learnt from: willyogo
PR: Nounspace/nounspace.ts#1118
File: src/common/fidgets/FidgetWrapper.tsx:160-175
Timestamp: 2025-06-05T19:55:02.560Z
Learning: In the Nounspace.ts codebase, fidget edit mode icons in FidgetWrapper.tsx require an extremely high z-index (999999) to prevent being covered by the tab bar. Lower z-index values like 1050 break this fix, indicating other UI elements use very high z-index values.
src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx (1)
Learnt from: j-paterson
PR: Nounspace/nounspace.ts#1279
File: src/app/(spaces)/Space.tsx:415-421
Timestamp: 2025-07-12T00:07:46.876Z
Learning: In the Nounspace.ts codebase, the team prefers to use internal assets hosted in the /images/ directory rather than external URLs for better performance and to avoid external dependencies. When suggesting external asset improvements, they proactively add files internally to the public/images folder.
src/common/data/stores/app/space/spaceStore.ts (1)
Learnt from: willyogo
PR: Nounspace/nounspace.ts#1118
File: src/common/fidgets/FidgetWrapper.tsx:160-175
Timestamp: 2025-06-05T19:55:02.560Z
Learning: In the Nounspace.ts codebase, reducing the z-index of fidget edit mode icons from 999999 to 1050 breaks the fix that prevents them from being covered by UI elements. The high z-index value of 999999 appears to be necessary despite seeming excessive.
🧬 Code Graph Analysis (6)
src/pages/api/space/registry/[spaceId]/index.ts (1)
src/common/data/database/supabase/serverHelpers.ts (1)
loadOwnedItentitiesForSpaceByFid(51-65)
src/app/(spaces)/p/[proposalId]/[tabname]/page.tsx (2)
src/app/(spaces)/p/[proposalId]/utils.ts (2)
loadProposalData(31-99)loadProposalSpaceId(198-216)src/common/providers/ProposalProvider.tsx (1)
ProposalProvider(29-48)
src/pages/api/space/registry/index.ts (1)
src/common/data/database/supabase/clients/server.ts (1)
createSupabaseServerClient(7-7)
src/common/components/molecules/ClaimButtonWithModal.tsx (1)
src/common/providers/TokenProvider.tsx (1)
useToken(100-106)
src/app/(spaces)/p/[proposalId]/ProposalDefinedSpace.tsx (1)
src/common/data/queries/farcaster.ts (1)
useFidFromAddress(165-183)
src/app/(spaces)/PublicSpace.tsx (1)
src/common/data/stores/app/currentSpace/index.ts (2)
setCurrentSpaceId(43-47)setCurrentTabName(51-55)
🔇 Additional comments (30)
src/common/data/queries/farcaster.ts (1)
165-183: Confirm/api/search/usersAPI route implementationI was unable to locate an API handler for
/api/search/usersin the repository. Please verify that:
- An API route exists under one of these paths:
pages/api/search/users.{js,ts}src/pages/api/search/users.{js,ts}app/api/search/users/route.{js,ts}- The endpoint returns JSON with the shape
{ value: { users: User[] } }, so thatres.data?.value?.usersis valid.src/common/components/molecules/ClaimModal.tsx (1)
28-40: Excellent refactoring for improved flexibility.The extraction of title and description into conditional constants makes the component more reusable and flexible. The logic correctly handles both token-specific and generic scenarios.
src/app/(spaces)/p/[proposalId]/ProposalDefinedSpace.tsx (3)
9-9: Good integration with the new hook.The import and usage of
useFidFromAddresshook properly integrates with the existing component architecture.
26-26: Proper hook usage for dynamic FID fetching.The hook correctly fetches the owner's Farcaster ID from their address, replacing the hardcoded value with dynamic data.
48-51: Correct prop updates for proposal support.The changes properly pass the dynamically fetched
ownerFidandproposalIdto thePublicSpacecomponent, enabling proposal-specific functionality.src/app/(spaces)/p/[proposalId]/[tabname]/page.tsx (3)
14-21: Good integration of space ID fetching.The addition of
loadProposalSpaceIdcall and the restructured props object properly support the new proposal space functionality.
28-28: Proper component transition.The switch from
ProposalDefinedSpacetoProposalPrimarySpaceContentappears to be part of the architectural refactoring for better proposal space handling.
7-8: ProposalPrimarySpaceContent interface verifiedThe
ProposalPrimarySpaceContentcomponent exists at
src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx
and its props (ProposalPrimarySpaceContentProps) correctly extendProposalPageSpaceProps, matching the fields passed (spaceId,tabName,proposalId,proposalData). No further changes needed—approving the integration.src/pages/api/space/registry/[spaceId]/index.ts (2)
140-140: LGTM: Database query updated to include new fields.The addition of
fidandidentityPublicKeyfields to the select statement is consistent with the new space registration types being supported.
159-165: LGTM: New conditional logic handles FID and identity-based spaces correctly.The implementation follows a clear hierarchy pattern:
- Contract-based spaces (existing)
- FID-based spaces (using helper function)
- Identity public key-based spaces (direct return)
- Fallback to empty array
The logic is sound and integrates well with the existing codebase.
src/pages/api/space/registry/index.ts (2)
255-260: LGTM: Query parameter extraction follows existing pattern.The extraction of
proposalIdfrom query parameters is consistent with the existingcontractAddressandnetworkparameter handling.
297-324: LGTM: Proposal lookup implementation is well-structured.The proposal lookup logic follows the same pattern as the existing contract lookup:
- Proper query parameter validation
- Consistent database query structure
- Appropriate error handling with 404 for not found cases
- Consistent response format
The use of
order("timestamp", { ascending: true }).limit(1)ensures deterministic results when multiple registrations exist for the same proposal.src/common/components/molecules/ClaimButtonWithModal.tsx (3)
15-15: LGTM: New optional prop enhances component flexibility.The addition of the optional
tokenSymbolprop allows external override of the token symbol, which is useful for contexts where the component is used outside of a TokenProvider.
23-30: LGTM: Graceful handling of missing TokenProvider context.The try-catch block properly handles the case where the component is used outside of a TokenProvider context. This is necessary because the
useTokenhook throws an error when no provider is available, as shown in the relevant code snippet fromTokenProvider.tsx.
32-36: LGTM: Symbol resolution logic follows proper fallback hierarchy.The symbol resolution correctly prioritizes:
- Explicit
tokenSymbolprop (highest priority)- Clanker data symbol from context
- Gecko data symbol from context
- Empty string fallback
This ensures consistent behavior across different usage contexts.
src/common/components/organisms/TabBar.tsx (2)
36-36: LGTM: New spaceId prop enables proposal space awareness.The addition of the optional
spaceIdprop allows the TabBar to be aware of the current space registration state, which is necessary for the proposal space functionality.
271-274: LGTM: Enhanced conditional rendering supports proposal pages.The updated condition correctly shows the claim button in two scenarios:
- Token pages (existing behavior)
- Proposal pages when no space is registered (
spaceIdis falsy)This enables users to claim and customize proposal spaces when they haven't been registered yet, aligning with the PR objectives.
src/app/(spaces)/p/[proposalId]/ProposalPrimarySpaceContent.tsx (2)
25-48: LGTM: Clean async data fetching with proper state management.The
fetchSpaceIdForProposalfunction is well-structured:
- Proper conditional logic to avoid unnecessary API calls
- Correct state updates for both success and completion
- Integration with global app store via
setCurrentSpaceId- Proper cleanup in finally block
The dependency array in the useEffect is correctly configured to re-run when relevant props change.
58-65: LGTM: Proper prop handling and component integration.The component correctly:
- Passes the resolved
spaceIdto the child component- Handles the
tabNameprop properly by checking if it's an array and providing a sensible default- Passes through all proposal-related props to
ProposalDefinedSpaceThe array check for
tabNameis necessary due to Next.js routing potentially providing arrays for dynamic segments.src/common/utils/spaceEditability.ts (3)
22-22: LGTM! Consistent pattern for proposal page support.The addition of
isProposalPagefollows the established pattern used forisTokenPage, with proper optional typing and sensible default value.Also applies to: 33-33
68-73: Good improvement: Case-insensitive address comparison.Converting addresses to lowercase before comparison is the correct approach for Ethereum addresses, which are case-insensitive. This prevents comparison failures due to checksum casing differences.
96-105: LGTM! Proposal page editability logic is consistent.The implementation correctly mirrors the token page logic for wallet-based ownership verification, including the case-insensitive address comparison.
src/app/(spaces)/PublicSpace.tsx (6)
42-42: LGTM! Props properly extended for proposal support.The
proposalIdprop is correctly typed as optional and follows the component's established pattern.Also applies to: 58-58
147-155: LGTM! Page type resolution logic is well-structured.The
resolvedPageTypecomputation correctly prioritizes the explicitpageTypeprop and falls back to detection based on other props. The "proposal:" prefix check aligns with the URL structure.
165-165: LGTM! Editability checker properly integrated.The
isProposalPageflag is correctly derived fromresolvedPageTypeand passed to the editability checker.Also applies to: 184-184
221-237: LGTM! Space resolution properly handles proposal pages.The logic correctly searches for existing proposal spaces by
proposalIdand updates the space ID accordingly. The explicit check for "profile" page type improves code clarity.
492-501: LGTM! Proposal space registration logic is correct.The implementation properly checks for existing proposal spaces before registration and correctly passes the required parameters to
registerProposalSpace.Also applies to: 536-537
728-728: LGTM! TabBar props updated correctly.The TabBar now receives the full
resolvedPageTypeinstead of just the booleanisTokenPage, providing better context. The addition ofspaceIdprop enables space-specific functionality.Also applies to: 796-796
src/common/data/stores/app/space/spaceStore.ts (2)
179-179: LGTM! Enhanced registerProposalSpace signature.Adding the
initialConfigparameter enables customizable initial configurations for proposal spaces, improving flexibility.
990-990: LGTM! Proposal space registration improvements.The changes properly align proposal spaces with the standard conventions:
- More specific "Nouns-Prop-" prefix for space names
- Uses "Profile" tab name for consistency with other space types
- Correctly utilizes the passed
initialConfig- Properly stores the
proposalIdin local space metadataAlso applies to: 1015-1015, 1039-1039, 1043-1047
There was a problem hiding this comment.
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 (1)
src/common/data/stores/app/space/spaceStore.ts (1)
220-224: Regex allows unintended chars due to hyphen range; fix validation.In
[a-zA-Z0-9-_ ],-creates a range9-_, allowing punctuation like:;<>?@. Escape or move the hyphen.Apply:
- if (/[^a-zA-Z0-9-_ ]/.test(tabName)) { + if (/[^a-zA-Z0-9 _-]/.test(tabName)) {
🧹 Nitpick comments (3)
src/common/data/stores/app/space/spaceStore.ts (3)
716-716: Remove unused variable to satisfy lint.
_diffis unused.Apply:
- const _diff = moment.duration(remoteTimestamp.diff(localTimestamp)); + // const diff = moment.duration(remoteTimestamp.diff(localTimestamp));
948-948: Drop noisy console.log in production path.Prefer gating logs or using a logger at debug level.
Apply:
- console.log("Nounspace registration response:", data); + // console.debug("Nounspace registration response:", data);
1040-1055: Gate verbose console logs or remove.Reduce noise or guard with an env flag.
Example:
const debug = process.env.NEXT_PUBLIC_DEBUG === "1"; // ... if (debug) console.log("[registerProposalSpace] unsignedRegistration:", unsignedRegistration); if (debug) console.log("[registerProposalSpace] Making API call to /api/space/registry"); // ... if (debug) console.log("[registerProposalSpace] API response:", data); if (debug) console.log("[registerProposalSpace] New space ID:", newSpaceId);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/common/data/stores/app/space/spaceStore.ts(13 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/common/data/stores/app/space/spaceStore.ts (4)
src/app/(spaces)/Space.tsx (1)
SpaceConfig(37-49)src/common/types/spaceData.ts (1)
SPACE_TYPES(7-11)src/pages/api/space/registry/index.ts (3)
ModifiableSpacesResponse(99-102)SpaceRegistrationProposer(41-44)RegisterNewSpaceResponse(97-97)src/common/lib/signedFiles.ts (1)
signSignable(54-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Vercel Agent Review
🔇 Additional comments (9)
src/common/data/stores/app/space/spaceStore.ts (9)
94-95: Adding proposalId to CachedSpace looks good.Correctly models proposal-backed spaces in local/remote caches.
167-170: Signature now requires initialConfig; update docs/callers if any remain.This change is fine; ensure examples/docs are aligned.
303-314: Good defensive init of localSpaces entry before writing tabs.Prevents undefined access when saving first tab on a space.
794-796: Correct spaceType for FID registration.PROFILE type is appropriate here.
936-937: Correct spaceType for contract registration.TOKEN type is appropriate here.
973-985: Confirm expected default tab for token spaces (“Token” vs “Overview”).If the UI expects “Overview” as default, creating “Token” will cause a mismatch. Align the created tab with the declared default.
1071-1079: Tab creation flow and error handling look solid.Nice explicit try/catch with actionable error message.
1133-1134: Good: persisting proposalId from server SpaceInfo to local cache.Ensures client state has proposal linkage.
999-1029: Fix proposal-space dedupe: wrong API response shape; risk of duplicates.Current code calls the modifiable-spaces endpoint and expects a spaces array; the proposal lookup by proposalId returns a single spaceId. This will miss existing spaces and create duplicates.
Apply:
- registerProposalSpace: async (proposalId, initialConfig) => { - try { - let existingSpaceId: string | undefined; - - // Check if a space already exists for this proposal - try { - const { data: existingSpaces } = await axiosBackend.get<ModifiableSpacesResponse>( - "/api/space/registry", - { - params: { - identityPublicKey: get().account.currentSpaceIdentityPublicKey, - proposalId, - }, - }, - ); - - if (existingSpaces.value) { - const existingSpace = existingSpaces.value.spaces.find( - (space) => space.proposalId === proposalId - ); - if (existingSpace) { - existingSpaceId = existingSpace.spaceId; - } - } - } catch (checkError) { - console.error("Error checking for existing proposal space:", checkError); - } - - if (existingSpaceId) { - return existingSpaceId; - } + registerProposalSpace: async (proposalId, initialConfig) => { + try { + // Guard identity to avoid non-null assertion crashes + const currentIdentityKey = get().account.currentSpaceIdentityPublicKey; + const identity = get().account.getCurrentIdentity?.(); + if (!currentIdentityKey || !identity?.rootKeys?.privateKey) { + console.error("No current space identity available"); + return undefined; + } + + // Check if a space already exists for this proposalId + try { + type ProposalLookupResponse = { result: string; value?: { spaceId?: string } }; + const { data: lookup } = await axiosBackend.get<ProposalLookupResponse>( + "/api/space/registry", + { params: { proposalId }, timeout: 5000 }, + ); + if (lookup?.value?.spaceId) { + return lookup.value.spaceId; + } + } catch (checkError) { + console.error("Error checking for existing proposal space:", checkError); + }This mirrors the previously noted API behavior and prevents duplicate registrations. (Based on learnings)
| } from "@/constants/initialPersonSpace"; | ||
| import { SPACE_TYPES } from "@/common/types/spaceData"; | ||
| import { INITIAL_SPACE_CONFIG_EMPTY } from "@/constants/initialSpaceConfig"; | ||
| import createIntialProfileSpaceConfigForFid from "@/constants/initialProfileSpace"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Typo in import: rename to createInitialProfileSpaceConfigForFid.
Avoid propagating a misspelled API. Rename import (and the source export) to “Initial”.
Apply:
-import createIntialProfileSpaceConfigForFid from "@/constants/initialProfileSpace";
+import createInitialProfileSpaceConfigForFid from "@/constants/initialProfileSpace";Verify the exported symbol name across the repo:
🏁 Script executed:
#!/bin/bash
# Find all references (both misspelled and corrected)
rg -nP --type=ts 'createInti(?:al|ial)ProfileSpaceConfigForFid|createInitialProfileSpaceConfigForFid'Length of output: 640
Rename createIntialProfileSpaceConfigForFid to createInitialProfileSpaceConfigForFid across all files
- src/constants/initialProfileSpace.ts: rename the
constandexport defaulttocreateInitialProfileSpaceConfigForFid - src/common/data/stores/app/space/spaceStore.ts: update import (line 10) and the call at line 825
- src/app/(spaces)/s/[handle]/utils.ts: update import (line 6) and the spread call at line 104
🤖 Prompt for AI Agents
In src/common/data/stores/app/space/spaceStore.ts around line 10 (and update
call at line 825), rename the imported identifier
createIntialProfileSpaceConfigForFid to createInitialProfileSpaceConfigForFid
and update the corresponding import path usage; also rename the exported const
in src/constants/initialProfileSpace.ts to createInitialProfileSpaceConfigForFid
(and update its export default) so the identifier matches; finally update the
import at src/app/(spaces)/s/[handle]/utils.ts (line ~6) and the spread call at
line ~104 to use createInitialProfileSpaceConfigForFid so all references are
consistent.
There was a problem hiding this comment.
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 (1)
src/app/(spaces)/s/[handle]/utils.ts (1)
41-94: Align getTabList return type with Tab; avoid extra fieldsYou return objects containing extra properties (fid, order, updatedAt) that don’t exist on Tab. This will trip type checks.
Apply:
- .select('spaceId, spaceName, fid') + .select('spaceId, spaceName') @@ - return [registration]; + return [{ spaceId: registration.spaceId, spaceName: registration.spaceName }]; @@ - const enhancedTab = { - ...registration, - order: tabOrderJson.tabOrder || [], - updatedAt: tabOrderJson.timestamp || new Date().toISOString(), - }; - - return [enhancedTab]; + return [{ + spaceId: registration.spaceId, + spaceName: registration.spaceName, + }]; @@ - return [registration]; + return [{ spaceId: registration.spaceId, spaceName: registration.spaceName }];Optionally, if you need order/updatedAt, extend Tab:
// At lines 8-11 (outside hunk) export type Tab = { spaceId: string; spaceName: string; order?: string[]; updatedAt?: string; };
🧹 Nitpick comments (3)
src/common/types/spaceData.ts (2)
38-42: Add canonical handle to Profile space data (for URL building)spaceName may be a display name; URLs should use the route handle. Add handle and use it client-side.
Apply:
export interface ProfileSpacePageData extends SpacePageData { spaceType: typeof SPACE_TYPES.PROFILE; defaultTab: 'Profile'; identityPublicKey?: string; + // Canonical route slug (e.g., Farcaster handle) for building URLs + handle: string; }Then return handle from the server loader and consume it in ProfileSpace.tsx (see comment there).
44-52: Tighten Token space network type to project typeAlign with MasterToken.network to avoid stringly-typed drift.
Apply:
export interface TokenSpacePageData extends SpacePageData { spaceType: typeof SPACE_TYPES.TOKEN; defaultTab: 'Token'; contractAddress: string; - network: string; + network: MasterToken['network']; spaceOwnerAddress: Address; tokenData?: MasterToken; // Optional to allow for loading states identityPublicKey?: string; }src/app/(spaces)/s/[handle]/ProfileSpace.tsx (1)
37-70: Avoid logging identity keys in client consoleThese logs include identity keys and user IDs; remove or gate behind a debug flag to reduce noise/PII in production.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/app/(spaces)/p/[proposalId]/ProposalSpace.tsx(1 hunks)src/app/(spaces)/p/[proposalId]/utils.ts(3 hunks)src/app/(spaces)/s/[handle]/ProfileSpace.tsx(1 hunks)src/app/(spaces)/s/[handle]/utils.ts(4 hunks)src/app/(spaces)/t/[network]/[contractAddress]/TokenSpace.tsx(1 hunks)src/common/types/spaceData.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/app/(spaces)/t/[network]/[contractAddress]/TokenSpace.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
src/app/(spaces)/s/[handle]/utils.ts (2)
src/common/types/spaceData.ts (2)
ProfileSpacePageData(38-42)SPACE_TYPES(7-11)src/common/data/database/supabase/clients/server.ts (1)
createSupabaseServerClient(7-7)
src/common/types/spaceData.ts (3)
src/app/(spaces)/Space.tsx (1)
SpaceConfig(37-49)src/common/providers/TokenProvider.tsx (1)
MasterToken(19-24)src/app/(spaces)/p/[proposalId]/utils.ts (1)
ProposalData(8-24)
src/app/(spaces)/p/[proposalId]/utils.ts (3)
src/common/data/database/supabase/clients/server.ts (1)
createSupabaseServerClient(7-7)src/common/types/spaceData.ts (2)
ProposalSpacePageData(54-61)SPACE_TYPES(7-11)src/constants/initialProposalSpace.ts (1)
createInitalProposalSpaceConfigForProposalId(6-217)
src/app/(spaces)/p/[proposalId]/ProposalSpace.tsx (4)
src/common/types/spaceData.ts (1)
ProposalSpacePageData(54-61)src/common/providers/ProposalProvider.tsx (1)
useProposal(76-82)src/common/lib/hooks/useCurrentSpaceIdentityPublicKey.ts (1)
useCurrentSpaceIdentityPublicKey(3-5)src/app/(spaces)/PublicSpace.tsx (1)
PublicSpace(29-762)
src/app/(spaces)/s/[handle]/ProfileSpace.tsx (3)
src/common/types/spaceData.ts (1)
ProfileSpacePageData(38-42)src/common/lib/hooks/useCurrentSpaceIdentityPublicKey.ts (1)
useCurrentSpaceIdentityPublicKey(3-5)src/app/(spaces)/PublicSpace.tsx (1)
PublicSpace(29-762)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Vercel Agent Review
🔇 Additional comments (12)
src/app/(spaces)/p/[proposalId]/utils.ts (5)
2-6: Restore named imports for Supabase client and initial config factoryBoth modules export named functions; the default imports here will be
undefinedat runtime, breaking every call site. Please switch back to named imports.-import createSupabaseServerClient from "@/common/data/database/supabase/clients/server"; +import { createSupabaseServerClient } from "@/common/data/database/supabase/clients/server"; import { unstable_noStore as noStore } from 'next/cache'; import { WEBSITE_URL } from "@/constants/app"; -import { ProposalSpacePageData, SPACE_TYPES } from "@/common/types/spaceData"; -import createInitalProposalSpaceConfigForProposalId from "@/constants/initialProposalSpace"; +import { ProposalSpacePageData, SPACE_TYPES } from "@/common/types/spaceData"; +import { createInitalProposalSpaceConfigForProposalId } from "@/constants/initialProposalSpace";
8-16: Keep proposer/signers typed asAddressThese IDs still represent Ethereum addresses. Downgrading them to
stringloses the viem type guarantees and forces unsafe casts later.export interface ProposalData { id: string; title: string; proposer: { - id: string; // API returns string addresses + id: Address; }; signers?: { - id: string; // API returns string addresses + id: Address; }[];
204-217: Order by newest registration when looking up spaceIdAscending timestamp returns the oldest registration, which is the wrong space after reclaims. Please fetch the most recent row.
.select("spaceId") .eq("proposalId", proposalId) - .order("timestamp", { ascending: true }) + .order("timestamp", { ascending: false }) .limit(1);
224-240: Same ordering fix needed for registration lookup
loadProposalSpaceRegistrationshould also grab the latest registration; current ascending sort returns stale data..select("spaceId, identityPublicKey") .eq("proposalId", proposalId) - .order("timestamp", { ascending: true }) + .order("timestamp", { ascending: false }) .limit(1);
290-299: Validate proposer address before casting toAddressHard-coding
"0x0"checks misses many invalid values (uppercase, malformed hex, etc.), and the direct cast is unsafe. UseisAddressto guard this before creating space data.-import { Address } from "viem"; +import { Address, isAddress } from "viem"; … - if (!proposalData?.proposer?.id || proposalData.proposer.id === "0x0" || proposalData.proposer.id === "0x0000000000000000000000000000000000000000") { + if ( + !proposalData?.proposer?.id || + proposalData.proposer.id === "0x0" || + proposalData.proposer.id === "0x0000000000000000000000000000000000000000" || + !isAddress(proposalData.proposer.id) + ) { return null; } … - const ownerAddress = proposalData.proposer.id as Address; + const ownerAddress = proposalData.proposer.id as Address;src/app/(spaces)/s/[handle]/utils.ts (2)
17-37: LGTM: safer Neynar response handlingValidation guards before destructuring are good; error logging is appropriate.
165-173: Restore correct defaults: keep tab default as "Profile"; handle slug fallbackDefaulting tabName to username hides the Profile tab; falling back spaceName to "Profile" yields routes like /s/Profile/… when Neynar omits username. Use the route handle for fallback and keep tab default “Profile”.
Apply:
- const tabName = tabNameParam || spaceOwnerUsername || "Profile"; + const tabName = tabNameParam || "Profile"; @@ return createProfileSpaceData( spaceId, // This can be undefined if space doesn't exist yet - spaceOwnerUsername || "Profile", + spaceOwnerUsername || handle, spaceOwnerFid, tabName, identityPublicKey );src/common/types/spaceData.ts (3)
7-15: LGTM: central SPACE_TYPES and derived typeConsistent discriminant with SpaceTypeValue; good base for guards.
16-35: Confirm server/client split for functions on SpacePageDataRequiring spacePageUrl/isEditable at type-level but omitting them on server responses is fine since consumers use Omit<…>. Ensure all loaders/components consistently omit and add client-side.
66-77: LGTM: type guardsCorrect discriminants for narrowing.
src/app/(spaces)/s/[handle]/ProfileSpace.tsx (2)
51-57: LGTM: editability via FID or identity keyClear, minimal, and aligns with registry semantics.
78-90: Encode handle segment; avoid using display name for URLsspaceName may contain spaces/special chars and may differ from the [handle] slug. Encode and prefer canonical handle when available.
Apply minimal safety now:
- spacePageUrl: (tabName: string) => `/s/${spaceData.spaceName}/${encodeURIComponent(tabName)}`, + spacePageUrl: (tabName: string) => + `/s/${encodeURIComponent(('handle' in spaceData ? (spaceData as any).handle : spaceData.spaceName))}/${encodeURIComponent(tabName)}`,Recommended follow-up (clean): add handle: string to ProfileSpacePageData (types file), return it from the server loader, and remove the cast:
- `/s/${encodeURIComponent(('handle' in spaceData ? (spaceData as any).handle : spaceData.spaceName))}/${encodeURIComponent(tabName)}`, + `/s/${encodeURIComponent(spaceData.handle)}/${encodeURIComponent(tabName)}`,
| // Require user to be logged in (have an identity key) | ||
| if (!currentUserIdentityPublicKey) { | ||
| console.log('[ProposalSpace] User not logged in - not editable'); | ||
| return false; | ||
| } | ||
|
|
||
| // Check wallet ownership (original logic) | ||
| const hasWalletOwnership = wallets?.some( | ||
| (w) => w.address.toLowerCase() === ownerAddress.toLowerCase() | ||
| ) || false; | ||
|
|
||
| // Check identity key ownership (only if space is registered) | ||
| const hasIdentityOwnership = !!(spaceId && spaceIdentityPublicKey && | ||
| spaceIdentityPublicKey === currentUserIdentityPublicKey); | ||
|
|
||
| console.log('[ProposalSpace] Editability check details:', { | ||
| ownerAddress, | ||
| currentUserFid, | ||
| walletAddresses: wallets?.map((w) => w.address), | ||
| spaceId, | ||
| spaceIdentityPublicKey, | ||
| currentUserIdentityPublicKey, | ||
| hasWalletOwnership, | ||
| hasIdentityOwnership, | ||
| isEditable: hasWalletOwnership || hasIdentityOwnership | ||
| }); | ||
|
|
||
| return hasWalletOwnership || hasIdentityOwnership; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allow wallet owners without an identity key to edit
We can’t require currentUserIdentityPublicKey before we even check wallet ownership. First‑time proposers land on this page without a registered identity key; with the early return, isEditable always resolves to false, so the claim/customize button never appears and they can’t finish the flow. Drop the identity-key gate and fold the identity comparison into hasIdentityOwnership so wallet ownership alone still unlocks editing.
- // Require user to be logged in (have an identity key)
- if (!currentUserIdentityPublicKey) {
- console.log('[ProposalSpace] User not logged in - not editable');
- return false;
- }
-
// Check wallet ownership (original logic)
const hasWalletOwnership = wallets?.some(
(w) => w.address.toLowerCase() === ownerAddress.toLowerCase()
) || false;
// Check identity key ownership (only if space is registered)
- const hasIdentityOwnership = !!(spaceId && spaceIdentityPublicKey &&
- spaceIdentityPublicKey === currentUserIdentityPublicKey);
+ const hasIdentityOwnership = !!(
+ spaceId &&
+ spaceIdentityPublicKey &&
+ currentUserIdentityPublicKey &&
+ spaceIdentityPublicKey === currentUserIdentityPublicKey
+ );
console.log('[ProposalSpace] Editability check details:', {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Require user to be logged in (have an identity key) | |
| if (!currentUserIdentityPublicKey) { | |
| console.log('[ProposalSpace] User not logged in - not editable'); | |
| return false; | |
| } | |
| // Check wallet ownership (original logic) | |
| const hasWalletOwnership = wallets?.some( | |
| (w) => w.address.toLowerCase() === ownerAddress.toLowerCase() | |
| ) || false; | |
| // Check identity key ownership (only if space is registered) | |
| const hasIdentityOwnership = !!(spaceId && spaceIdentityPublicKey && | |
| spaceIdentityPublicKey === currentUserIdentityPublicKey); | |
| console.log('[ProposalSpace] Editability check details:', { | |
| ownerAddress, | |
| currentUserFid, | |
| walletAddresses: wallets?.map((w) => w.address), | |
| spaceId, | |
| spaceIdentityPublicKey, | |
| currentUserIdentityPublicKey, | |
| hasWalletOwnership, | |
| hasIdentityOwnership, | |
| isEditable: hasWalletOwnership || hasIdentityOwnership | |
| }); | |
| return hasWalletOwnership || hasIdentityOwnership; | |
| }; | |
| // Check wallet ownership (original logic) | |
| const hasWalletOwnership = wallets?.some( | |
| (w) => w.address.toLowerCase() === ownerAddress.toLowerCase() | |
| ) || false; | |
| // Check identity key ownership (only if space is registered) | |
| const hasIdentityOwnership = !!( | |
| spaceId && | |
| spaceIdentityPublicKey && | |
| currentUserIdentityPublicKey && | |
| spaceIdentityPublicKey === currentUserIdentityPublicKey | |
| ); | |
| console.log('[ProposalSpace] Editability check details:', { | |
| ownerAddress, | |
| currentUserFid, | |
| walletAddresses: wallets?.map((w) => w.address), | |
| spaceId, | |
| spaceIdentityPublicKey, | |
| currentUserIdentityPublicKey, | |
| hasWalletOwnership, | |
| hasIdentityOwnership, | |
| isEditable: hasWalletOwnership || hasIdentityOwnership | |
| }); | |
| return hasWalletOwnership || hasIdentityOwnership; | |
| }; |
🤖 Prompt for AI Agents
In src/app/(spaces)/p/[proposalId]/ProposalSpace.tsx around lines 50 to 78,
remove the early gate that returns false when currentUserIdentityPublicKey is
falsy so wallet owners without a registered identity can still edit; keep the
wallet ownership check as-is, and fold the identity comparison into
hasIdentityOwnership (only true when spaceId and spaceIdentityPublicKey exist
and match currentUserIdentityPublicKey); preserve the detailed debug log and
return hasWalletOwnership || hasIdentityOwnership.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (16)
src/common/providers/ProposalProvider.tsx (3)
27-39: Add AbortSignal support to prevent stale state updates.When navigating fast, fetches can complete after unmount or a newer request. Support an optional AbortSignal.
Apply:
-export const fetchProposalData = async (proposalId: string): Promise<ProposalData | null> => { +export const fetchProposalData = async ( + proposalId: string, + signal?: AbortSignal, +): Promise<ProposalData | null> => { try { - const response = await fetch(`/api/proposal/${proposalId}`); + const response = await fetch(`/api/proposal/${proposalId}`, { signal }); if (!response.ok) { throw new Error(`Failed to fetch proposal: ${response.statusText}`); } const data = await response.json(); return data; } catch (error) { console.error("Failed to fetch proposal data:", error); return null; } };
51-61: Pass AbortSignal and guard setState on abort.Create a controller per request, pass its signal, and ignore abort errors.
Apply:
- const fetchProposalInfo = useCallback(async (proposalId: string): Promise<void> => { - setIsLoading(true); - try { - const data = await fetchProposalData(proposalId); - setProposalData(data); - } catch (error) { - console.error("Failed to fetch proposal data:", error); - } finally { - setIsLoading(false); - } - }, []); + const fetchProposalInfo = useCallback(async (proposalId: string): Promise<void> => { + const controller = new AbortController(); + setIsLoading(true); + try { + const data = await fetchProposalData(proposalId, controller.signal); + setProposalData(data); + } catch (error: any) { + if (error?.name !== "AbortError") { + console.error("Failed to fetch proposal data:", error); + } + } finally { + setIsLoading(false); + } + // Return a no-op cancel function if you later expose cancellation to callers + }, []);
63-68: Ensure effect cancels in-flight fetches on param change/unmount.Tie an AbortController to the effect for the initial auto-load.
Apply:
// Loads if defaultProposalData is not provided useEffect(() => { - if (!defaultProposalData) { - fetchProposalInfo(proposalId); - } + if (!defaultProposalData) { + fetchProposalInfo(proposalId); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [proposalId, fetchProposalInfo]);If you adopt the controller in fetchProposalInfo as suggested above and want strict cancellation here, consider exposing cancel or inlining the fetch with a local controller. Otherwise, current approach is acceptable.
src/app/(spaces)/SpacePage.tsx (1)
53-56: Consider simpler fallback instead of Suspense.If you don’t strictly need Suspense here, returning null (or a skeleton) until config is ready avoids complexity and edge-cases.
- if (!config) { - throw deferredRef.current.promise; - } + if (!config) { + return null; + }docs/PUBLIC_SPACES_PATTERN.md (4)
21-33: Add language to fenced code block (markdownlint MD040).Mark this diagram as text.
-``` +```text Server-side data loading (Utils Functions) ↓ Omit<SpaceData, 'isEditable'> (Serializable data) ↓ Page Component (Routes and renders) ↓ Client-side space component (Adds isEditable function) ↓ Common space management (PublicSpace) ↓ Space rendering and interaction -``` +```
229-249: Add language to fenced code block and fix [tabname] casing.
- Mark as text for the tree.
- Use [tabName] consistently.
-``` +```text src/app/(spaces)/ ├── s/[handle]/ # Profile Spaces @@ -│ └── [tabname]/page.tsx # Tab wrapper (re-exports main page) +│ └── [tabName]/page.tsx # Tab wrapper (re-exports main page) └── t/[network]/[contractAddress]/ # Token Spaces @@ -``` +```
374-384: Use spaceId consistently (not id) to match examples.Prevents confusion across docs.
-interface SpaceData { - id?: string; // Set by PublicSpace through registration +interface SpaceData { + spaceId?: string; // Set by PublicSpace through registration spaceName: string; spaceType: SPACE_TYPES; updatedAt: string; spacePageUrl: (tabName: string) => string; isEditable: (currentUserFid?: number, wallets?: { address: Address }[]) => boolean; defaultTab: string; // Default tab name for this space config: SpaceConfig; }
552-560: Add language to fenced code block (markdownlint MD040).Mark directory structure as text.
- ``` + ```text src/app/(spaces)/{type}/[param]/ ├── page.tsx # Page component (routing) ├── utils.ts # Data loading functions ├── {Type}Space.tsx # Client-side space component ├── layout.tsx # Space-specific layout └── [tabName]/page.tsx # Tab wrapper (re-exports main page) - ``` + ```src/app/(spaces)/t/[network]/[contractAddress]/layout.tsx (2)
2-2: Import Metadata from "next" (not "next/types").Recommended and future-proof.
-import { Metadata } from "next/types"; +import type { Metadata } from "next";
16-21: Remove unnecessary await on params and type params.
paramsisn’t a promise; add a minimal type.-export async function generateMetadata({ - params, -}): Promise<Metadata> { - const { network, contractAddress, tabName: tabNameParam } = await params; +export async function generateMetadata({ + params, +}: { + params: { network?: string; contractAddress?: string; tabName?: string }; +}): Promise<Metadata> { + const { network, contractAddress, tabName: tabNameParam } = params;src/app/(spaces)/s/[handle]/utils.ts (1)
45-51: Order results to get the newest registration.Without ordering,
.limit(1)is non-deterministic.const { data: registrations, error: regError } = await createSupabaseServerClient() .from("spaceRegistrations") .select('spaceId, spaceName, fid') .eq('fid', fid) + .order("timestamp", { ascending: false }) .limit(1)src/app/(spaces)/s/[handle]/ProfileSpace.tsx (1)
85-93: Align isEditable signature with PublicSpace expectations.PublicSpace calls isEditable with (currentUserFid, walletAddresses). Accept a second, optional arg for type compatibility.
- isEditable: (currentUserFid: number | undefined) => + isEditable: (currentUserFid: number | undefined, _walletAddresses?: string[]) => isProfileSpaceEditable( spaceData.spaceOwnerFid, currentUserFid, spaceData.spaceId, spaceData.identityPublicKey, currentUserIdentityPublicKey ),src/common/data/stores/app/space/spaceStore.ts (2)
1001-1024: Dedupe proposal spaces by proposalId via the dedicated API path.Current check filters only modifiable spaces for the current identity; use the proposalId lookup to avoid duplicates across identities.
- try { - const { data: existingSpaces } = await axiosBackend.get<ModifiableSpacesResponse>( - "/api/space/registry", - { - params: { - identityPublicKey: get().account.currentSpaceIdentityPublicKey, - }, - }, - ); - - if (existingSpaces.value) { - const existingSpace = existingSpaces.value.spaces.find( - (space) => space.proposalId === proposalId - ); - if (existingSpace) { - existingSpaceId = existingSpace.spaceId; - } - } - } catch (checkError) { + try { + type ProposalLookupResponse = { result: string; value?: { spaceId?: string } }; + const { data } = await axiosBackend.get<ProposalLookupResponse>( + "/api/space/registry", + { params: { proposalId } }, + ); + if (data?.value?.spaceId) { + existingSpaceId = data.value.spaceId; + } + } catch (checkError) { console.error("Error checking for existing proposal space:", checkError); - } + }
943-954: Reduce verbose console logs in production paths.Registration logs are noisy; demote to debug or guard by NODE_ENV.
- console.log("Nounspace registration response:", data); + if (process.env.NODE_ENV === "development") console.log("Nounspace registration response:", data); @@ - console.log("[registerProposalSpace] unsignedRegistration:", unsignedRegistration); + if (process.env.NODE_ENV === "development") console.log("[registerProposalSpace] unsignedRegistration:", unsignedRegistration); @@ - console.log("[registerProposalSpace] Making API call to /api/space/registry"); + if (process.env.NODE_ENV === "development") console.log("[registerProposalSpace] Making API call to /api/space/registry"); @@ - console.log("[registerProposalSpace] API response:", data); + if (process.env.NODE_ENV === "development") console.log("[registerProposalSpace] API response:", data); @@ - console.log("[registerProposalSpace] New space ID:", newSpaceId); + if (process.env.NODE_ENV === "development") console.log("[registerProposalSpace] New space ID:", newSpaceId); @@ - console.log("[registerProposalSpace] Space initialized in store"); + if (process.env.NODE_ENV === "development") console.log("[registerProposalSpace] Space initialized in store"); @@ - console.log("[registerProposalSpace] Creating Overview tab..."); + if (process.env.NODE_ENV === "development") console.log("[registerProposalSpace] Creating Overview tab..."); @@ - console.log("[registerProposalSpace] Overview tab created successfully"); + if (process.env.NODE_ENV === "development") console.log("[registerProposalSpace] Overview tab created successfully");Also applies to: 1046-1054, 1067-1078
src/app/(spaces)/t/[network]/[contractAddress]/utils.ts (2)
211-215: Return identityPublicKey and drop unused array.You compute owningIdentities but don’t use them; at minimum include identityPublicKey in the returned space data and remove the unused var.
- // Add identityPublicKey to owningIdentities if not already included - const finalOwningIdentities = [...ownership.owningIdentities]; - if (internalData.identityPublicKey && !finalOwningIdentities.includes(internalData.identityPublicKey)) { - finalOwningIdentities.push(internalData.identityPublicKey); - } + // Keep identityPublicKey available on the space data + // (owningIdentities can be re-derived when needed) @@ return { spaceId: internalData.spaceId, spaceName, spaceType: SPACE_TYPES.TOKEN, updatedAt: new Date().toISOString(), defaultTab: "Token", currentTab: tabName, spaceOwnerFid, spaceOwnerAddress, config, contractAddress, network, tokenData, + identityPublicKey: internalData.identityPublicKey, };Also applies to: 245-258
17-19: Remove unused import._MasterToken alias is unused.
-import { MasterToken as _MasterToken } from "@/common/providers/TokenProvider";
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
docs/PUBLIC_SPACES_PATTERN.md(1 hunks)src/app/(spaces)/SpacePage.tsx(2 hunks)src/app/(spaces)/p/[proposalId]/utils.ts(3 hunks)src/app/(spaces)/s/[handle]/ProfileSpace.tsx(1 hunks)src/app/(spaces)/s/[handle]/utils.ts(4 hunks)src/app/(spaces)/t/[network]/[contractAddress]/layout.tsx(3 hunks)src/app/(spaces)/t/[network]/[contractAddress]/utils.ts(1 hunks)src/common/data/stores/app/space/spaceStore.ts(13 hunks)src/common/providers/ProposalProvider.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
src/app/(spaces)/t/[network]/[contractAddress]/layout.tsx (2)
src/common/data/queries/serverTokenData.ts (1)
fetchMasterTokenServer(15-40)src/constants/etherscanChainIds.ts (1)
EtherScanChainName(1-1)
src/app/(spaces)/SpacePage.tsx (1)
src/app/(spaces)/Space.tsx (1)
SpaceConfig(37-49)
src/app/(spaces)/p/[proposalId]/utils.ts (3)
src/common/data/database/supabase/clients/server.ts (1)
createSupabaseServerClient(7-7)src/common/types/spaceData.ts (2)
ProposalSpacePageData(54-61)SPACE_TYPES(7-11)src/constants/initialProposalSpace.ts (1)
createInitalProposalSpaceConfigForProposalId(6-217)
src/common/providers/ProposalProvider.tsx (1)
src/app/(spaces)/p/[proposalId]/utils.ts (1)
ProposalData(8-24)
src/app/(spaces)/s/[handle]/ProfileSpace.tsx (3)
src/common/types/spaceData.ts (1)
ProfileSpacePageData(38-42)src/common/lib/hooks/useCurrentSpaceIdentityPublicKey.ts (1)
useCurrentSpaceIdentityPublicKey(3-5)src/app/(spaces)/PublicSpace.tsx (1)
PublicSpace(29-762)
src/app/(spaces)/t/[network]/[contractAddress]/utils.ts (8)
src/common/data/database/supabase/clients/server.ts (1)
createSupabaseServerClient(7-7)src/common/data/api/etherscan.ts (3)
OwnerType(16-16)loadViemViewOnlyContract(183-202)contractOwnerFromContract(216-307)src/common/data/queries/clanker.ts (2)
TokenOwnerLookup(51-57)tokenRequestorFromContractAddress(59-130)src/common/data/database/supabase/serverHelpers.ts (2)
loadOwnedItentitiesForWalletAddress(6-15)loadOwnedItentitiesForFid(42-49)src/common/types/spaceData.ts (2)
TokenSpacePageData(44-52)SPACE_TYPES(7-11)src/common/data/queries/serverTokenData.ts (1)
fetchMasterTokenServer(15-40)src/constants/etherscanChainIds.ts (1)
EtherScanChainName(1-1)src/constants/initialTokenSpace.ts (1)
createInitialTokenSpaceConfigForAddress(10-443)
src/common/data/stores/app/space/spaceStore.ts (4)
src/app/(spaces)/Space.tsx (1)
SpaceConfig(37-49)src/common/types/spaceData.ts (1)
SPACE_TYPES(7-11)src/pages/api/space/registry/index.ts (3)
ModifiableSpacesResponse(99-102)SpaceRegistrationProposer(41-44)RegisterNewSpaceResponse(97-97)src/common/lib/signedFiles.ts (1)
signSignable(54-62)
src/app/(spaces)/s/[handle]/utils.ts (2)
src/common/types/spaceData.ts (2)
ProfileSpacePageData(38-42)SPACE_TYPES(7-11)src/common/data/database/supabase/clients/server.ts (1)
createSupabaseServerClient(7-7)
🪛 markdownlint-cli2 (0.18.1)
docs/PUBLIC_SPACES_PATTERN.md
21-21: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
229-229: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
412-412: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
552-552: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Vercel Agent Review
🔇 Additional comments (9)
src/common/providers/ProposalProvider.tsx (1)
13-17: Good fix: context type now matches async implementation.Typing
fetchProposalInfoasPromise<void>and exposingisLoadingis correct.docs/PUBLIC_SPACES_PATTERN.md (1)
113-118: Fix TokenSpace example args: use ownerAddress (not ownerId/ownerIdType).Aligns with TokenSpaceData.
- return createTokenSpaceData(spaceId, spaceName, contractAddress, network, ownerId, ownerIdType, tokenData, tabName); + return createTokenSpaceData( + spaceId, + spaceName, + contractAddress, + network, + ownerAddress, + tokenData, + tabName + );src/app/(spaces)/s/[handle]/utils.ts (3)
17-37: Robust Neynar response validation looks good.Graceful fallback and logging are appropriate.
165-173: Fix tab default and spaceName fallback (route correctness, customization visibility).Keep tab default as literal "Profile"; use handle for missing username.
- const tabName = tabNameParam || spaceOwnerUsername || "Profile"; + const tabName = tabNameParam || "Profile"; return createProfileSpaceData( spaceId, // This can be undefined if space doesn't exist yet - spaceOwnerUsername || "Profile", + spaceOwnerUsername || handle, spaceOwnerFid, - tabName, + tabName, identityPublicKey );
123-135: Fetch latest registration (not oldest).Ascending true + limit(1) returns the oldest row.
- .order("timestamp", { ascending: true }) + .order("timestamp", { ascending: false })src/app/(spaces)/s/[handle]/ProfileSpace.tsx (1)
75-93: Hooks usage LGTM.Hooks are called unconditionally before branching; previous conditional-hook issue is resolved.
Confirm this component renders under both logged-in and logged-out states without React hook warnings.
src/app/(spaces)/p/[proposalId]/utils.ts (2)
2-6: Fix Supabase client import (named export).server client exports a named function; default import will fail.
-import createSupabaseServerClient from "@/common/data/database/supabase/clients/server"; +import { createSupabaseServerClient } from "@/common/data/database/supabase/clients/server";
204-218: Fetch newest registration, not oldest.Ordering ascending returns the oldest record; use descending to get latest registration.
- .order("timestamp", { ascending: true }) + .order("timestamp", { ascending: false }) .limit(1);src/common/data/stores/app/space/spaceStore.ts (1)
716-716: Remove unused variable to satisfy lint.The underscore var is unused.
- const _diff = moment.duration(remoteTimestamp.diff(localTimestamp)); + // const diff = moment.duration(remoteTimestamp.diff(localTimestamp));
| @@ -1,14 +1,18 @@ | |||
| import { Address } from "viem"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden proposer address validation with viem.isAddress and zero-address check.
Guard against malformed or null addresses before casting/using as Address.
-import { Address } from "viem";
+import { Address, isAddress } from "viem";
@@
- // Check if proposal data is valid (not the fallback with 0x0 address)
- if (!proposalData?.proposer?.id || proposalData.proposer.id === "0x0" || proposalData.proposer.id === "0x0000000000000000000000000000000000000000") {
+ // Validate proposer address (must be a proper EVM address and not the null address)
+ const proposer = proposalData?.proposer?.id;
+ const NULL_ADDRESS = "0x0000000000000000000000000000000000000000";
+ if (!proposer || !isAddress(proposer) || proposer.toLowerCase() === NULL_ADDRESS) {
return null;
}
@@
- const ownerAddress = proposalData.proposer.id as Address;
+ const ownerAddress = proposer as Address;Also applies to: 290-298
🤖 Prompt for AI Agents
In src/app/(spaces)/p/[proposalId]/utils.ts around line 1 (and also apply same
change at lines 290-298), the proposer address is being cast/used as an Address
without validating it; update the code to import and use viem's isAddress to
first check the value is a valid non-zero address (reject/address zero
(0x000...000) as invalid), handle null/undefined/malformed inputs by returning
an error or undefined as appropriate, and only cast to Address after the
validation passes so downstream code never operates on an invalid proposer
address.
| export async function loadProposalSpaceRegistration(proposalId: string): Promise<{ | ||
| spaceId?: string; | ||
| identityPublicKey?: string; | ||
| } | null> { | ||
| noStore(); | ||
| try { | ||
| const { data, error } = await createSupabaseServerClient() | ||
| .from("spaceRegistrations") | ||
| .select("spaceId, identityPublicKey") | ||
| .eq("proposalId", proposalId) | ||
| .order("timestamp", { ascending: true }) | ||
| .limit(1); | ||
| if (error) { | ||
| console.error("Error fetching proposal space registration:", error); | ||
| return null; | ||
| } | ||
| return data && data.length > 0 ? data[0] : null; | ||
| } catch (e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same ordering issue for registration details.
Return the most recent row when fetching identityPublicKey.
- .order("timestamp", { ascending: true })
+ .order("timestamp", { ascending: false })
.limit(1);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function loadProposalSpaceRegistration(proposalId: string): Promise<{ | |
| spaceId?: string; | |
| identityPublicKey?: string; | |
| } | null> { | |
| noStore(); | |
| try { | |
| const { data, error } = await createSupabaseServerClient() | |
| .from("spaceRegistrations") | |
| .select("spaceId, identityPublicKey") | |
| .eq("proposalId", proposalId) | |
| .order("timestamp", { ascending: true }) | |
| .limit(1); | |
| if (error) { | |
| console.error("Error fetching proposal space registration:", error); | |
| return null; | |
| } | |
| return data && data.length > 0 ? data[0] : null; | |
| } catch (e) { | |
| const { data, error } = await createSupabaseServerClient() | |
| .from("spaceRegistrations") | |
| .select("spaceId, identityPublicKey") | |
| .eq("proposalId", proposalId) | |
| .order("timestamp", { ascending: false }) | |
| .limit(1); |
🤖 Prompt for AI Agents
In src/app/(spaces)/p/[proposalId]/utils.ts around lines 224 to 241 the query
fetching spaceRegistrations uses ascending timestamp order and returns the first
row, which returns the oldest registration; change the ordering to descending
(most recent first) so the .limit(1) returns the latest row (i.e., order by
timestamp descending) and keep the rest of the error handling and return logic
unchanged.
| const spaceDataWithClientSideLogic = useMemo(() => ({ | ||
| ...spaceData, | ||
| spacePageUrl: (tabName: string) => `/s/${spaceData.spaceName}/${encodeURIComponent(tabName)}`, | ||
| isEditable: (currentUserFid: number | undefined) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use canonical handle and encode both URL segments.
Don’t build routes from display names; encode both segments and prefer a canonical handle field returned by the loader/type.
Apply:
- const spaceDataWithClientSideLogic = useMemo(() => ({
- ...spaceData,
- spacePageUrl: (tabName: string) => `/s/${spaceData.spaceName}/${encodeURIComponent(tabName)}`,
+ const spaceDataWithClientSideLogic = useMemo(() => ({
+ ...spaceData,
+ spacePageUrl: (tabName: string) =>
+ `/s/${encodeURIComponent((spaceData as any).handle ?? spaceData.spaceName)}/${encodeURIComponent(tabName)}`,If available, add handle: string to ProfileSpacePageData and use it explicitly:
- `/s/${encodeURIComponent((spaceData as any).handle ?? spaceData.spaceName)}/${encodeURIComponent(tabName)}`,
+ `/s/${encodeURIComponent(spaceData.handle)}/${encodeURIComponent(tabName)}`,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const spaceDataWithClientSideLogic = useMemo(() => ({ | |
| ...spaceData, | |
| spacePageUrl: (tabName: string) => `/s/${spaceData.spaceName}/${encodeURIComponent(tabName)}`, | |
| isEditable: (currentUserFid: number | undefined) => | |
| const spaceDataWithClientSideLogic = useMemo(() => ({ | |
| ...spaceData, | |
| spacePageUrl: (tabName: string) => | |
| `/s/${encodeURIComponent((spaceData as any).handle ?? spaceData.spaceName)}/${encodeURIComponent(tabName)}`, | |
| isEditable: (currentUserFid: number | undefined) => |
🤖 Prompt for AI Agents
In src/app/(spaces)/s/[handle]/ProfileSpace.tsx around lines 82 to 85, the route
builder currently uses spaceData.spaceName (a display name) and only encodes the
tab segment; change it to use a canonical handle property from
ProfileSpacePageData (e.g., spaceData.handle) and encode both URL segments with
encodeURIComponent when composing spacePageUrl, and if the handle field is not
present add it to the ProfileSpacePageData type returned by the loader so the
component can rely on the canonical handle instead of the display name.
| @@ -1,9 +1,9 @@ | |||
| import React, { ReactNode } from "react"; | |||
| import React, { ReactNode, useRef, useEffect } from "react"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add "use client" directive (hooks used in this file).
This component uses hooks; without "use client" Next.js will error.
Apply:
+\"use client\";
+
import React, { ReactNode, useRef, useEffect } from "react";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import React, { ReactNode, useRef, useEffect } from "react"; | |
| "use client"; | |
| import React, { ReactNode, useRef, useEffect } from "react"; |
🤖 Prompt for AI Agents
In src/app/(spaces)/SpacePage.tsx around lines 1 to 1, this file uses React
hooks but is missing the Next.js "use client" directive; add a top-of-file line
containing exactly "use client" (without quotes) as the first non-whitespace
content before any imports so the component is treated as a client component and
hooks work correctly.
| // Watch for config becoming defined and resolve the deferred | ||
| useEffect(() => { | ||
| if (config && deferredRef.current) { | ||
| deferredRef.current.resolve(); | ||
| } | ||
| }, [config]); | ||
|
|
||
| // If config is undefined, throw the stable promise to trigger Suspense fallback | ||
| if (!config) { | ||
| throw deferredRef.current.promise; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Reset the deferred after resolve and verify a Suspense boundary exists.
- Once resolved, the same thenable is reused and won’t suspend if config becomes undefined again; reset the ref to allow re-suspension.
- Throwing thenables requires a Suspense boundary higher up; otherwise this will error at runtime.
Apply:
useEffect(() => {
if (config && deferredRef.current) {
- deferredRef.current.resolve();
+ deferredRef.current.resolve();
+ deferredRef.current = null;
}
}, [config]);Run to confirm Suspense boundaries wrap SpacePage usage:
🏁 Script executed:
#!/bin/bash
# Find SpacePage imports/usages and nearby Suspense wrappers
rg -nP -C3 'import\s+SpacePage|<SpacePage\b|SpacePage\(' --type=ts --type=tsx
rg -nP -C3 '<Suspense\b|React\.Suspense' --type=ts --type=tsxLength of output: 55
🏁 Script executed:
#!/bin/bash
# Locate SpacePage imports/usages in TS/TSX files
rg -nP -C3 'import\s+SpacePage|<SpacePage\b' --glob '*.ts' --glob '*.tsx'
# Locate Suspense wrappers in TS/TSX files
rg -nP -C3 '<Suspense\b|React\.Suspense' --glob '*.ts' --glob '*.tsx'Length of output: 8554
🏁 Script executed:
#!/bin/bash
# Inspect deferredRef and createDeferred usage in SpacePage.tsx
sed -n '1,200p' 'src/app/(spaces)/SpacePage.tsx' | sed -n '1,200p'
rg -nP 'deferredRef' -C3 --glob 'src/app/(spaces)/SpacePage.tsx'
rg -nP 'createDeferred' -C3 --glob '*.ts' --glob '*.tsx'Length of output: 3916
Reset deferredRef on resolve and ensure Suspense wraps SpacePage
- After resolving
deferredRef.current, clear it so future renders can suspend again:useEffect(() => { if (config && deferredRef.current) { - deferredRef.current.resolve(); + deferredRef.current.resolve(); + deferredRef.current = null; } }, [config]); - Verify every
<SpacePage>usage (e.g. in PublicSpace.tsx, PrivateSpace.tsx, home/[tabname]/page.tsx) is wrapped in a<Suspense>boundary to catch the thrown promise.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Watch for config becoming defined and resolve the deferred | |
| useEffect(() => { | |
| if (config && deferredRef.current) { | |
| deferredRef.current.resolve(); | |
| } | |
| }, [config]); | |
| // If config is undefined, throw the stable promise to trigger Suspense fallback | |
| if (!config) { | |
| throw deferredRef.current.promise; | |
| } | |
| // Watch for config becoming defined and resolve the deferred | |
| useEffect(() => { | |
| if (config && deferredRef.current) { | |
| deferredRef.current.resolve(); | |
| deferredRef.current = null; | |
| } | |
| }, [config]); | |
| // If config is undefined, throw the stable promise to trigger Suspense fallback | |
| if (!config) { | |
| throw deferredRef.current.promise; | |
| } |
🤖 Prompt for AI Agents
In src/app/(spaces)/SpacePage.tsx around lines 46 to 56, after calling
deferredRef.current.resolve() clear/reset deferredRef.current (e.g., set it to
null/undefined or create a new deferred instance) so future renders can suspend
again; then ensure every usage of <SpacePage> (for example PublicSpace.tsx,
PrivateSpace.tsx, and home/[tabname]/page.tsx) is wrapped in a React <Suspense>
boundary to catch the thrown promise — update those files to add/verify Suspense
wrappers if missing.
| const { data, error } = await query | ||
| .order("timestamp", { ascending: true }) | ||
| .limit(1); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fetch newest registration, not oldest.
Use descending order to get the latest registration row.
- const { data, error } = await query
- .order("timestamp", { ascending: true })
- .limit(1);
+ const { data, error } = await query
+ .order("timestamp", { ascending: false })
+ .limit(1);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { data, error } = await query | |
| .order("timestamp", { ascending: true }) | |
| .limit(1); | |
| const { data, error } = await query | |
| .order("timestamp", { ascending: false }) | |
| .limit(1); |
🤖 Prompt for AI Agents
In src/app/(spaces)/t/[network]/[contractAddress]/utils.ts around lines 64 to
67, the query orders by "timestamp" ascending which returns the oldest
registration; change the ordering to descending so the query returns the newest
registration row instead (i.e., use order("timestamp", { ascending: false })),
keeping the limit(1) to fetch only the latest record.
| import createIntialProfileSpaceConfigForFid from "@/constants/initialProfileSpace"; | ||
| import { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Rename and fix import for initial profile space config.
Avoid propagating the “Intial” typo; ensure the export name matches the source.
-import createIntialProfileSpaceConfigForFid from "@/constants/initialProfileSpace";
+import { createInitialProfileSpaceConfigForFid } from "@/constants/initialProfileSpace";
@@
- await get().space.createSpaceTab(
+ await get().space.createSpaceTab(
newSpaceId,
"Profile",
- createIntialProfileSpaceConfigForFid(fid),
+ createInitialProfileSpaceConfigForFid(fid),
);Run this to verify the exported symbol name:
Also applies to: 821-826
🏁 Script executed:
#!/bin/bash
rg -nP --type=ts 'export (?:const|function)\s+createInitialProfileSpaceConfigForFid\b|export default\s+createInitialProfileSpaceConfigForFid\b|export (?:const|function)\s+createIntialProfileSpaceConfigForFid\b'Length of output: 0
🏁 Script executed:
#!/bin/bash
# Locate the initialProfileSpace file(s) and list their exports
files=$(fd -t f initialProfileSpace\.*)
echo "Found files:"
printf " %s\n" $files
for f in $files; do
echo "=== $f ==="
rg -nP --type=ts 'export ' "$f"
doneLength of output: 152
Rename export and fix imports for initial profile space config
- In
src/constants/initialProfileSpace.ts, rename the function and its default export fromcreateIntialProfileSpaceConfigForFidtocreateInitialProfileSpaceConfigForFid. - In
src/common/data/stores/app/space/spaceStore.ts, update the import and call site to use the corrected name.
🤖 Prompt for AI Agents
In src/common/data/stores/app/space/spaceStore.ts around lines 10-11, the
imported function name createIntialProfileSpaceConfigForFid is misspelled;
rename the function and default export in src/constants/initialProfileSpace.ts
from createIntialProfileSpaceConfigForFid to
createInitialProfileSpaceConfigForFid (fix the "Intial" → "Initial" typo) and
then update the import statement and any call sites in
src/common/data/stores/app/space/spaceStore.ts to use
createInitialProfileSpaceConfigForFid.
Summary
Testing
npm run lint(fails: next not found)npm run check-types(fails: missing type definitions)https://chatgpt.com/codex/tasks/task_e_6875387fa9d08325b9834301ea94a856
Summary by CodeRabbit
New Features
Improvements
API
Documentation