Skip to content

Conversation

@willyogo
Copy link
Member

@willyogo willyogo commented Dec 18, 2025

Summary

Updating community_configs fields and mapping additional fields to elements:

  • remove unused community_config fields and align brand/ui schema with new font and governance options
  • switch token gating to use configured ERC20/NFT tokens with fallback legacy contract addresses and shared gate token utilities
  • apply nav/tab font color and font URL settings across navigation surfaces and update seeds/docs accordingly

Implemented domain->config routing:

  • return the matched community/config pairing when resolving middleware lookups
  • prefer exact domain matches when resolving community IDs to support domains stored in community_id
  • fetch community configs using ordered candidate IDs and cache results consistently

Add endpoint for registering community_configs:

  • added a public POST /api/community-config route to parse incoming community configuration payloads and upsert them into the community_configs table
  • parses and normalizes config file from launchpad and creates a new row in community_configs table
  • extends Supabase database typings to cover the community_configs table
  • to-do: add authorization (currently it's public) and implement the API call and auth in blank.space launchpad

Testing

  • npm run lint

Codex Task

Summary by CodeRabbit

  • New Features

    • Configurable token-gating (ERC20 + multiple NFT rules) across UI; per-community theming including custom fonts, font colors and cast button font color
    • New community config API to create/update brand, UI and token settings
  • Bug Fixes

    • Simplified and more consistent domain detection in request handling; middleware now surfaces detected domain header
  • Documentation

    • Architecture overview expanded with mini-app tags and governance identifiers; architecture simplification guidance added

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
nounspace-ts Ready Ready Preview, Comment Dec 29, 2025 10:52pm
space-system Ready Ready Preview, Comment Dec 29, 2025 10:52pm
space-system (blankspace-testing) Ready Ready Preview, Comment Dec 29, 2025 10:52pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Warning

Rate limit exceeded

@j-paterson has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 57 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 002f19e and 6a6a45a.

📒 Files selected for processing (4)
  • middleware.ts
  • src/app/layout.tsx
  • src/config/index.ts
  • src/config/loaders/registry.ts
📝 Walkthrough

Walkthrough

Adds domain normalization and cached SystemConfig loading, a POST /api/community-config route, token-gating utilities/hooks (ERC20 + NFT), font/color theming support, runtime metadata/frame fetching, and updates components to use token-gates and new theming fields.

Changes

Cohort / File(s) Summary
Domain & registry
middleware.ts, src/config/loaders/registry.ts, src/config/loaders/index.ts, src/config/loaders/utils.ts
Introduce normalizeDomain, domain→community resolution (getCommunityConfigForDomain), in-memory SystemConfig cache with TTL, loadSystemConfigById, and change middleware to set x-detected-domain only; remove old utils exports.
Config loaders & runtime
src/config/index.ts, src/config/loaders/runtimeLoader.ts
Rework load priority (explicit id → domain → dev → default), surface token-related types, remove singleton runtime loader pattern, and delegate runtime loader to loadSystemConfigById.
Community config API & seed/data
src/app/api/community-config/route.ts, scripts/seed.ts, supabase/seed.sql
Add POST /api/community-config upsert route; update seed/SQL payloads (community_id changes) and introduce UI theming fields (font URL, fontColor, castButtonFontColor, backgroundColor).
System shape & brands
src/config/systemConfig.ts, src/config/nouns/*.brand.ts, src/config/clanker/*.brand.ts, src/config/example/*.brand.ts
Remove name/tagline from brands; extend UIConfig with url, fontColor, castButtonFontColor, backgroundColor; add optional token name fields; remove contracts type.
Community files
src/config/nouns/*.community.ts, src/config/clanker/*.community.ts, src/config/example/*.community.ts
Remove social/url fields (twitter, github, forum, discord), simplify governance to optional/empty, remove contracts blocks, and simplify token address representations.
Token-gates core
src/common/lib/utils/tokenGates.ts, src/common/constants/gates.ts
New token-gates utilities to extract ERC20/NFT tokens, map networks/chains, format balances; add MIN_SPACE_TOKENS_FOR_UNLOCK = 1111.
Token-gate hook
src/common/lib/hooks/useTokenGate.ts
New useTokenGate(systemConfig?) hook combining ERC20 balance (wagmi) and nogs/NFT checks; returns token, balance, gating booleans, wallet.
Contract address constants
src/constants/nogs.ts, src/constants/spaceToken.ts
Replace lazy/config-driven address resolution with deterministic env-driven defaults and simplified validated getters.
Components: gating & generation
src/common/components/organisms/NogsGateButton.tsx, src/common/components/organisms/NogsChecker.tsx, src/common/lib/theme/BackgroundGenerator.tsx, src/fidgets/farcaster/components/CreateCast.tsx
Replace direct on-chain/Privy balance checks with useTokenGate; add/propagate optional systemConfig prop where required; update copy to use configurable gate rules.
UI theming & fonts
src/common/lib/hooks/useUIColors.ts, src/common/lib/utils/fontUtils.ts, src/app/layout.tsx
Add extractFontFamilyFromUrl, derive/inject validated stylesheet URL, expose fontColor, castButtonFontColor, fontFamily, fontUrl, and inject CSS vars/inline styles.
Navigation & header styling
src/common/components/organisms/Navigation.tsx, src/common/components/organisms/MobileNavbar.tsx, src/common/components/organisms/MobileHeader.tsx, src/common/components/atoms/reorderable-tab.tsx
Replace color props with fontColor/fontFamily, consume useUIColors, apply CSS-variable-driven styling, pass systemConfig into CreateCast, adjust hover/text color behavior.
Metadata & frames (async)
src/constants/metadata.ts, src/app/(spaces)/*/layout.tsx
Replace static defaultFrame/metadata with async getDefaultFrame() usage and buildDefaultMetadata() helpers; use displayName/description for titles.
Terms & copy
src/app/terms/page.tsx, src/common/components/organisms/NogsChecker.tsx
Replace static nogs/$SPACE copy with configurable rule: one eligible NFT or ≥ MIN_SPACE_TOKENS_FOR_UNLOCK ERC20 units, with legacy fallback note.
Misc docs & architecture
docs/SYSTEMS/CONFIGURATION/ARCHITECTURE_OVERVIEW.md, ARCHITECTURE_SIMPLIFICATION.md
Document architecture simplification, unified SystemConfig transformation and caching, and note updated config fields (mini-app tags, governance identifiers, font metadata).
Misc small additions
src/app/api/test-logs/route.ts
Add GET test-logs API that emits server logs and returns a timestamped JSON response.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Middleware as Middleware
    participant Registry as Registry
    participant Cache as SystemConfigCache
    participant DB as Supabase

    Browser->>Middleware: HTTP request (Host header)
    Middleware->>Middleware: normalizeDomain(host)
    Middleware->>Cache: lookup(normalizedDomain)
    alt cache hit
        Cache-->>Middleware: cached config
    else cache miss
        Middleware->>Registry: getCommunityConfigForDomain(normalizedDomain)
        Registry->>DB: query community_configs (by communityId)
        DB-->>Registry: row
        Registry->>Registry: transformRowToSystemConfig(row)
        Registry->>Cache: write(normalizedDomain, SystemConfig)
        Registry-->>Middleware: { communityId, config }
    end
    Middleware->>Browser: Respond (sets x-detected-domain header)
Loading
sequenceDiagram
    participant Component
    participant Hook as useTokenGate
    participant Privy as Privy
    participant Wagmi as Wagmi/useBalance
    participant Alchemy as Alchemy NFT API
    participant Store as AppStore

    Component->>Hook: call useTokenGate(systemConfig)
    Hook->>Privy: request wallet address
    alt wallet connected
        Hook->>Wagmi: fetch ERC20 balance for configured token
        Wagmi-->>Hook: balance
        par NFT checks
            Hook->>Alchemy: query NFT holders for token A
            Hook->>Alchemy: query NFT holders for token B
        and
            Alchemy-->>Hook: holdings
        end
        Hook->>Store: read hasNogs
        Hook-->>Component: { hasEnoughTokens, hasNogs, gatingSatisfied, balanceAmount }
    else no wallet
        Hook-->>Component: defaults (not satisfied)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hop through domains and cached delight,
Fonts unfurling pixels bright.
Tokens checked, the gates align,
Configs upserted, frames in time.
Cheers — a rabbit’s tiny code-flight 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.04% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the four main changes: updating community_config fields, mapping fields to UI elements, implementing domain→config routing, and adding a registration endpoint.

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.

❤️ Share

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

@willyogo willyogo changed the title Refine community config and token gating Update community_configs fields, map additional fields to elements, implement domain->config routing, and add endpoint for registering community_configs Dec 19, 2025
@willyogo willyogo added the LGFTP Looks Good From Testing Perspective label Dec 19, 2025
@willyogo willyogo marked this pull request as ready for review December 19, 2025 21:39
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (2)
src/app/terms/page.tsx (1)

99-117: Duplicate "Contact and Support" content.

The "Contact and Support" section is duplicated (lines 99-108 and lines 110-117 contain identical content). This appears to be an unintentional copy-paste.

🔎 Proposed fix: Remove duplicate section
 * **Email/Other:** If an official support email or contact form is provided on the nounspace website, you can use that as well. (At the time of writing these Terms, the primary support channels are Discord and GitHub, as listed above.)

-
-Nounspace is a community-driven project, and we value our users. If you have any questions, concerns, or feedback about these Terms or the platform in general, please reach out to us. The best ways to contact us include:
-
-* **Discord:** We have an official nounspace Discord community (chat server) where the team and community members are active. Feel free to join and ask questions or raise concerns.
-
-* **GitHub:** Since nounspace is open source, you can also open an issue on our GitHub repository if you encounter bugs or have questions about the code. (For general Terms or account issues, Discord or email might get a faster response, but we do monitor GitHub issues as well.)
-
-* **Email/Other:** If an official support email or contact form is provided on the nounspace website, you can use that as well. (At the time of writing these Terms, the primary support channels are Discord and GitHub, as listed above.)
-

 We are here to help and to ensure that your experience on nounspace is positive.
src/common/components/organisms/NogsGateButton.tsx (1)

164-198: Stale closure bug: nftTokens missing from dependency array.

The checkForNogs callback calls isHoldingNogs, which reads from nftTokens state. However, nftTokens is not in the dependency array, so if nftTokens updates after the initial render, checkForNogs will still reference the old isHoldingNogs closure that sees empty/stale nftTokens.

🔎 Proposed fix
   }, [
     user,
     nogsTimeoutTimer,
     nogsRecheckCountDownTimer,
     nogsRecheckTimerLength,
     setNogsRecheckCountDown,
     setNogsRecheckTimerLength,
     setNogsShouldRecheck,
     setNogsTimeoutTimer,
     setHasNogs,
+    nftTokens,
   ]);
🧹 Nitpick comments (7)
supabase/seed.sql (1)

165-180: Minor: Inconsistent color format across communities.

Clanker uses rgba() format while other communities use rgb(). Both are valid CSS, but for consistency, consider standardizing on one format across all community configs.

src/app/api/community-config/route.ts (1)

64-82: Consider validating payload structure before database insertion.

The config fields are cast directly to database types without schema validation. Malformed data could cause runtime issues or corrupt the database. Consider using a schema validation library (e.g., Zod) to validate the structure of brandConfig, assetsConfig, etc.

🔎 Example with Zod validation
import { z } from 'zod';

const BrandConfigSchema = z.object({
  displayName: z.string(),
  description: z.string(),
  miniAppTags: z.array(z.string()),
});

// Validate before insertion
const brandConfigResult = BrandConfigSchema.safeParse(brandConfig);
if (!brandConfigResult.success) {
  return NextResponse.json(
    { success: false, error: 'Invalid brand_config structure', details: brandConfigResult.error },
    { status: 400 }
  );
}
src/fidgets/farcaster/components/CreateCast.tsx (1)

370-370: Dependency array references a mutable ref .current value.

Using editorContentRef.current in the dependency array is problematic—React won't detect changes to .current. The effect runs only on mount/unmount because the ref object itself doesn't change, which may be intentional here, but the dependency is misleading.

🔎 Proposed fix
     return () => {
       el.removeEventListener("paste", handler as any);
     };
-  }, [editorContentRef.current]);
+  }, []); // Effect depends on ref availability, not its current value
src/constants/spaceToken.ts (1)

9-18: Async function performs only synchronous operations.

getSpaceContractAddr() is marked async but only validates and returns a constant. If the async signature is for future-proofing (e.g., dynamic config loading), this is acceptable. Otherwise, consider making it synchronous to avoid unnecessary Promise wrapping.

If async is not required for future use
-export async function getSpaceContractAddr(): Promise<Address> {
+export function getSpaceContractAddr(): Address {
   if (!isAddress(DEFAULT_SPACE_CONTRACT_ADDR)) {
     throw new Error(
       "Invalid default SPACE contract address configured. Expected a checksummed 0x-prefixed address.",
     );
   }
 
   return DEFAULT_SPACE_CONTRACT_ADDR;
 }
src/common/components/organisms/NogsGateButton.tsx (2)

137-148: Avoid as any type assertion.

The token.network as any bypasses type safety. Since nftTokens state is typed as { address: string; network?: CommunityTokenNetwork }[], the network should already be compatible with mapNetworkToAlchemy.

🔎 Proposed fix
-        const alchemyNetwork = mapNetworkToAlchemy(token.network as any);
+        const alchemyNetwork = mapNetworkToAlchemy(token.network);

240-253: Parallel NFT checks could hit rate limits.

With multiple NFT tokens, Promise.all fires all Alchemy API requests simultaneously. If the number of configured NFT tokens grows, this could trigger rate limiting.

Consider adding a sequential check with early exit (similar to isHoldingNogs), or limit concurrency for larger token lists.

src/config/loaders/registry.ts (1)

55-58: Consider consolidating Vercel preview handling into DOMAIN_TO_COMMUNITY_MAP.

The inline check for .vercel.app deployments containing 'nounspace' introduces a special case that could be harder to discover. Moving this logic to the explicit mapping at the top of the file would improve discoverability.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f7b079 and 4da70e5.

📒 Files selected for processing (31)
  • docs/SYSTEMS/CONFIGURATION/ARCHITECTURE_OVERVIEW.md (1 hunks)
  • middleware.ts (1 hunks)
  • scripts/seed.ts (6 hunks)
  • src/app/api/community-config/route.ts (1 hunks)
  • src/app/layout.tsx (4 hunks)
  • src/app/terms/page.tsx (1 hunks)
  • src/common/components/atoms/reorderable-tab.tsx (2 hunks)
  • src/common/components/organisms/MobileHeader.tsx (4 hunks)
  • src/common/components/organisms/MobileNavbar.tsx (4 hunks)
  • src/common/components/organisms/Navigation.tsx (7 hunks)
  • src/common/components/organisms/NogsChecker.tsx (1 hunks)
  • src/common/components/organisms/NogsGateButton.tsx (4 hunks)
  • src/common/lib/hooks/useUIColors.ts (2 hunks)
  • src/common/lib/theme/BackgroundGenerator.tsx (2 hunks)
  • src/common/lib/utils/fontUtils.ts (1 hunks)
  • src/common/lib/utils/tokenGates.ts (1 hunks)
  • src/config/clanker/clanker.brand.ts (0 hunks)
  • src/config/clanker/clanker.community.ts (1 hunks)
  • src/config/example/example.brand.ts (0 hunks)
  • src/config/example/example.community.ts (1 hunks)
  • src/config/index.ts (2 hunks)
  • src/config/loaders/registry.ts (3 hunks)
  • src/config/nouns/nouns.brand.ts (0 hunks)
  • src/config/nouns/nouns.community.ts (1 hunks)
  • src/config/systemConfig.ts (3 hunks)
  • src/constants/metadata.ts (3 hunks)
  • src/constants/nogs.ts (1 hunks)
  • src/constants/spaceToken.ts (1 hunks)
  • src/fidgets/farcaster/components/CreateCast.tsx (3 hunks)
  • src/supabase/database.d.ts (1 hunks)
  • supabase/seed.sql (5 hunks)
💤 Files with no reviewable changes (3)
  • src/config/nouns/nouns.brand.ts
  • src/config/example/example.brand.ts
  • src/config/clanker/clanker.brand.ts
🧰 Additional context used
🧬 Code graph analysis (12)
src/app/api/community-config/route.ts (2)
src/common/data/database/supabase/clients/server.ts (1)
  • createSupabaseServerClient (7-7)
src/supabase/database.d.ts (1)
  • Database (11-1004)
src/common/lib/utils/tokenGates.ts (4)
src/config/systemConfig.ts (3)
  • CommunityErc20Token (6-12)
  • CommunityNftToken (14-20)
  • CommunityTokenNetwork (4-4)
src/config/index.ts (1)
  • loadSystemConfig (37-71)
src/constants/spaceToken.ts (1)
  • getSpaceContractAddr (10-18)
src/constants/nogs.ts (1)
  • getNogsContractAddr (7-9)
src/fidgets/farcaster/components/CreateCast.tsx (3)
src/config/index.ts (1)
  • CommunityTokenNetwork (78-78)
src/config/systemConfig.ts (1)
  • CommunityTokenNetwork (4-4)
src/common/lib/utils/tokenGates.ts (2)
  • getGateTokens (12-48)
  • getChainForNetwork (50-55)
src/common/lib/hooks/useUIColors.ts (1)
src/common/lib/utils/fontUtils.ts (1)
  • extractFontFamilyFromUrl (1-11)
src/common/components/organisms/Navigation.tsx (1)
src/common/lib/hooks/useUIColors.ts (1)
  • useUIColors (15-49)
middleware.ts (1)
src/config/loaders/registry.ts (2)
  • normalizeDomain (86-93)
  • resolveCommunityConfig (205-221)
src/config/systemConfig.ts (1)
src/config/index.ts (3)
  • CommunityTokenNetwork (78-78)
  • CommunityNftToken (77-77)
  • CommunityTokensConfig (79-79)
src/common/lib/theme/BackgroundGenerator.tsx (3)
src/config/index.ts (1)
  • CommunityTokenNetwork (78-78)
src/config/systemConfig.ts (1)
  • CommunityTokenNetwork (4-4)
src/common/lib/utils/tokenGates.ts (2)
  • getGateTokens (12-48)
  • getChainForNetwork (50-55)
src/common/components/organisms/NogsGateButton.tsx (4)
src/config/systemConfig.ts (1)
  • CommunityTokenNetwork (4-4)
src/common/lib/utils/tokenGates.ts (3)
  • getGateTokens (12-48)
  • getChainForNetwork (50-55)
  • mapNetworkToAlchemy (57-62)
src/pages/api/signerRequests.ts (1)
  • AlchemyIsHolderOfContract (70-72)
src/constants/urls.ts (1)
  • ALCHEMY_API (7-19)
src/app/layout.tsx (1)
src/common/lib/utils/fontUtils.ts (1)
  • extractFontFamilyFromUrl (1-11)
src/config/loaders/registry.ts (2)
src/supabase/database.d.ts (1)
  • Database (11-1004)
src/common/data/database/supabase/clients/server.ts (1)
  • createSupabaseServerClient (7-7)
src/constants/metadata.ts (2)
middleware.ts (1)
  • config (43-55)
src/constants/app.ts (1)
  • WEBSITE_URL (1-6)
🔇 Additional comments (43)
src/config/nouns/nouns.community.ts (3)

17-34: Token addresses verified and correct.

Both addresses have been confirmed against official blockchain explorers:

  • Nouns NFT (0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03) is the official contract on Ethereum mainnet
  • SPACE token (0x48c6740bcf807d6c47c864faeea15ed4da3910ab) is correctly deployed on Base network

No action required.


7-35: Verify removed fields aren't breaking references across the codebase.

Significant fields have been removed from the nouns community configuration:

  • urls.twitter, urls.github, urls.forum
  • social.discord, social.twitter
  • Entire contracts block (previously contained nouns, auctionHouse, space, nogs)

No references to these removed properties exist in the codebase. The only file consuming nounsCommunity (src/config/nouns/nouns.explore.ts) correctly uses the new tokens configuration and social.farcaster, both of which remain intact.


16-16: Empty governance object is intentional and safe—no action required.

The governance: {} configuration is valid. The governance field is optional in the CommunityConfig type, and all properties within it (snapshotSpace and nounishGov) are also optional. No code in the codebase accesses these properties; the NounishGovernance fidget uses its own settings object, not the community config's governance field. This empty object is consistent across all community configurations in the project and poses no safety concerns.

Likely an incorrect or invalid review comment.

docs/SYSTEMS/CONFIGURATION/ARCHITECTURE_OVERVIEW.md (1)

200-206: LGTM!

The documentation table updates accurately reflect the new configuration fields being added to the system: mini-app tags for brand config, governance identifiers and tokens for community config, and font colors/font URL for UI config. These align well with the broader UI theming and token gating changes in this PR.

supabase/seed.sql (2)

98-114: LGTM!

The Nouns community configuration is well-structured with the new UI theming fields (fontColor, castButtonFontColor, url for fonts, backgroundColor) and properly configured token gate data. The nested castButton object provides consistent styling configuration.


137-142: Placeholder token addresses are appropriate for example community.

The example community uses obvious placeholder addresses (0x1234...) which is suitable for test/example configurations. The UI config structure matches the other communities correctly.

src/common/lib/utils/tokenGates.ts (2)

22-48: LGTM!

The fallback mechanism correctly constructs legacy SPACE and NOGS tokens when no community-configured tokens exist. Using Promise.all for concurrent address resolution is efficient, and the conditional NFT token inclusion based on nogsAddress truthiness is clean.


50-55: LGTM!

Clean mapping from internal network identifiers to viem Chain objects with sensible base default.

src/common/lib/utils/fontUtils.ts (1)

1-11: Consider edge case: multiple font families in URL.

Google Fonts allows multiple font families to be requested by separating the names with a pipe character (|), and the CSS2 API supports multiple families via repeated family= parameters. The current implementation only extracts the first family. If the function is intended for single-family URLs only, document this constraint; otherwise, consider handling pipe-separated families or multiple family parameters.

src/app/terms/page.tsx (1)

17-17: The 1,111 ERC20 threshold is correctly implemented in the gating logic. Line 17 of NogsGateButton.tsx defines const MIN_SPACE_TOKENS_FOR_UNLOCK = 1111; and this constant is used in the balance check (lines 92-100) to verify users hold sufficient tokens. The terms documentation accurately reflects the implementation.

src/config/systemConfig.ts (3)

6-12: LGTM! Optional name field added for display purposes.

Adding optional name fields to both CommunityErc20Token and CommunityNftToken provides flexibility for human-readable token identification without breaking existing configurations.

Also applies to: 14-20


37-50: LGTM! UI theming configuration extended.

The optional fields (url, fontColor, castButtonFontColor, backgroundColor) complement the required color properties and enable per-community font/color customization.


103-117: Optional interface properties require consumers to use defensive access patterns.

While making social, governance, and tokens optional aligns the interface with the actual runtime values in provided configs, ensure all consuming code handles potential undefined values. TypeScript will enforce type safety at compile time if strictNullChecks is enabled, but verify that configuration access patterns use optional chaining (?.) where needed.

src/common/components/atoms/reorderable-tab.tsx (1)

65-72: LGTM! Consistent theming via useUIColors.

Applying fontColor and fontFamily from the centralized UI colors hook ensures consistent styling across navigation surfaces and aligns with the broader theming approach in this PR.

src/config/index.ts (2)

1-7: LGTM! Clean type exports.

Importing SystemConfig as a value (for runtime use) alongside type-only imports for token configuration types is correct. This enables proper tree-shaking while exposing the necessary types.


73-80: LGTM! Public API expanded for token types.

Exporting the token-related types (CommunityErc20Token, CommunityNftToken, CommunityTokenNetwork, CommunityTokensConfig) enables consumers to properly type their token configurations.

src/common/components/organisms/MobileHeader.tsx (2)

137-144: LGTM! Consistent button theming applied.

The cast button properly uses castButtonFontColor for both the button color and icon, with fontFamily for consistent typography. The hover/active state handlers maintain the interactive feedback.

Also applies to: 148-173


175-201: LGTM! Login button styled with primary colors.

The login button appropriately uses primaryColor with corresponding hover/active states, maintaining visual consistency with the theming system.

src/constants/metadata.ts (1)

36-54: LGTM! Metadata fields aligned with updated BrandConfig.

The migration from brand.name/brand.tagline to brand.displayName/brand.description correctly reflects the simplified BrandConfig interface.

src/fidgets/farcaster/components/CreateCast.tsx (2)

65-66: LGTM!

The imports align correctly with the new token gate utilities. The type import for CommunityTokenNetwork is properly scoped.


396-409: Approve the balance query refactor with a note on edge case behavior.

The logic correctly uses the loaded erc20Token for balance queries. When erc20Token is null:

  • token defaults to zeroAddress, which queries native token balance (not ERC-20).
  • query.enabled is false, so the query won't run until token is loaded.

This is acceptable since enabled: !!erc20Token prevents the query from executing prematurely. The decimals fallback chain (result.data.decimals ?? erc20Token?.decimals ?? 18) is robust.

src/app/layout.tsx (3)

11-11: LGTM!

The font utility import and font stack construction are well-implemented with proper fallbacks to system fonts.

Also applies to: 124-128


139-147: LGTM!

The CSS variable injection via inline styles is safe—React escapes values appropriately. The type assertion pattern ["--ns-nav-font" as string] is a reasonable workaround for TypeScript's CSSProperties type limitations.


58-65: LGTM!

The metadata updates correctly use displayName for user-facing text and properly populate the Farcaster frame action fields.

middleware.ts (1)

14-24: LGTM on the async refactor!

The middleware correctly awaits the async community resolution. The normalizeDomain call before resolveCommunityConfig ensures consistent domain handling. The relevant code snippet shows that resolveCommunityConfig internally calls normalizeDomain again, but this is harmless (idempotent).

src/config/example/example.community.ts (1)

7-35: LGTM!

The example community config cleanup is appropriate. The simplified structure with an empty governance object and retained tokens block aligns with the PR's goal of removing unused fields. The satisfies CommunityConfig assertion ensures type compatibility.

src/constants/nogs.ts (1)

1-13: LGTM!

The refactor to environment-driven defaults is clean and maintains backward compatibility:

  • Environment variable priority chain is correct (NEXT_PUBLIC_ for client, then server-side, then hardcoded fallback).
  • The async function signature is preserved for API compatibility even though it now returns immediately.
  • The Promise.resolve() wrapper for NOGS_CONTRACT_ADDR maintains the Promise type for existing consumers.
src/common/components/organisms/MobileNavbar.tsx (2)

34-35: Clean refactor to CSS variable-based theming.

The switch from activeColor/inactiveColor to fontColor/fontFamily with opacity-based selection state (1 vs 0.7) simplifies the styling logic while maintaining visual distinction between selected and unselected tabs. The inline style approach correctly overrides the Tailwind classes.

Also applies to: 43-44, 78-86


312-313: CSS variable defaults with proper fallback chain.

The fallback chain var(--ns-nav-font-color, #0f172a) and the font family fallback var(--ns-nav-font, var(--font-sans, Inter, system-ui, -apple-system, sans-serif)) ensure graceful degradation when CSS variables are not defined.

src/common/lib/theme/BackgroundGenerator.tsx (2)

59-77: Verify behavior when no ERC20 tokens are configured.

When tokens.erc20Tokens is empty, erc20Token remains null, the balance query is disabled (enabled: !!erc20Token), and spaceLoading will be false after initial load. This means userHoldEnoughSpace defaults to false and the banner will show if !hasNogs.

Confirm this is the intended behavior—if no tokens are configured, users without NOGS cannot generate backgrounds. If the feature should be unrestricted when no gating token is configured, consider adding a fallback or feature flag.


72-85: Balance query and formatting logic looks correct.

The query correctly waits for erc20Token to be loaded before enabling, and the decimal fallback chain (result.data.decimals ?? erc20Token?.decimals ?? 18) provides sensible defaults. The getChainForNetwork utility properly maps the network to a chain ID.

src/common/components/organisms/Navigation.tsx (2)

101-101: Clean integration of UI colors hook.

The useUIColors hook provides centralized color/font values, and the navTextStyle object efficiently bundles them for reuse across multiple elements. The cast button colors correctly derive from uiColors.castButton with a separate castButtonFontColor for text contrast.

Also applies to: 110-119


313-313: Consistent navTextStyle application across navigation elements.

Applying navTextStyle to NavItem, NavButton, and the nav container ensures consistent font styling throughout the navigation. This is a well-structured approach.

Also applies to: 342-342, 372-372

src/common/lib/hooks/useUIColors.ts (2)

22-28: CSS variable values are cached and won't update if variables change.

The useMemo dependency is [systemConfig], so CSS variable values read via getComputedStyle are captured once and won't recompute if the CSS variables change at runtime (e.g., dynamic theme switching). If CSS variables are set once on page load based on the community config, this is acceptable. Otherwise, consider adding a mechanism to trigger recomputation.


30-30: Font family derivation with proper fallback chain.

The logic correctly prioritizes: parsed font family from URL → CSS variable → default system fonts. The fallback chain ensures graceful degradation when font configuration is incomplete.

Also applies to: 35-37

scripts/seed.ts (2)

344-357: UI config enrichment aligns with theming system.

The new ui_config fields (fontColor, castButtonFontColor, url, backgroundColor) correctly populate the values expected by useUIColors. The consistent use of Inter font and matching color schemes across communities ensures a cohesive experience.

Also applies to: 435-448, 553-566


266-266: Empty governance objects reduce config surface.

Setting governance: {} for all communities aligns with the PR objective of simplifying the public config surface. If governance features are needed later, the structure is in place.

Also applies to: 394-394, 493-493

src/constants/spaceToken.ts (1)

20-22: No action needed. SPACE_CONTRACT_ADDR is not used anywhere in the codebase—the search found zero usages outside its definition. The code imports and uses getSpaceContractAddr() function instead. The constant exists only as a legacy export for backward compatibility with no actual callers that need updating.

Likely an incorrect or invalid review comment.

src/config/clanker/clanker.community.ts (1)

7-28: LGTM!

The governance simplification to an empty object aligns with the PR's objective to remove unused fields. The satisfies assertions ensure type safety is maintained.

src/supabase/database.d.ts (1)

186-224: Schema addition looks correct.

The new community_configs table follows the existing patterns in this file. A few observations:

  1. Using Json for config fields provides flexibility but sacrifices compile-time type safety on the stored data.
  2. The community_id is a required string on insert, which aligns with the domain-based resolution in registry.ts.

Consider adding a comment or documentation indicating what shape is expected for each JSON config field (e.g., brand_config schema) to help future maintainers.

src/common/components/organisms/NogsGateButton.tsx (1)

85-100: LGTM on the ERC20 balance check logic.

The useBalance hook is properly guarded with query.enabled, and the decimals fallback chain is sensible.

src/config/loaders/registry.ts (2)

33-34: In-memory cache implementation is reasonable.

The cache with TTL provides good protection against navigation bursts. Note that in serverless deployments (Vercel Edge, Lambda), the cache won't be shared across instances, but it still provides value for rapid successive requests within the same instance lifecycle.

Also applies to: 118-138


205-221: LGTM on the public API.

The resolveCommunityConfig function provides a clean interface for domain-to-config resolution with appropriate warning logging for unresolved domains.

Comment on lines +18 to +28
export async function POST(request: NextRequest): Promise<Response> {
try {
const body = await request.json();
const incomingConfig: IncomingCommunityConfig | undefined = body?.community_config;

if (!incomingConfig || typeof incomingConfig !== 'object') {
return NextResponse.json(
{ success: false, error: 'Missing community_config payload' },
{ status: 400 }
);
}
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 19, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing authentication and authorization.

This endpoint allows unauthenticated users to create or overwrite any community configuration by specifying a community_id. This is a significant security vulnerability that could allow:

  • Hijacking existing communities by overwriting their configs
  • Creating spam/malicious community entries
  • Data corruption

Add authentication (e.g., verify the request is from an authenticated user) and authorization (e.g., verify the user is an admin of the specified community).

🔎 Suggested approach
export async function POST(request: NextRequest): Promise<Response> {
  try {
    // Add authentication check
    const supabase = createSupabaseServerClient();
    const { data: { user }, error: authError } = await supabase.auth.getUser();
    
    if (authError || !user) {
      return NextResponse.json(
        { success: false, error: 'Authentication required' },
        { status: 401 }
      );
    }

    const body = await request.json();
    const incomingConfig = body?.community_config;
    
    // ... existing validation ...
    
    // Add authorization check - verify user can modify this community
    const { data: existingConfig } = await supabase
      .from('community_configs')
      .select('community_id')
      .eq('community_id', communityId)
      .single();
    
    if (existingConfig) {
      // Verify user has permission to update existing config
      // (implement your authorization logic here)
    }
    
    // ... rest of the handler ...
  } catch (error) {
    // ...
  }
}
🤖 Prompt for AI Agents
In src/app/api/community-config/route.ts around lines 18 to 28 the handler
accepts a community_config payload without any authentication or authorization,
allowing unauthenticated users to create/overwrite community configs; fix by
adding an auth check (e.g., createSupabaseServerClient and call auth.getUser())
and return 401 if no authenticated user, then validate the incoming payload
including community_id, and perform an authorization check against your DB
(e.g., query memberships/communities to confirm the authenticated user is an
admin or has permission to create/modify that community); if the user is not
authorized return 403, otherwise proceed with the existing create/update logic
and surface auth/db errors in the response/logs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Will add auth before deploying to prod

Copy link
Contributor

Choose a reason for hiding this comment

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

@willyogo, thanks for acknowledging this! Since this is a critical security issue, would you like me to create a GitHub issue to track adding authentication and authorization before production deployment? This would help ensure it doesn't get overlooked.

Copy link
Member Author

Choose a reason for hiding this comment

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

sure, that would be great! thanks coderabbit

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create GitHub issue to track authentication requirement

gh issue create \
  --repo blankdotspace/space-system \
  --title "Add authentication and authorization to /api/community-config endpoint" \
  --body "## Description

The POST /api/community-config endpoint currently accepts requests without authentication or authorization checks. This creates a critical security vulnerability that could allow:
- Unauthenticated users to create or overwrite community configurations
- Hijacking existing communities
- Data corruption

## Action Required

Add authentication and authorization checks to the endpoint before deploying to production.

## Context

- Endpoint: \`src/app/api/community-config/route.ts\`
- Identified in PR: https://github.com/blankdotspace/space-system/pull/1625
- Discussion: https://github.com/blankdotspace/space-system/pull/1625#discussion_r2636386868
- Requested by: @willyogo

## Priority

🔴 Critical - Must be resolved before production deployment" \
  --assignee willyogo

Length of output: 57


@willyogo, I've created issue #1633 to track adding authentication and authorization to the /api/community-config endpoint: #1633

The issue has been assigned to you and marked as critical priority. This will help ensure the security measures are in place before production deployment. 🔒

@willyogo
Copy link
Member Author

@j-paterson got this into a good working state!

I can successfully

  • send the config output by blank.space launchpad to the API endpoint to register a new space system (can test by going through the flow on https://blank-space-launchpad.vercel.app/ which will also configure the domain)
  • see that space system's config when I navigate to the domain that matches the community_id (note that until we merge to staging, need to use a custom vercel environment mapped to the domain in order to test)

Also removed some unused config fields and added and mapped some missing fields that the launchpad outputs.

Going to create another branch on top of this to make sure each variable is correctly mapped throughout the app, but would love your review on it and coderabbit's feedback so far (down to run walk through it together too)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/config/loaders/registry.ts (1)

167-170: Caching null on transient errors still present.

This concern was raised in a previous review. When a Supabase error occurs (network timeout, temporary unavailability), all candidates are cached as null for 60 seconds, which can extend outages. Consider not caching on error or using a shorter TTL for error cases.

🧹 Nitpick comments (3)
scripts/seed.ts (1)

344-357: Consider renaming url to fontUrl for clarity.

The url property in ui_config holds a Google Fonts URL, but the generic name could be confusing. Consider renaming it to fontUrl or fontStylesheetUrl to make its purpose self-documenting.

🔎 Suggested naming improvement
       fontColor: 'rgb(15, 23, 42)',
       castButtonFontColor: '#ffffff',
-      url: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap',
+      fontUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap',
       backgroundColor: 'rgb(255, 255, 255)',

This same change would apply to lines 441, 559 for the other community configs.

src/config/index.ts (1)

75-92: Solid fallback strategy, but consider structured error logging.

The fallback to DEFAULT_COMMUNITY_ID prevents runtime crashes when a community config fails to load. However, logging the raw error object may expose stack traces or sensitive details in production logs. Consider logging only error instanceof Error ? error.message : String(error).

🔎 Suggested improvement for error logging
     if (communityId && communityId !== DEFAULT_COMMUNITY_ID) {
       console.warn(
         `Falling back to default community after failed load for "${communityId}".`,
-        error
+        error instanceof Error ? error.message : String(error)
       );
src/config/loaders/registry.ts (1)

197-213: Minor: Domain is normalized twice.

resolveCommunityConfig normalizes the domain on line 200, then passes it to getCommunityConfigForDomain, which calls getCommunityIdCandidates that normalizes again on line 36. This is harmless (idempotent) but slightly redundant.

🔎 Option to avoid double normalization

Either remove normalization from resolveCommunityConfig and let getCommunityIdCandidates handle it, or pass a flag/use an internal variant that skips re-normalization.

 export async function resolveCommunityConfig(
   domain: string
 ): Promise<{ communityId: string; config: CommunityConfigRow } | null> {
-  const normalizedDomain = normalizeDomain(domain);
-  if (!normalizedDomain) {
+  if (!domain?.trim()) {
     return null;
   }

-  const resolution = await getCommunityConfigForDomain(normalizedDomain);
+  const resolution = await getCommunityConfigForDomain(domain);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da70e5 and 64164a9.

📒 Files selected for processing (5)
  • scripts/seed.ts (9 hunks)
  • src/config/index.ts (3 hunks)
  • src/config/loaders/index.ts (1 hunks)
  • src/config/loaders/registry.ts (2 hunks)
  • src/config/loaders/utils.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/config/loaders/utils.ts (2)
src/config/loaders/registry.ts (1)
  • DEFAULT_COMMUNITY_ID (17-17)
src/config/loaders/index.ts (1)
  • getCommunityIdFromHeaders (19-19)
src/config/loaders/registry.ts (2)
src/supabase/database.d.ts (1)
  • Database (11-1004)
src/common/data/database/supabase/clients/server.ts (1)
  • createSupabaseServerClient (7-7)
src/config/index.ts (2)
src/config/loaders/utils.ts (2)
  • getDomainFromContext (70-99)
  • getCommunityIdFromHeaders (47-58)
src/config/loaders/registry.ts (1)
  • DEFAULT_COMMUNITY_ID (17-17)
🔇 Additional comments (9)
scripts/seed.ts (1)

249-266: LGTM!

The community_id change to nounspace.com aligns with the new domain-based routing strategy, and the simplified governance: {} removes unused configuration fields as intended.

src/config/loaders/index.ts (1)

19-19: LGTM!

The new getCommunityIdFromHeaders export properly extends the public API surface to support the middleware-resolved community ID pattern.

src/config/index.ts (1)

46-58: LGTM!

The parallel fetching of domain and middleware-resolved community ID is efficient. The fallback chain (middleware header → domain resolution → default) provides robust community identification.

src/config/loaders/utils.ts (2)

39-58: LGTM!

Well-documented helper that safely reads the middleware-set x-community-id header. The dynamic import pattern and try/catch gracefully handle non-request contexts (static generation), returning undefined consistently with other functions in this module.


30-34: LGTM!

The build-time fallback behavior is now clearly documented and uses DEFAULT_COMMUNITY_ID consistently, ensuring predictable behavior during static generation.

src/config/loaders/registry.ts (4)

79-86: LGTM!

The normalizeDomain function handles common variations robustly: port stripping, lowercasing, and www. prefix removal. The empty string return for invalid input is a safe default.


35-70: LGTM!

The candidate generation logic implements a clear priority order with proper deduplication. The localhost subdomain extraction supports local development testing workflows effectively.


145-190: Efficient batch resolution with proper priority handling.

The single .in() query fetches all candidates in one round-trip, and the iteration preserves the priority order established by getCommunityIdCandidates. The cache updates for both hits and misses reduce subsequent DB calls.


27-33: Cache lacks size bounds but likely acceptable.

The in-memory cache has no maximum size, but since community_id values are limited in practice, unbounded growth is unlikely. In serverless/edge environments, the cache may reset between requests anyway.

Consider if you're running in a long-lived server process with many domains, whether a cache size limit or LRU eviction would be warranted.

@willyogo
Copy link
Member Author

@CodeRabbit full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (1)
src/config/loaders/registry.ts (1)

84-98: Past review comment about runtime validation still applies.

The type assertions as unknown as SystemConfig['...'] bypass compile-time checks. While field presence is validated (lines 223-240, 282-298), there's no validation of the nested JSON structure. If the database contains malformed JSON (e.g., missing nested properties or incorrect types), runtime errors will occur when downstream components access expected properties.

Consider adding runtime validation using a schema validator (e.g., Zod) or document that callers must handle potential structure mismatches.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffe7b9f and b8d56ca.

📒 Files selected for processing (1)
  • src/config/loaders/registry.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: j-paterson
Repo: blankdotspace/space-system PR: 1625
File: src/common/lib/utils/tokenGates.ts:12-20
Timestamp: 2025-12-29T18:25:25.957Z
Learning: Gate tokens in src/common/lib/utils/tokenGates.ts are currently fetched redundantly from 3 client components (CreateCast.tsx, BackgroundGenerator.tsx, NogsGateButton.tsx) with no caching, and should be refactored to use a cached API route with a shared client hook to reduce redundant server calls.
🧬 Code graph analysis (1)
src/config/loaders/registry.ts (5)
src/supabase/database.d.ts (1)
  • Database (11-1010)
src/config/index.ts (1)
  • SystemConfig (89-89)
src/config/shared/themes.ts (1)
  • themes (8-8)
src/config/loaders/index.ts (2)
  • getCommunityConfigForDomain (20-20)
  • loadSystemConfigById (20-20)
src/common/data/database/supabase/clients/server.ts (1)
  • createSupabaseServerClient (7-7)
🔇 Additional comments (8)
src/config/loaders/registry.ts (8)

1-7: LGTM!

The imports and default community ID constant are well-structured and properly exported for use throughout the module.


8-20: LGTM!

The domain mapping configuration is clear and well-documented. The comment correctly notes that Vercel preview deployments are handled separately through pattern matching.


22-38: LGTM!

The cache implementation is well-structured with appropriate types and a reasonable 60-second TTL for configuration data.


67-81: LGTM!

The domain normalization logic is thorough and handles edge cases correctly, including port stripping, case normalization, and www prefix removal.


100-123: LGTM!

The cache helpers correctly implement a three-state model (undefined = not cached, null = cached miss, value = hit) with automatic cleanup of expired entries.


126-173: LGTM!

The domain-based config resolution implements a clear priority system with appropriate caching and fallback logic. The function correctly avoids redundant fallback attempts when the primary community is already the default.


179-249: LGTM!

The error handling correctly distinguishes between transient errors and legitimate not-found cases, avoiding aggressive null-caching that could suppress valid entries during temporary outages. The validation checks all required fields as expected. Past review comments have been properly addressed.


251-307: LGTM!

The function appropriately throws errors instead of returning null, which is the correct behavior when loading a config by a known community ID. The validation and caching logic matches tryLoadCommunityConfig, ensuring consistency.

Comment on lines 39 to 65
/**
* Resolve community ID from domain.
* Simple priority: special mapping → domain as-is → default fallback
*/
function resolveCommunityIdFromDomain(domain: string): string {
const normalizedDomain = normalizeDomain(domain);

// Check special domain mappings first (highest priority)
if (domain in DOMAIN_TO_COMMUNITY_MAP) {
return DOMAIN_TO_COMMUNITY_MAP[domain];
if (!normalizedDomain) {
return DEFAULT_COMMUNITY_ID;
}

// Handle Vercel preview deployments (e.g., nounspace.vercel.app, branch-nounspace.vercel.app)
// All Vercel preview deployments should point to nouns community
// Priority 1: Handle Vercel preview deployments (e.g., nounspace.vercel.app, branch-nounspace.vercel.app)
// All Vercel preview deployments should point to nounspace.com community
// Match any .vercel.app domain (preview deployments have random branch names)
if (domain.endsWith('.vercel.app')) {
return 'nouns';
return DEFAULT_COMMUNITY_ID;
}

// Priority 2: Special domain mappings (from DOMAIN_TO_COMMUNITY_MAP)
if (normalizedDomain in DOMAIN_TO_COMMUNITY_MAP) {
return DOMAIN_TO_COMMUNITY_MAP[normalizedDomain];
}

// Support localhost subdomains for local testing
// e.g., example.localhost:3000 -> example
if (domain.includes('localhost')) {
const parts = domain.split('.');
if (parts.length > 1 && parts[0] !== 'localhost') {
// Has subdomain before localhost
return parts[0];
// Priority 3: Domain as community ID (e.g., example.nounspace.com → example.nounspace.com)
// Priority 4: Default fallback (handled by caller if domain lookup fails)
return normalizedDomain;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix inconsistent domain checking for Vercel deployments.

Line 53 checks domain.endsWith('.vercel.app') but should use normalizedDomain for consistency. The domain is normalized on line 44, but the original domain parameter is used for the Vercel check. This inconsistency could cause Vercel preview deployments with port numbers (e.g., example.vercel.app:3000) or mixed case (e.g., EXAMPLE.VERCEL.APP) to not be recognized correctly.

🔎 Proposed fix
   // Priority 1: Handle Vercel preview deployments (e.g., nounspace.vercel.app, branch-nounspace.vercel.app)
   // All Vercel preview deployments should point to nounspace.com community
   // Match any .vercel.app domain (preview deployments have random branch names)
-  if (domain.endsWith('.vercel.app')) {
+  if (normalizedDomain.endsWith('.vercel.app')) {
     return DEFAULT_COMMUNITY_ID;
   }
🤖 Prompt for AI Agents
In src/config/loaders/registry.ts around lines 39 to 65, the Vercel preview
check uses the original domain variable which can miss normalized variants
(ports, casing); replace the condition to use
normalizedDomain.endsWith('.vercel.app') so the normalized value (already
computed and validated) is used for matching Vercel deployments, keeping the
check before the special mapping branch.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/config/loaders/registry.ts (1)

53-55: Critical: Use normalized domain for Vercel deployment check.

Line 53 checks domain.endsWith('.vercel.app') but should use normalizedDomain (computed on line 44) for consistency. The original domain parameter may contain port numbers (e.g., example.vercel.app:3000) or mixed case (e.g., EXAMPLE.VERCEL.APP), which would cause the check to fail or behave inconsistently.

Note: This issue was flagged in a previous review as critical and marked as addressed in commit b8d56ca, but the fix is not present in the current code. This may indicate a regression or that the commit was not included in this PR.

🔎 Proposed fix
   // Priority 1: Handle Vercel preview deployments (e.g., nounspace.vercel.app, branch-nounspace.vercel.app)
   // All Vercel preview deployments should point to nounspace.com community
   // Match any .vercel.app domain (preview deployments have random branch names)
-  if (domain.endsWith('.vercel.app')) {
+  if (normalizedDomain.endsWith('.vercel.app')) {
     return DEFAULT_COMMUNITY_ID;
   }
🧹 Nitpick comments (1)
src/config/index.ts (1)

79-81: Remove redundant development environment check.

Lines 79-81 check process.env.NODE_ENV === 'development' inside a block that already verified this condition on line 77. The inner check is unnecessary.

🔎 Proposed fix
   // Priority 3: Development override
   if (process.env.NODE_ENV === 'development' && process.env.NEXT_PUBLIC_TEST_COMMUNITY) {
     const devCommunityId = process.env.NEXT_PUBLIC_TEST_COMMUNITY;
-    if (process.env.NODE_ENV === 'development') {
-      console.log(`✅ Loading config for community: ${devCommunityId} (dev override)`);
-    }
+    console.log(`✅ Loading config for community: ${devCommunityId} (dev override)`);
     return await loadSystemConfigById(devCommunityId);
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8d56ca and 2d5562d.

📒 Files selected for processing (4)
  • src/config/index.ts
  • src/config/loaders/index.ts
  • src/config/loaders/registry.ts
  • src/config/loaders/utils.ts
💤 Files with no reviewable changes (1)
  • src/config/loaders/utils.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/config/index.ts (2)
src/config/loaders/index.ts (2)
  • loadSystemConfigById (19-19)
  • getCommunityConfigForDomain (19-19)
src/config/loaders/registry.ts (3)
  • loadSystemConfigById (275-331)
  • getCommunityConfigForDomain (137-172)
  • DEFAULT_COMMUNITY_ID (6-6)
🔇 Additional comments (2)
src/config/loaders/registry.ts (2)

201-224: Verified: Transient error handling is correct.

The error handling logic correctly distinguishes between legitimate "not found" errors (PGRST116) and transient errors. Transient errors do not cache null, allowing subsequent requests to retry. This addresses the concern raised in a previous review.


240-260: Verified: All required fields are validated.

The validation logic correctly checks all four required fields (brand_config, assets_config, community_config, fidgets_config), addressing the concern from a previous review about missing validation for community_config and fidgets_config.

…fig loading functions

- Remove all automatic fallbacks to DEFAULT_COMMUNITY_ID
- Bypass cache completely - all lookups query database directly
- Consolidate tryLoadCommunityConfig and loadSystemConfigById into single core function loadCommunityConfigFromDatabase
- Add comprehensive logging throughout config loading flow for debugging
- Update error handling to throw informative errors instead of silent fallbacks
- Add 'export const dynamic = force-dynamic' to root layout
- Remove build-time fallback metadata logic
- Metadata now generated per-request based on actual domain/community config
- Ensures metadata matches runtime config instead of build-time default
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (4)
src/config/loaders/index.ts (1)

16-19: Redundant explicit export.

Line 17 already uses export * from './registry', which exports all named exports from the registry module including getCommunityConfigForDomain, loadSystemConfigById, and DEFAULT_COMMUNITY_ID. The explicit re-export on line 19 is redundant.

🔎 Proposed fix
 export * from './types';
 export * from './registry';
 export * from './runtimeLoader';
-export { getCommunityConfigForDomain, loadSystemConfigById, DEFAULT_COMMUNITY_ID } from './registry';
src/app/layout.tsx (1)

124-125: Consider sanitizing color values from the database.

navFontColor and castButtonFontColor are injected directly into inline styles from database-sourced config. While the current threat model assumes admin-controlled data, malicious values could potentially break styling or (in edge cases) be used for CSS-based attacks.

Consider adding a simple validation function to ensure these are valid CSS color values (hex, rgb, or named colors).

🔎 Example validation approach
const CSS_COLOR_REGEX = /^(#[0-9a-fA-F]{3,8}|rgb\(|rgba\(|hsl\(|hsla\(|[a-z]+)$/i;

function validateCssColor(color: string | undefined, fallback: string): string {
  if (!color) return fallback;
  // Basic check - allows hex, rgb/rgba/hsl/hsla functions, and named colors
  if (CSS_COLOR_REGEX.test(color.trim())) {
    return color.trim();
  }
  console.warn('Invalid CSS color value rejected:', color);
  return fallback;
}

// Usage:
const navFontColor = validateCssColor(systemConfig.ui?.fontColor, "#0f172a");
const castButtonFontColor = validateCssColor(systemConfig.ui?.castButtonFontColor, "#ffffff");
src/config/index.ts (1)

82-89: Minor: Redundant environment check.

The NODE_ENV === 'development' check on line 85 is redundant since it's already checked on line 83.

🔎 Proposed fix
   // Priority 3: Development override
   if (process.env.NODE_ENV === 'development' && process.env.NEXT_PUBLIC_TEST_COMMUNITY) {
     const devCommunityId = process.env.NEXT_PUBLIC_TEST_COMMUNITY;
-    if (process.env.NODE_ENV === 'development') {
-      console.log(`✅ Loading config for community: ${devCommunityId} (dev override)`);
-    }
+    console.log(`✅ Loading config for community: ${devCommunityId} (dev override)`);
     return await loadSystemConfigById(devCommunityId);
   }
src/config/loaders/registry.ts (1)

30-36: Unused cache infrastructure.

The SYSTEM_CONFIG_CACHE, SystemConfigCacheEntry, readSystemConfigCache, and writeSystemConfigCache are defined but never used—getCommunityConfigForDomain and loadCommunityConfigFromDatabase both explicitly bypass the cache. Consider removing this dead code or implementing the caching if it's intended for future use.

🔎 Option 1: Remove unused cache code
-/**
- * Cache entry for SystemConfig lookups.
- *
- * Keeps a short-lived in-memory cache to reduce Supabase round-trips when the
- * same community is requested repeatedly (e.g., during navigation or asset loads).
- * Caches the final SystemConfig (transformed and ready to use).
- */
-type SystemConfigCacheEntry = {
-  expiresAt: number;
-  value: SystemConfig | null;
-};
-
-const SYSTEM_CONFIG_CACHE = new Map<string, SystemConfigCacheEntry>();
-const SYSTEM_CONFIG_CACHE_TTL_MS = 60_000;

...

-/**
- * Attempt to read a cached SystemConfig entry if it has not expired.
- */
-function readSystemConfigCache(communityId: string): SystemConfig | null | undefined {
-  const entry = SYSTEM_CONFIG_CACHE.get(communityId);
-  if (!entry) return undefined;
-
-  if (entry.expiresAt > Date.now()) {
-    return entry.value;
-  }
-
-  SYSTEM_CONFIG_CACHE.delete(communityId);
-  return undefined;
-}
-
-/**
- * Store a SystemConfig value in the cache with a short TTL.
- */
-function writeSystemConfigCache(communityId: string, value: SystemConfig | null) {
-  SYSTEM_CONFIG_CACHE.set(communityId, {
-    expiresAt: Date.now() + SYSTEM_CONFIG_CACHE_TTL_MS,
-    value,
-  });
-}

Also applies to: 95-118

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d5562d and 002f19e.

📒 Files selected for processing (6)
  • middleware.ts
  • src/app/api/test-logs/route.ts
  • src/app/layout.tsx
  • src/config/index.ts
  • src/config/loaders/index.ts
  • src/config/loaders/registry.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-29T18:25:25.957Z
Learnt from: j-paterson
Repo: blankdotspace/space-system PR: 1625
File: src/common/lib/utils/tokenGates.ts:12-20
Timestamp: 2025-12-29T18:25:25.957Z
Learning: Gate tokens in src/common/lib/utils/tokenGates.ts are currently fetched redundantly from 3 client components (CreateCast.tsx, BackgroundGenerator.tsx, NogsGateButton.tsx) with no caching, and should be refactored to use a cached API route with a shared client hook to reduce redundant server calls.

Applied to files:

  • src/app/layout.tsx
🧬 Code graph analysis (4)
middleware.ts (1)
src/config/loaders/registry.ts (1)
  • normalizeDomain (69-76)
src/app/layout.tsx (3)
src/config/index.ts (1)
  • loadSystemConfig (27-100)
src/constants/app.ts (1)
  • WEBSITE_URL (1-6)
src/common/lib/utils/fontUtils.ts (1)
  • extractFontFamilyFromUrl (1-11)
src/config/loaders/registry.ts (4)
src/config/loaders/index.ts (3)
  • DEFAULT_COMMUNITY_ID (19-19)
  • getCommunityConfigForDomain (19-19)
  • loadSystemConfigById (19-19)
src/supabase/database.d.ts (1)
  • Database (11-1010)
src/config/shared/themes.ts (1)
  • themes (8-8)
src/common/data/database/supabase/clients/server.ts (1)
  • createSupabaseServerClient (7-7)
src/config/index.ts (1)
src/config/loaders/registry.ts (2)
  • loadSystemConfigById (263-276)
  • getCommunityConfigForDomain (132-168)
🔇 Additional comments (11)
src/app/api/test-logs/route.ts (1)

1-2: LGTM!

Standard Next.js import for API routes.

middleware.ts (1)

12-35: LGTM!

The middleware simplification is well-designed. Deferring community resolution to server components avoids unnecessary database queries for static assets and API routes. The use of console.error for Vercel log visibility is appropriate.

src/app/layout.tsx (2)

13-40: LGTM!

The stylesheet URL validation properly implements defense-in-depth by requiring HTTPS and checking against a trusted host allowlist. This addresses the previous security concern about CSS injection.


46-103: LGTM!

The dynamic metadata generation correctly uses runtime config to populate Open Graph, icons, and Farcaster frame metadata. The force-dynamic export ensures this runs per-request rather than at build time.

src/config/index.ts (1)

27-100: LGTM!

The refactored loadSystemConfig function has clear priority-based resolution with explicit error messages when configs are missing. Removing automatic fallbacks aligns with the PR objective to ensure each domain gets its intended config.

src/config/loaders/registry.ts (6)

43-60: LGTM!

The resolveCommunityIdFromDomain function has clear priority logic with proper error handling when domain normalization fails. The comment on line 58 mentions "Priority 3" but there's no Priority 2—consider updating the comment for clarity (minor nit).


69-76: LGTM!

The normalizeDomain function correctly handles edge cases: empty input, port stripping, lowercase conversion, and www. prefix removal.


83-93: Verify runtime behavior of type assertions.

The type assertions (as unknown as SystemConfig['...']) bypass compile-time type checking. While required field validation exists (lines 231-248), there's no validation of the JSON structure within each field. If brand_config exists but has incorrect nested properties, runtime errors will occur when downstream components access expected properties.

This was noted in a past review with a suggestion to add Zod validation. For MVP this may be acceptable, but consider documenting this limitation or adding runtime validation in a follow-up.


199-218: LGTM!

Good improvement distinguishing between legitimate "not found" errors (PGRST116) and transient/unknown errors. This prevents caching failures on transient issues and provides clear error messages for debugging.


230-248: LGTM!

Comprehensive validation of all required fields (brand_config, assets_config, community_config, fidgets_config) with clear error messages. This addresses the previous review comment about incomplete validation.


263-276: LGTM!

The loadSystemConfigById function provides a clean interface for loading config by explicit ID with proper error handling when config is not found.

- If x-detected-domain header is not set by middleware, read host header directly
- Normalize host header to get domain as fallback
- Add detailed logging to debug header issues
- Ensures domain detection works even if middleware doesn't set header
- Delete middleware.ts - no longer needed
- Read host header directly in loadSystemConfig() instead of relying on middleware header
- Simplify domain detection by removing middleware layer
- Update error messages to remove middleware references
- Host header works directly, making middleware unnecessary
- Remove all console.error debug/informational logs
- Keep only actual error logs (failures, not found, transient errors)
- Clean up entry point logs, domain resolution logs, database query logs
- Remove success messages and cache bypass messages
- Only log when something goes wrong, not for normal operation
- Delete ARCHITECTURE_SIMPLIFICATION.md planning file
- Delete test-logs API endpoint used for debugging
- Clean up temporary files added during issue investigation
- Re-enable cache checks in getCommunityConfigForDomain and loadSystemConfigById
- Add cache check in loadCommunityConfigFromDatabase before database query
- Write successful config loads to cache with 60 second TTL
- Cache stores transformed SystemConfig objects, not raw database rows
- Does not cache null values to allow immediate retries for transient errors
- Fall back to DEFAULT_COMMUNITY_ID when domain config not found
- Fall back to DEFAULT_COMMUNITY_ID when no domain/communityId provided
- Explicit communityId still throws error if not found (no fallback)
- Ensures app always has a config to use, even if specific domain config is missing
@j-paterson j-paterson merged commit 0765d93 into canary Dec 29, 2025
5 checks passed
@j-paterson j-paterson deleted the codex/find-unused-fields-in-community_config branch December 29, 2025 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex LGFTP Looks Good From Testing Perspective

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants