Skip to content

fix: replace mobile navbar crown button with AI quota display - #134

Merged
2witstudios merged 3 commits into
masterfrom
claude/mobile-navbar-quota-button-bOinj
Dec 28, 2025
Merged

2witstudios merged 3 commits into
masterfrom
claude/mobile-navbar-quota-button-bOinj

Conversation

@2witstudios

@2witstudios 2witstudios commented Dec 28, 2025 •

Copy link
Copy Markdown
Owner

Remove the billing crown button from mobile navbar and show the
UsageCounter component on all screen sizes instead.

Summary by CodeRabbit

  • New Features

    • Usage counter now shows model-specific Standard and Pro quotas and indicates when you’re near limits.
  • User Interface

    • Header simplified; usage counter moved to a consistent right-side location.
    • Quota display now shows loading and error states and uses attention styling for near-limit warnings.

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

Remove the billing crown button from mobile navbar and show the
UsageCounter component on all screen sizes instead.
- Only show quota when PageSpace provider is selected
- Show Standard quota only when Standard model (glm-4.5-air) is selected
- Show Pro quota only when Pro model (glm-4.6) is selected
- Hide quota entirely for external providers (OpenAI, Anthropic, etc.)
@coderabbitai

coderabbitai Bot commented Dec 28, 2025 •

Copy link
Copy Markdown
Contributor

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

UsageCounter now loads assistant settings to detect PageSpace provider and model (Standard/Pro), then conditionally fetches and displays model-specific quotas with near-limit indicators. Main header replaces the billing/settings button with an unconditional UsageCounter.

Changes

Cohort / File(s) Summary
UsageCounter Model Awareness
apps/web/src/components/billing/UsageCounter.tsx
Loads assistant settings on mount; adds PageSpace model identifiers (PAGESPACE_STANDARD_MODEL, PAGESPACE_PRO_MODEL); derives flags (isPageSpaceProvider, isStandardModel, isProModel); gates rendering to PageSpace; conditionally fetches usage and displays Standard/Pro quota badges with near-limit logic and adjusted loading/error handling.
Main Header Simplification
apps/web/src/components/layout/main-header/index.tsx
Removes Crown import and the billing/settings link button; renders UsageCounter unconditionally in the right-side cluster, simplifying header layout.

Sequence Diagram(s)

sequenceDiagram
    participant Header as MainHeader (UI)
    participant Counter as UsageCounter (UI)
    participant Store as AssistantSettingsStore
    participant API as Usage API (SWR fetch)

    Header->>Counter: Render UsageCounter
    Counter->>Store: loadSettings() (on mount)
    Store-->>Counter: settings (provider, model, access)
    alt provider == PageSpace
        Counter->>API: fetch usage (SWR) with model context
        API-->>Counter: usage data / error
        Counter->>Counter: compute near-limit flags (Standard/Pro)
        Counter-->>Header: render quota badges (Standard/Pro) or loading/error
    else not PageSpace
        Counter-->>Header: render minimal/hidden usage UI
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped to check the settings store,
Found PageSpace models, and then some more.
Quotas counted, badges gleam,
Header trimmed — a tidy team,
A rabbit cheers for tidy code galore! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'fix: replace mobile navbar crown button with AI quota display' directly reflects the main change: removing the crown button from the navbar and replacing it with the UsageCounter component showing quota information.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/mobile-navbar-quota-button-bOinj

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb0add5 and 40455e9.

📒 Files selected for processing (1)
  • apps/web/src/components/billing/UsageCounter.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Use camelCase for variable and function names
Use UPPER_SNAKE_CASE for constants
Use PascalCase for type and enum names
Use kebab-case for filenames, except React hooks (camelCase with use prefix), Zustand stores (camelCase with use prefix), and React components (PascalCase)
Lint with Next/ESLint as configured in apps/web/eslint.config.mjs
Message content should always use the message parts structure with { parts: [{ type: 'text', text: '...' }] }
Use centralized permission functions from @pagespace/lib/permissions (e.g., getUserAccessLevel, canUserEditPage) instead of implementing permission logic locally
Always use Drizzle client from @pagespace/db package for database access
Use ESM modules throughout the codebase

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Write code that is explicit over implicit and self-documenting

Files:

  • apps/web/src/components/billing/UsageCounter.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/*.tsx: React component files should use PascalCase (e.g., UserProfile.tsx)
Use @dnd-kit for drag-and-drop functionality
Use Zustand for client state management
Use SWR for server state management and caching

Files:

  • apps/web/src/components/billing/UsageCounter.tsx
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier

Files:

  • apps/web/src/components/billing/UsageCounter.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS and shadcn/ui components for styling and UI

Files:

  • apps/web/src/components/billing/UsageCounter.tsx
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/src/**/*.{ts,tsx}: Use message parts structure for message content: { parts: [{ type: 'text', text: '...' }] }
For database access, always use Drizzle client from @pagespace/db: import { db, pages } from '@pagespace/db';
Use centralized Drizzle ORM with PostgreSQL for all database operations - no direct SQL or other ORMs
Use Socket.IO for real-time collaboration features - imported from the realtime service at port 3001
Use Vercel AI SDK with async/await for all AI operations and streaming
Use Next.js 15 App Router and TypeScript for all routes and components

Files:

  • apps/web/src/components/billing/UsageCounter.tsx
apps/web/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/src/components/**/*.{ts,tsx}: When document editing, register editing state with useEditingStore.getState().startEditing() to prevent UI refreshes, and clean up in return statement
When AI is streaming, register streaming state with useEditingStore.getState().startStreaming() to prevent UI refreshes, and clean up in return statement
For SWR data fetching with editing protection, use isPaused: () => hasLoadedRef.current && isEditingActive() to allow initial fetch and only pause after, with revalidateOnFocus: false
Use Zustand for client-side state management as the primary state solution
Use SWR for server state and caching with proper configuration including revalidateOnFocus: false for editing protection
Use TipTap rich text editor with markdown support for document editing
Use Monaco Editor for code editing features
Use @dnd-kit for drag-and-drop functionality instead of other libraries
Use Tailwind CSS with shadcn/ui components for all UI styling and components

Files:

  • apps/web/src/components/billing/UsageCounter.tsx
🧠 Learnings (5)
📚 Learning: 2025-12-18T05:22:42.263Z
Learnt from: 2witstudios
Repo: 2witstudios/PageSpace PR: 96
File: apps/web/src/components/layout/right-sidebar/ai-assistant/SidebarChatTab.tsx:641-657
Timestamp: 2025-12-18T05:22:42.263Z
Learning: In apps/web/src/components/layout/right-sidebar/ai-assistant/SidebarChatTab.tsx, the provider/model selector buttons are intentionally non-functional placeholders in the compact sidebar view. The `hideModelSelector={true}` prop is passed to ChatInput to hide the full ProviderModelSelector. Users are expected to use the full GlobalAssistantView for model selection. A settings link may be added in a future iteration.

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-14T14:54:45.713Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:45.713Z
Learning: Applies to app/**/*.{ts,tsx} : Import and use `getUserAccessLevel()` and `canUserEditPage()` from `pagespace/lib/permissions` for centralized permission logic

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-23T18:49:41.955Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.955Z
Learning: Applies to apps/web/src/components/**/*.{ts,tsx} : When AI is streaming, register streaming state with `useEditingStore.getState().startStreaming()` to prevent UI refreshes, and clean up in return statement

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-23T18:49:41.955Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.955Z
Learning: Applies to apps/web/src/components/**/*.{ts,tsx} : For SWR data fetching with editing protection, use `isPaused: () => hasLoadedRef.current && isEditingActive()` to allow initial fetch and only pause after, with `revalidateOnFocus: false`

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-23T18:49:41.955Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.955Z
Learning: Applies to apps/web/src/components/**/*.{ts,tsx} : When document editing, register editing state with `useEditingStore.getState().startEditing()` to prevent UI refreshes, and clean up in return statement

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
🧬 Code graph analysis (1)
apps/web/src/components/billing/UsageCounter.tsx (1)
apps/web/src/stores/useAssistantSettingsStore.ts (1)
  • useAssistantSettingsStore (52-168)
⏰ 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: Unit Tests
🔇 Additional comments (4)
apps/web/src/components/billing/UsageCounter.tsx (4)

77-80: LGTM: Near-limit calculations are correct.

The near-limit logic properly checks for quota existence, validates limits are active, and uses a reasonable threshold of 10 remaining requests for visual warnings.


129-192: LGTM: Conditional quota display logic is well-structured.

The renderQuotaDisplay function correctly implements model-specific quota badges with appropriate error handling, loading states, and near-limit visual warnings. The intentional behavior of showing loading state only for PageSpace provider aligns with the design—external providers don't have quotas to display.


194-218: LGTM: Unconditional billing button ensures consistent navigation.

The billing/upgrade button is always rendered regardless of provider or model selection, providing reliable access to billing settings. This design choice, combined with conditional quota display, successfully addresses the regression concern from the previous review while maintaining a clean, responsive layout.


52-67: The billing button now always renders, addressing the prior regression.

The addition of loadSettings() on mount combined with the unconditional billing button (lines 200-217) ensures that billing navigation is always available, even on direct page loads before settings finish loading.

The model constants PAGESPACE_STANDARD_MODEL = 'glm-4.5-air' and PAGESPACE_PRO_MODEL = 'glm-4.6' are correct and match backend definitions found in the database schema and rate-limit middleware.

Regarding the race condition: while useSWR starts fetching usage data immediately (line 68) and loadSettings() is asynchronous, the component gracefully handles the interim state. The quota badges only render when both provider and model flags are true (lines 129-192), and a loading state is shown for the PageSpace provider while waiting for usage data (lines 141-148). The isPaused configuration also prevents SWR revalidation during editing/streaming.


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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/components/billing/UsageCounter.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/src/components/billing/UsageCounter.tsx (1)

19-21: Consider extracting model constants to a shared location.

These model identifiers ('glm-4.5-air' and 'glm-4.6') are hardcoded in 40+ places across the codebase. They already exist in apps/web/src/lib/ai/core/ai-providers-config.ts as part of the AI_PROVIDERS configuration. Instead of duplicating them locally, reference the centralized config to reduce fragility if these model IDs change.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a050079 and bb0add5.

📒 Files selected for processing (2)
  • apps/web/src/components/billing/UsageCounter.tsx
  • apps/web/src/components/layout/main-header/index.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Use camelCase for variable and function names
Use UPPER_SNAKE_CASE for constants
Use PascalCase for type and enum names
Use kebab-case for filenames, except React hooks (camelCase with use prefix), Zustand stores (camelCase with use prefix), and React components (PascalCase)
Lint with Next/ESLint as configured in apps/web/eslint.config.mjs
Message content should always use the message parts structure with { parts: [{ type: 'text', text: '...' }] }
Use centralized permission functions from @pagespace/lib/permissions (e.g., getUserAccessLevel, canUserEditPage) instead of implementing permission logic locally
Always use Drizzle client from @pagespace/db package for database access
Use ESM modules throughout the codebase

**/*.{ts,tsx}: Never use any types - always use proper TypeScript types
Write code that is explicit over implicit and self-documenting

Files:

  • apps/web/src/components/layout/main-header/index.tsx
  • apps/web/src/components/billing/UsageCounter.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/*.tsx: React component files should use PascalCase (e.g., UserProfile.tsx)
Use @dnd-kit for drag-and-drop functionality
Use Zustand for client state management
Use SWR for server state management and caching

Files:

  • apps/web/src/components/layout/main-header/index.tsx
  • apps/web/src/components/billing/UsageCounter.tsx
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier

Files:

  • apps/web/src/components/layout/main-header/index.tsx
  • apps/web/src/components/billing/UsageCounter.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Tailwind CSS and shadcn/ui components for styling and UI

Files:

  • apps/web/src/components/layout/main-header/index.tsx
  • apps/web/src/components/billing/UsageCounter.tsx
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/src/**/*.{ts,tsx}: Use message parts structure for message content: { parts: [{ type: 'text', text: '...' }] }
For database access, always use Drizzle client from @pagespace/db: import { db, pages } from '@pagespace/db';
Use centralized Drizzle ORM with PostgreSQL for all database operations - no direct SQL or other ORMs
Use Socket.IO for real-time collaboration features - imported from the realtime service at port 3001
Use Vercel AI SDK with async/await for all AI operations and streaming
Use Next.js 15 App Router and TypeScript for all routes and components

Files:

  • apps/web/src/components/layout/main-header/index.tsx
  • apps/web/src/components/billing/UsageCounter.tsx
apps/web/src/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/src/components/**/*.{ts,tsx}: When document editing, register editing state with useEditingStore.getState().startEditing() to prevent UI refreshes, and clean up in return statement
When AI is streaming, register streaming state with useEditingStore.getState().startStreaming() to prevent UI refreshes, and clean up in return statement
For SWR data fetching with editing protection, use isPaused: () => hasLoadedRef.current && isEditingActive() to allow initial fetch and only pause after, with revalidateOnFocus: false
Use Zustand for client-side state management as the primary state solution
Use SWR for server state and caching with proper configuration including revalidateOnFocus: false for editing protection
Use TipTap rich text editor with markdown support for document editing
Use Monaco Editor for code editing features
Use @dnd-kit for drag-and-drop functionality instead of other libraries
Use Tailwind CSS with shadcn/ui components for all UI styling and components

Files:

  • apps/web/src/components/layout/main-header/index.tsx
  • apps/web/src/components/billing/UsageCounter.tsx
🧠 Learnings (5)
📚 Learning: 2025-12-14T14:54:45.713Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-14T14:54:45.713Z
Learning: Applies to app/**/*.{ts,tsx} : Import and use `getUserAccessLevel()` and `canUserEditPage()` from `pagespace/lib/permissions` for centralized permission logic

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-18T05:22:42.263Z
Learnt from: 2witstudios
Repo: 2witstudios/PageSpace PR: 96
File: apps/web/src/components/layout/right-sidebar/ai-assistant/SidebarChatTab.tsx:641-657
Timestamp: 2025-12-18T05:22:42.263Z
Learning: In apps/web/src/components/layout/right-sidebar/ai-assistant/SidebarChatTab.tsx, the provider/model selector buttons are intentionally non-functional placeholders in the compact sidebar view. The `hideModelSelector={true}` prop is passed to ChatInput to hide the full ProviderModelSelector. Users are expected to use the full GlobalAssistantView for model selection. A settings link may be added in a future iteration.

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-23T18:49:41.955Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.955Z
Learning: Applies to apps/web/src/components/**/*.{ts,tsx} : When AI is streaming, register streaming state with `useEditingStore.getState().startStreaming()` to prevent UI refreshes, and clean up in return statement

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-23T18:49:41.955Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.955Z
Learning: Applies to apps/web/src/components/**/*.{ts,tsx} : For SWR data fetching with editing protection, use `isPaused: () => hasLoadedRef.current && isEditingActive()` to allow initial fetch and only pause after, with `revalidateOnFocus: false`

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
📚 Learning: 2025-12-23T18:49:41.955Z
Learnt from: CR
Repo: 2witstudios/PageSpace PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-23T18:49:41.955Z
Learning: Applies to apps/web/src/components/**/*.{ts,tsx} : When document editing, register editing state with `useEditingStore.getState().startEditing()` to prevent UI refreshes, and clean up in return statement

Applied to files:

  • apps/web/src/components/billing/UsageCounter.tsx
🧬 Code graph analysis (2)
apps/web/src/components/layout/main-header/index.tsx (1)
apps/web/src/components/billing/UsageCounter.tsx (1)
  • UsageCounter (45-204)
apps/web/src/components/billing/UsageCounter.tsx (2)
apps/web/src/stores/useAssistantSettingsStore.ts (1)
  • useAssistantSettingsStore (52-168)
apps/web/src/components/ui/badge.tsx (1)
  • Badge (46-46)
⏰ 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: Unit Tests
🔇 Additional comments (7)
apps/web/src/components/layout/main-header/index.tsx (2)

5-5: LGTM!

Clean removal of the unused Crown icon import. The remaining icons (Home, PanelLeft, PanelRight, Search) are all still in use within the component.


71-72: LGTM!

Good approach placing UsageCounter unconditionally in the right-side cluster. Since UsageCounter now handles its own visibility logic (returning null for non-PageSpace providers), the header remains clean and delegates the display decision appropriately.

apps/web/src/components/billing/UsageCounter.tsx (5)

52-59: LGTM!

Good use of Zustand selector pattern to subscribe only to the specific state fields needed. The derived booleans for provider/model checks are straightforward and efficient.


71-73: LGTM!

The near-limit detection logic is sound. The limit > 0 guard correctly handles edge cases where the limit might be zero or unset.


122-125: LGTM!

Correct placement of the early return after all hooks but before the loading/error UI. This ensures hooks are always called (React rules) while hiding the component for non-PageSpace providers.


145-148: Consider handling unknown PageSpace models.

If currentModel is null, undefined, or a model ID not matching either constant (e.g., a future model), both showProQuota and showStandardQuota will be false. The component will render only the billing button with no quota display.

Is this the intended behavior, or should there be a fallback (e.g., show Standard quota by default)?


152-180: LGTM!

Clean implementation of model-specific quota displays. The responsive design appropriately hides labels on smaller screens while keeping the essential badge visible. The near-limit visual feedback using the destructive variant provides clear UX for users approaching their quota.

- Load assistant settings on mount to ensure provider/model are available
- Always render the billing/upgrade button for navigation
- Only conditionally show quota badges based on provider and model
- Show loading state only when PageSpace provider is selected
- Fixes regression where billing navigation was missing on direct page loads
@2witstudios
2witstudios merged commit cc9d9c3 into master Dec 28, 2025
3 checks passed
@2witstudios
2witstudios deleted the claude/mobile-navbar-quota-button-bOinj branch January 29, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants