Skip to content

Commit 7ef3571

Browse files
sirjamesgrayclaude
andcommitted
fix: Resolve infinite render loop, stale cache issues, and Firebase permission errors
This commit addresses several critical bugs affecting page loading and editing: ## Infinite Render Loop Fix (useLinkSuggestions.ts) - Fixed "Maximum update depth exceeded" error caused by linkSuggestionActions object being recreated on every render - Added useMemo to memoize the actions object with proper dependencies - Added stateRef to access state values in callbacks without triggering re-renders - Changed analyzeText callback to use stateRef.current instead of direct state access ## Stale Cache Fix (apiClient.ts, ContentPageView.tsx) - Created comprehensive invalidatePageCacheAfterSave() function that clears: - ConsolidatedApiClient cache (client-side 5-min TTL cache) - Server-side pageCache and invalidateCache - InternalLinkWithTitle caches - sessionStorage optimistic page data - Added skipCache option to pageApi.getPage() and getPageById() - Logged-in users now skip client-side cache when loading pages to ensure fresh content for editors ## Firebase Permission Error Fix (WhatLinksHere.tsx) - Changed from client-side Firestore onSnapshot() subscription to server-side /api/links/backlinks API endpoint - Client-side Firestore queries don't have permission to access backlinks collection, but server-side API uses Firebase Admin SDK which bypasses rules ## Other Improvements - Performance optimization: Batch dashboard analytics API endpoint - Text selection menu: Fixed Link button functionality in design system - PageGraph3D: Fixed clientWidth null error - ActivityFeed: Removed redundant loading state - Added new API endpoints for monthly earnings creation and dashboard batch 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9b80274 commit 7ef3571

33 files changed

Lines changed: 3328 additions & 134 deletions

PLAN.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# CSS System Refactoring Plan
2+
3+
## Current State Analysis
4+
5+
### File Sizes
6+
| File | Lines | Purpose |
7+
|------|-------|---------|
8+
| `globals.css` | 5,328 | Main CSS - bloated with many systems |
9+
| `card-theme.css` | 371 | Card glassmorphism (now mostly unused) |
10+
| `content-display.css` | 329 | Content viewer styles |
11+
| `editor-styles.css` | 252 | Slate editor styles |
12+
| `allocation-bar-animations.css` | 283 | Payment allocation animations |
13+
| `fixed-layer.css` | 101 | Z-index and fixed positioning |
14+
| **Total** | **6,664** | |
15+
16+
### Problems Identified
17+
1. **Duplicated systems**: Multiple "shiny" classes for pills, buttons, badges, allocation bars
18+
2. **Dead code**: Card glassmorphism system (`wewrite-card`, `wewrite-floating`) now deprecated
19+
3. **Radix color classes**: 400+ lines of manually defined `.bg-neutral-alpha-N`, `.text-accent-N` etc. that duplicate what Tailwind already provides
20+
4. **Mobile admin overrides**: 150+ lines of hacky `!important` overrides
21+
5. **Scattered CSS variables**: Defined in 5+ places across different files
22+
6. **Input system complexity**: 150+ lines for `.wewrite-input` with glassmorphism
23+
24+
### What to KEEP (PillLink dependencies)
25+
From `PillStyleContext.tsx`, PillLink uses these CSS classes:
26+
- `pill-shiny-style` (lines 193-245 in globals.css)
27+
- `pill-outline-shiny-style` (lines 251-278)
28+
- `shiny-shimmer-base` (lines 318-400)
29+
- `shiny-glow-base` (extends shimmer base)
30+
- Touch device fixes for `[data-pill-style]` (lines 284-316)
31+
- `@keyframes shimmer` animation
32+
33+
---
34+
35+
## Proposed New Structure
36+
37+
### Phase 1: Consolidate CSS Variables
38+
Create a single source of truth for all CSS variables:
39+
40+
```
41+
app/styles/
42+
├── variables.css # ALL CSS variables (colors, spacing, z-index)
43+
├── base.css # Tailwind base layer + html/body defaults
44+
├── components/
45+
│ ├── pill-links.css # PillLink shiny system (KEEP intact)
46+
│ ├── buttons.css # Button shiny variants
47+
│ ├── inputs.css # Simplified input styles
48+
│ └── modals.css # Dialog/drawer backdrop styles
49+
├── utilities.css # Touch utilities, scrollbar, selection
50+
└── animations.css # All @keyframes
51+
```
52+
53+
### Phase 2: Simplify Color System
54+
**Remove from globals.css:**
55+
- All 400+ manual Radix-style color utility classes (`.bg-neutral-alpha-N`, etc.)
56+
- These are already available via Tailwind config
57+
58+
**Keep in Tailwind config:**
59+
- The existing color definitions in `tailwind.config.ts` already handle this
60+
61+
### Phase 3: Remove Dead Code
62+
**Delete entirely:**
63+
- Card glassmorphism system (`.wewrite-card`, `.wewrite-floating`, `card-theme.css`)
64+
- Replace remaining usages with `bg-popover border-border`
65+
- Mobile admin page overrides (use responsive Tailwind instead)
66+
67+
### Phase 4: Simplify Shiny System
68+
**Current:** 6 different shiny classes (pill, button variants, badge, allocation-bar)
69+
**Proposed:** 2 base classes + color modifiers
70+
71+
```css
72+
/* Base shimmer effect */
73+
.shimmer { /* shimmer animation */ }
74+
75+
/* Glow effect - uses CSS custom property for color */
76+
.glow {
77+
--glow-color: var(--primary);
78+
box-shadow: 0 0 4px oklch(var(--glow-color) / 0.3), ...;
79+
}
80+
81+
/* Usage */
82+
.shimmer.glow { --glow-color: var(--accent-base); } /* Pills */
83+
.shimmer.glow { --glow-color: var(--primary); } /* Buttons */
84+
.shimmer.glow { --glow-color: var(--error); } /* Destructive */
85+
```
86+
87+
---
88+
89+
## Migration Strategy
90+
91+
### Step 1: Extract & Preserve PillLink Styles
92+
1. Create `app/styles/components/pill-links.css`
93+
2. Move pill-shiny, pill-outline-shiny, shimmer base, and touch fixes
94+
3. Test PillLink still works in all 4 styles
95+
96+
### Step 2: Create New Variables File
97+
1. Create `app/styles/variables.css`
98+
2. Consolidate all `:root` definitions from:
99+
- `globals.css` (lines 1718-1900)
100+
- `card-theme.css` (lines 13-37)
101+
- `fixed-layer.css` (lines 3-29)
102+
3. Remove card-bg/card-border variables (no longer needed)
103+
104+
### Step 3: Delete Radix Color Utilities
105+
1. Remove lines 3393-3710 from globals.css (manual color utilities)
106+
2. These are already in Tailwind config - components should use `bg-neutral-10` etc.
107+
108+
### Step 4: Simplify Input System
109+
1. Create `app/styles/components/inputs.css`
110+
2. Remove glassmorphism, use solid backgrounds:
111+
```css
112+
.wewrite-input {
113+
@apply bg-background border border-border rounded-lg px-4 py-3;
114+
@apply focus:border-accent focus:ring-2 focus:ring-accent/20;
115+
}
116+
```
117+
118+
### Step 5: Clean Up Button Shiny
119+
1. Create single `.button-shiny` class in `buttons.css`
120+
2. Use CSS custom properties for color variants
121+
3. Remove duplicate button-*-shiny-style classes
122+
123+
### Step 6: Remove Mobile Admin Hacks
124+
1. Delete lines 4360-4500 (mobile-admin-page overrides)
125+
2. Update admin pages to use responsive Tailwind classes directly
126+
127+
---
128+
129+
## Expected Results
130+
131+
| Metric | Before | After |
132+
|--------|--------|-------|
133+
| Total CSS lines | 6,664 | ~2,500 |
134+
| CSS files | 6 | 8 (more organized) |
135+
| Shiny class variants | 12+ | 2 base + modifiers |
136+
| Manual color utilities | 400+ | 0 (use Tailwind) |
137+
| Card glassmorphism code | 371 lines | 0 |
138+
139+
## Risk Mitigation
140+
- **PillLink**: Extract FIRST, test BEFORE other changes
141+
- **Incremental**: Each phase is independently deployable
142+
- **Visual regression**: Screenshot key pages before/after
143+
144+
## Files to Modify
145+
1. `app/globals.css` - Major reduction
146+
2. `app/styles/card-theme.css` - DELETE
147+
3. `app/tailwind.config.ts` - No changes needed
148+
4. Various components using `wewrite-card` - Update to `bg-popover`
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"use client";
2+
3+
import React, { useState } from 'react';
4+
import { Icon } from '@/components/ui/Icon';
5+
import { Button } from '../../../components/ui/button';
6+
import { ComponentShowcase, StateDemo } from './shared';
7+
import { handleGenericShare, handleProfileShare } from '../../../utils/pageActionHandlers';
8+
import { useAuth } from '../../../providers/AuthProvider';
9+
10+
export function ShareSection({ id }: { id: string }) {
11+
const { user } = useAuth();
12+
const [lastAction, setLastAction] = useState<string>('');
13+
14+
const handleDemoShare = (type: string, title: string) => {
15+
setLastAction(`Triggered: ${type}`);
16+
17+
if (type === 'page') {
18+
// Demo page share
19+
handleGenericShare({
20+
title: 'Example Page Title by author on WeWrite',
21+
text: 'Check out this page on WeWrite',
22+
analyticsContext: 'design_system_demo',
23+
user
24+
});
25+
} else if (type === 'profile-bio') {
26+
handleProfileShare('demouser', 'bio', user);
27+
} else if (type === 'profile-graph') {
28+
handleProfileShare('demouser', 'graph', user);
29+
} else if (type === 'profile-pages') {
30+
handleProfileShare('demouser', 'pages', user);
31+
} else if (type === 'custom') {
32+
handleGenericShare({
33+
url: 'https://getwewrite.app/map?lat=35&lng=-106',
34+
title: 'New Mexico on WeWrite Map',
35+
text: 'Check out this location',
36+
analyticsContext: 'design_system_demo',
37+
user
38+
});
39+
}
40+
};
41+
42+
return (
43+
<ComponentShowcase
44+
id={id}
45+
title="Share System"
46+
path="app/utils/pageActionHandlers.ts"
47+
description="Centralized share functionality using Web Share API with clipboard fallback. All share actions should use these handlers to ensure consistent behavior, proper titles, and analytics tracking. See docs/ui/SHARE_SYSTEM.md for full documentation."
48+
>
49+
<StateDemo label="Share Handlers">
50+
<div className="flex flex-wrap gap-2">
51+
<Button
52+
variant="outline"
53+
size="sm"
54+
onClick={() => handleDemoShare('page', 'Page Share')}
55+
>
56+
<Icon name="Share" size={14} className="mr-2" />
57+
handleShare (Page)
58+
</Button>
59+
<Button
60+
variant="outline"
61+
size="sm"
62+
onClick={() => handleDemoShare('profile-bio', 'Profile Bio')}
63+
>
64+
<Icon name="User" size={14} className="mr-2" />
65+
handleProfileShare (Bio)
66+
</Button>
67+
<Button
68+
variant="outline"
69+
size="sm"
70+
onClick={() => handleDemoShare('profile-graph', 'Profile Graph')}
71+
>
72+
<Icon name="Network" size={14} className="mr-2" />
73+
handleProfileShare (Graph)
74+
</Button>
75+
<Button
76+
variant="outline"
77+
size="sm"
78+
onClick={() => handleDemoShare('custom', 'Custom URL')}
79+
>
80+
<Icon name="MapPin" size={14} className="mr-2" />
81+
handleGenericShare (Custom)
82+
</Button>
83+
</div>
84+
{lastAction && (
85+
<p className="text-sm text-muted-foreground mt-2">{lastAction}</p>
86+
)}
87+
</StateDemo>
88+
89+
<StateDemo label="Title Formats">
90+
<div className="space-y-2 text-sm">
91+
<div className="flex items-start gap-2">
92+
<code className="bg-muted px-2 py-1 rounded text-xs">Page:</code>
93+
<span className="text-muted-foreground">"Page Title by author on WeWrite"</span>
94+
</div>
95+
<div className="flex items-start gap-2">
96+
<code className="bg-muted px-2 py-1 rounded text-xs">Profile (Bio):</code>
97+
<span className="text-muted-foreground">"username - Bio on WeWrite"</span>
98+
</div>
99+
<div className="flex items-start gap-2">
100+
<code className="bg-muted px-2 py-1 rounded text-xs">Profile (Pages):</code>
101+
<span className="text-muted-foreground">"username - Pages on WeWrite"</span>
102+
</div>
103+
<div className="flex items-start gap-2">
104+
<code className="bg-muted px-2 py-1 rounded text-xs">Profile (Graph):</code>
105+
<span className="text-muted-foreground">"username - Graph on WeWrite"</span>
106+
</div>
107+
<div className="flex items-start gap-2">
108+
<code className="bg-muted px-2 py-1 rounded text-xs">Custom:</code>
109+
<span className="text-muted-foreground">Your custom title</span>
110+
</div>
111+
</div>
112+
</StateDemo>
113+
114+
<StateDemo label="Available Tab Names">
115+
<div className="flex flex-wrap gap-2">
116+
{['bio', 'pages', 'recent-activity', 'timeline', 'graph', 'map', 'external-links'].map(tab => (
117+
<code key={tab} className="bg-muted px-2 py-1 rounded text-xs">{tab}</code>
118+
))}
119+
</div>
120+
</StateDemo>
121+
122+
<StateDemo label="Implementation">
123+
<div className="bg-muted/50 rounded-lg p-4 font-mono text-xs overflow-x-auto">
124+
<pre>{`// Page sharing
125+
import { handleShare } from '@/utils/pageActionHandlers';
126+
handleShare(page, page.title, user);
127+
128+
// Profile sharing (tab-aware)
129+
import { handleProfileShare } from '@/utils/pageActionHandlers';
130+
handleProfileShare(username, currentTab, user);
131+
132+
// Custom URL sharing
133+
import { handleGenericShare } from '@/utils/pageActionHandlers';
134+
handleGenericShare({
135+
url: 'https://...',
136+
title: 'Title on WeWrite',
137+
text: 'Description',
138+
analyticsContext: 'my_feature',
139+
user
140+
});`}</pre>
141+
</div>
142+
</StateDemo>
143+
144+
<StateDemo label="Share Flow">
145+
<div className="text-sm text-muted-foreground space-y-1">
146+
<p>1. Check if <code className="bg-muted px-1 rounded">navigator.share</code> is available</p>
147+
<p className="pl-4">- If yes: Use native Web Share API (mobile share sheets)</p>
148+
<p className="pl-4">- If no: Copy URL to clipboard</p>
149+
<p>2. Track analytics event (succeeded/aborted)</p>
150+
<p>3. Show toast feedback on clipboard copy</p>
151+
</div>
152+
</StateDemo>
153+
154+
<StateDemo label="Analytics Events">
155+
<div className="flex flex-wrap gap-2">
156+
<code className="bg-green-500/10 text-green-600 dark:text-green-400 px-2 py-1 rounded text-xs">page_share_succeeded</code>
157+
<code className="bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 px-2 py-1 rounded text-xs">page_share_aborted</code>
158+
</div>
159+
<p className="text-xs text-muted-foreground mt-2">
160+
Tracked with: share_method, share_context, user_id, share_title
161+
</p>
162+
</StateDemo>
163+
</ComponentShowcase>
164+
);
165+
}

0 commit comments

Comments
 (0)