Merge pull request #874 from bitsocialhq/master

Development
This commit is contained in:
Tom
2026-01-27 13:36:08 +08:00
committed by GitHub
6 changed files with 81 additions and 2590 deletions
+46 -12
View File
@@ -32,7 +32,7 @@ yarn electron # Run Electron app
- TypeScript strict mode
- Prettier for formatting (runs on pre-commit)
- Follow DRY principle—extract reusable components
- **DRY principle**: Never repeat UI elements across views—extract them into reusable components in `src/components/`. Same applies to logic—extract into custom hooks in `src/hooks/`
## React Patterns (Critical)
@@ -78,22 +78,41 @@ src/
└── data/ # Static data (default subplebbits, etc.)
```
## Documentation
## Recommended Skills
The following docs exist for deeper guidance. **Do not read them automatically**—they are large and will bloat the context window. Instead:
- Be aware they exist
- Consult them when relevant to the task or when the user asks
- Offer to read them if the user seems to need React pattern guidance
Skills are more efficient than docs—they inject targeted guidance without bloating the context window.
Available docs:
- **[docs/react-guide.md](docs/react-guide.md)** — Bad vs good React patterns with code examples
- **[docs/you-might-not-need-an-effect.md](docs/you-might-not-need-an-effect.md)** — When to avoid useEffect (comprehensive)
### Context7 (for library docs)
When you need documentation for libraries like **plebbit-react-hooks** or **plebbit-js**, use the Context7 skill to fetch current docs instead of relying on potentially outdated training data.
```bash
npx skills add https://github.com/intellectronica/agent-skills --skill context7
```
### Vercel React Best Practices
For deeper React/Next.js performance guidance. Provides 57 prioritized rules across 8 categories (waterfalls, bundle size, server-side performance, client-side fetching, re-renders, rendering, JS performance, and advanced patterns).
```bash
npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-best-practices
```
### Find Skills
Discover and install skills from the open agent skills ecosystem.
```bash
npx skills add https://github.com/vercel-labs/skills --skill find-skills
```
## Recommended MCP Servers
If you need to look up library documentation (like plebbit-react-hooks or plebbit-js), suggest the user install the **Exa MCP server**. Exa's `get_code_context_exa` tool provides accurate, up-to-date docs and code context—it offers broader coverage and fewer hallucinations than alternatives like context7.
### GitHub MCP
If you need to check Dependabot security alerts, read GitHub Actions logs, search issues/PRs, or look up code across GitHub, suggest the user install the **GitHub MCP server** with the `default,dependabot,actions` toolsets enabled.
For Dependabot security alerts, GitHub Actions logs, issue/PR searches, or cross-repo code lookup, use the **GitHub MCP server** (with `default,dependabot,actions` toolsets enabled).
If not available, suggest the user install it.
### Context Window Warning
@@ -148,11 +167,26 @@ node scripts/update-translations.js --audit --dry
node scripts/update-translations.js --audit --write
```
## Workflow
### GitHub Issues
When proposing or implementing code changes, always suggest a GitHub issue title and description. Format:
- **Title**: Use [Conventional Commits](https://www.conventionalcommits.org/) style (e.g., `fix: ...`, `feat: ...`, `perf: ...`, `refactor: ...`, `docs: ...`, `chore: ...`)
- **Description**: 2-3 informal sentences describing the problem (not the solution). Write as if the issue hasn't been fixed yet. Use markdown.
Use the `perf` type for performance optimizations (not `fix`).
### Troubleshooting
When stuck on a bug or issue, search the web for solutions. Developer communities often have recent fixes or workarounds that aren't in training data.
## Boundaries
- Never commit secrets or API keys
- Use yarn, not npm
- Keep components focused—split large components
- Add comments for complex logic, skip obvious code
- Add comments for complex/unclear code (especially custom functions in this FOSS project with many contributors). Skip comments for obvious code
- Test on mobile viewport (this is a responsive app)
+15
View File
@@ -1,3 +1,18 @@
## [0.6.2](https://github.com/plebbit/plebchan/compare/v0.6.1...v0.6.2) (2026-01-26)
### Bug Fixes
* **ci:** use setup-python action for macos release build ([3335f6f](https://github.com/plebbit/plebchan/commit/3335f6ff7675d93bd592b7390ba575fe7358eda3))
* **post:** include pending replies in useReplies calls ([2887347](https://github.com/plebbit/plebchan/commit/28873478a29c54b7c1ec22f02ca7300d545b20a1))
### Features
* **mod-queue:** improve compact view and add pending approval features ([fa4ec6d](https://github.com/plebbit/plebchan/commit/fa4ec6da40b42b7041774d747531c64dc10b26f6))
## [0.6.1](https://github.com/plebbit/plebchan/compare/v0.6.0...v0.6.1) (2026-01-25)
+19 -19
View File
@@ -9,12 +9,12 @@ _Telegram group for this repo https://t.me/fivechandev_
# 5chan
5chan is a serverless, adminless, decentralized and open-source 4chan alternative built on the [Plebbit protocol](https://plebbit.com). It features the same directory structure as 4chan, but with a crucial difference: **anyone can create and own boards, and multiple boards can compete for each directory slot**.
5chan is a serverless, adminless, decentralized and open-source 4chan alternative built on the [Bitsocial protocol](https://bitsocial.net). It features the same directory structure as 4chan, but with a crucial difference: **anyone can create and own boards, and multiple boards can compete for each directory slot**.
## Key Features
### Decentralized Board Ownership
Unlike traditional imageboards, 5chan has no global admins or central authority. Anyone can create unlimited boards using their own [plebbit node](https://github.com/plebbit/plebbit-cli). Each board owner runs their own P2P node that users connect to peer-to-peer, giving them complete control over their board's content, moderation, and rules.
Unlike traditional imageboards, 5chan has no global admins or central authority. Anyone can create unlimited boards using their own [bitsocial node](https://github.com/bitsocialhq/bitsocial-cli). Each board owner runs their own P2P node that users connect to peer-to-peer, giving them complete control over their board's content, moderation, and rules.
### Competitive Directory System
5chan maintains the familiar 4chan directory structure (Japanese Culture, Video Games, Interests, Creative, etc.), but introduces competition: **multiple boards can compete for each directory slot**. For example, there can be unlimited "Business & Finance" boards, but only the highest-voted one appears in the directory on the homepage.
@@ -23,7 +23,7 @@ Currently, directory assignments are temporarily handpicked by developers throug
### How It Works
- **Current System**: Developers manually curate directory assignments by reviewing pull requests to the [5chan-multisub.json](https://github.com/plebbit/lists/blob/master/5chan-multisub.json) file. This is temporary until DAO curation is implemented.
- **Current System**: Developers manually curate directory assignments by reviewing pull requests to the [5chan-multisub.json](https://github.com/bitsocialhq/lists/blob/master/5chan-multisub.json) file.
- **Future System**: Directory board assignments will be determined through gasless voting using pubsub. Community members will vote on which board should be assigned to each directory, and the highest-voted board will automatically become the directory board. This creates a competitive marketplace where board quality and community engagement determine directory placement.
@@ -33,33 +33,33 @@ Currently, directory assignments are temporarily handpicked by developers throug
#### In-App Board Creation
Creating boards directly from the 5chan web app (5chan.app) is planned. This requires connecting via RPC to a plebbit node—technically already possible, but there's no default connection configured. A default connection would require a public RPC service (similar to what Infura provides for crypto wallets, but for plebbit nodes). This would allow all users to be connected to a P2P node by default using a free tier subscription in the background, potentially monetized via ads injected in the RPC service-owned boards.
Creating boards directly from the 5chan web app (5chan.app) is planned. This requires connecting via RPC to a bitsocial node—technically already possible, but there's no default connection configured. A default connection would require a public RPC service (similar to what Infura provides for crypto wallets, but for bitsocial nodes). This would allow all users to be connected to a P2P node by default using a free tier subscription in the background, potentially monetized via ads injected in the RPC service-owned boards.
#### Pubsub Voting
The protocol design for pubsub voting is already drafted in [plebbit-js issue #25](https://github.com/plebbit/plebbit-js/issues/25). This will enable:
The protocol design for pubsub voting is already drafted in [pkc-js issue #25](https://github.com/pkcprotocol/pkc-js/issues/25). This will enable:
- Gasless voting using pubsub topics
- Weighted voting based on token balances
- Automatic directory resolution based on vote tallies
- Full decentralization without any intermediaries
This feature is on the plebbit-js roadmap but hasn't been implemented yet.
This feature is on the pkc-js roadmap but hasn't been implemented yet.
## Downloads
- **Web version**: https://5chan.app (also available using Brave/IPFS Companion on https://5chan.eth)
- **Desktop version** (full P2P plebbit node, seeds automatically): Available for Mac/Windows/Linux, [download from the release page](https://github.com/plebbit/5chan/releases/latest)
- **Mobile version**: Available for Android, [download from the release page](https://github.com/plebbit/5chan/releases/latest)
- **Desktop version** (full P2P bitsocial node, seeds automatically): Available for Mac/Windows/Linux, [download from the release page](https://github.com/bitsocialhq/5chan/releases/latest)
- **Mobile version**: Available for Android, [download from the release page](https://github.com/bitsocialhq/5chan/releases/latest)
## Creating a Board
In the plebbit protocol, a 5chan board is called a _subplebbit_. To create and run a subplebbit:
In the bitsocial protocol, a 5chan board is called a _community_. To create and run a community:
1. Install plebbit-cli, available for Windows, macOS, and Linux: [latest release](https://github.com/plebbit/plebbit-cli/releases/latest)
2. Follow the instructions in the repo's README
3. When running the daemon for the first time, it will output WebUI links you can use to manage your subplebbit with a GUI
1. Install bitsocial-cli, available for Windows, macOS, and Linux: [latest release](https://github.com/bitsocialhq/bitsocial-cli/releases/latest);
2. Follow the instructions in the repo's README;
3. When running the daemon for the first time, it will output WebUI links you can use to manage your bitsocial community with a GUI.
Once created, anyone can connect to your subplebbit using any plebbit client (such as 5chan) by using the subplebbit's address. The address is not stored in any central database—plebbit is a pure peer-to-peer protocol.
Once created, anyone can connect to your community using any bitsocial client (such as 5chan) by using the community address. The address is not stored in any central database—bitsocial is a pure peer-to-peer protocol.
**Note**: Creating boards directly from the 5chan web app is planned for the future (see [Future Roadmap](#future-roadmap)).
@@ -72,16 +72,16 @@ To have your board appear in a directory on the 5chan homepage:
- Relevant to the directory category
- **99% uptime** (since a board acts like its own server—it's a P2P node)
2. Open a pull request on GitHub by editing the [5chan-multisub.json](https://github.com/plebbit/lists/blob/master/5chan-multisub.json) file
2. Open a pull request on GitHub by editing the [5chan-multisub.json](https://github.com/bitsocialhq/lists/blob/master/5chan-multisub.json) file
3. Add your board's entry with:
- Title
- Address
- NSFW status (if applicable)
- Title: in the format `/directoryCode/ - Title`, e.g. "/biz/ - Business & Finance";
- Address: the bitsocial community address, whether IPNS key (`12KooW...`) or readable crypto address (`mydomain.eth`);
- NSFW status: `true` or `false`, must be the same as 4chan.
4. The developers will review your PR and merge it if approved
**Note**: Even if your board isn't assigned to a directory, users can still access it at any time using its address. Directory assignment only affects visibility on the homepage.
**Note**: Even if your board isn't assigned to a directory, users can still access it at any time using its bitsocial community address. Directory assignment only affects visibility on the homepage.
## Development
@@ -106,7 +106,7 @@ To have your board appear in a directory on the 5chan homepage:
### Challenge Types
Subplebbits can require challengers to solve one or more prompts before a publication is accepted. 5chan already supported text and image prompts, and now also handles `url/iframe` challenges so Mintpass communities can run their iframe flow directly inside the modal. The modal first shows a hostname confirmation (showing only the host for mintpass.org, full URL otherwise), then opens the HTTPS iframe with the current theme, replaces `{userAddress}` tokens with the signed-in address, and submits automatically when the user finishes.
Bitsocial communities can require users to solve one or more anti-spam challenges before a publication is accepted. 5chan already supports multiple challenge types, including `url/iframe` challenges so [Mintpass](https://github.com/bitsocialhq/mintpass) communities can run their iframe flow directly inside a modal. The modal first shows a hostname confirmation (showing only the host for mintpass.org, full URL otherwise), then opens the HTTPS iframe with the current theme, replaces `{userAddress}` tokens with the signed-in address, and submits automatically when the user finishes.
### Build
-813
View File
@@ -1,813 +0,0 @@
# React guide
A lot of AI-generated React code is heavily biased toward using useState and useEffect, often in verbose or naive ways. That’s because these hooks are:
1. Explicit, making them easy for LLMs to pattern-match.
2. Safe defaults, requiring less architectural decision-making.
3. Well-documented, so the model’s training data is oversaturated with examples.
But modern, well-architected React apps rarely rely on raw useState/useEffect beyond simple UI state or lifecycle quirks. Here’s a breakdown of what serious devs use instead—and why this is the real moat for human engineers.
Alternatives to useState and useEffect
1. State Machines / Statecharts (e.g. Zustand)
- Why: Replaces ad-hoc local state management with deterministic, declarative logic.
- Advantage: Less bugs, easier to test, clearer state transitions.
- AI gap: LLMs struggle to model state transitions declaratively. LLM Agent must overcome this.
2. React Query / SWR for Data Fetching
- Why: Avoids useEffect-based data fetching and loading/error state juggling.
- Advantage: Caching, stale-while-revalidate, retries, pagination—all handled out of the box.
- AI gap: AI often uses useEffect + fetch and forgets cleanup, race conditions, or caching.
3. Global State with Zustand, Jotai, Recoil, or Redux Toolkit
- Why: Local useState doesn’t scale for shared or persistent state.
- Advantage: Better performance, devtools support, persistence.
- AI gap: Defaults to prop-drilling or bloated context APIs.
4. Custom Hooks and Composition
- Why: Reuse logic cleanly without bloating components or copy-pasting useEffect.
- Advantage: Separation of concerns, encapsulation.
- AI gap: Often fails to factor logic out, keeping everything in one component.
## Why This Matters
AI can mimic patterns, but it can’t:
- Architect a system for long-term maintainability.
- Predict runtime performance tradeoffs.
- Refactor spaghetti effects into custom hooks or state machines.
- Decide when state should be colocated vs globalized.
- Avoid footguns like stale closures, unnecessary re-renders, or race conditions.
In short: AI is great at writing code, but bad at engineering software.
Takeaway
- Use fewer useEffect and useState hooks.
- Learn to design systems, not just code components.
- Reach for declarative, composable patterns.
- Understand caching, reactivity, and scalability beyond local state.
Here’s a side-by-side breakdown of common AI-generated React patterns (bad) vs what experienced engineers write (good) across 4 key areas: state management, data fetching, side effects, and logic reuse.
## 1. State Management
❌ Bad (AI-style useState)
```typescript
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
setTodos([...todos, { text: input }]);
setInput('');
};
return (
<>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, i) => <li key={i}>{todo.text}</li>)}
</ul>
</>
);
}
```
✅ Good (Zustand store, state extracted)
```typescript
import { create } from 'zustand';
const useTodoStore = create((set) => ({
todos: [],
input: '',
addTodo: () => set((state) => ({
todos: [...state.todos, { text: state.input }],
input: '',
})),
setInput: (val) => set({ input: val }),
}));
function TodoApp() {
const { todos, input, setInput, addTodo } = useTodoStore();
return (
<>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button onClick={addTodo}>Add</button>
<ul>
{todos.map((todo, i) => <li key={i}>{todo.text}</li>)}
</ul>
</>
);
}
```
## 2. Data Fetching
❌ Bad (AI-style useEffect)
```typescript
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(setUsers);
}, []);
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
```
✅ Good (React Query)
```typescript
import { useQuery } from '@tanstack/react-query';
function UserList() {
const { data: users = [], isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(res => res.json()),
});
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
```
## 3. Side Effects
❌ Bad (naive effect with race condition)
```typescript
function Search({ term }) {
const [results, setResults] = useState([]);
useEffect(() => {
fetch(`/api/search?q=${term}`)
.then(res => res.json())
.then(setResults);
}, [term]);
return <div>{results.length} results</div>;
}
```
✅ Good (abort previous fetch with AbortController)
```typescript
function Search({ term }) {
const [results, setResults] = useState([]);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${term}`, { signal: controller.signal })
.then(res => res.json())
.then(setResults)
.catch((e) => {
if (e.name !== 'AbortError') console.error(e);
});
return () => controller.abort();
}, [term]);
return <div>{results.length} results</div>;
}
```
Better yet? Don’t use useEffect at all. Use React Query with term as a key.
## 4. Logic Reuse
❌ Bad (repeated logic inline)
```typescript
function ComponentA() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
}
function ComponentB() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
}
```
✅ Good (custom hook)
```typescript
function useCounter(intervalMs = 1000) {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), intervalMs);
return () => clearInterval(id);
}, [intervalMs]);
return count;
}
function ComponentA() {
const count = useCounter();
return <div>{count}</div>;
}
function ComponentB() {
const count = useCounter();
return <div>{count}</div>;
}
```
## 5. State Machines with Zustand
❌ Bad (AI-style complex useState with boolean flags)
```typescript
function OrderProcess() {
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [cart, setCart] = useState([]);
const [paymentDetails, setPaymentDetails] = useState(null);
const [orderPlaced, setOrderPlaced] = useState(false);
const addToCart = (item) => {
setCart([...cart, item]);
};
const submitPayment = async (details) => {
setIsLoading(true);
setIsError(false);
try {
await processPayment(details);
setPaymentDetails(details);
setIsSuccess(true);
setIsLoading(false);
} catch (error) {
setIsError(true);
setIsLoading(false);
}
};
const placeOrder = async () => {
setIsLoading(true);
setIsError(false);
try {
await submitOrder(cart, paymentDetails);
setOrderPlaced(true);
setIsSuccess(true);
setIsLoading(false);
} catch (error) {
setIsError(true);
setIsLoading(false);
}
};
return (
<div>
{/* Complex conditional rendering based on multiple state variables */}
{isLoading && <LoadingSpinner />}
{isError && <ErrorMessage />}
{!isLoading && !isError && !orderPlaced && (
<>
<CartItems items={cart} onAddItem={addToCart} />
<PaymentForm onSubmit={submitPayment} />
{paymentDetails && <OrderButton onClick={placeOrder} />}
</>
)}
{orderPlaced && <OrderConfirmation />}
</div>
);
}
```
✅ Good (Zustand state machine)
```typescript
import { create } from 'zustand';
// Create a Zustand store with state machine pattern
const useOrderStore = create((set, get) => ({
// State machine's current state
state: 'browsing',
// Context/data
cart: [],
paymentDetails: null,
error: null,
// Actions that transition between states
addToCart: (item) => set((state) => ({
cart: [...state.cart, item]
})),
goToCheckout: () => set({ state: 'paymentEntry' }),
goBack: () => {
const { state: currentState } = get();
if (currentState === 'paymentEntry') {
set({ state: 'browsing' });
} else if (currentState === 'confirmOrder') {
set({ state: 'paymentEntry' });
}
},
submitPayment: async (details) => {
set({ state: 'processingPayment', error: null });
try {
const result = await processPayment(details);
set({
state: 'confirmOrder',
paymentDetails: result
});
} catch (error) {
set({
state: 'paymentEntry',
error: error.message
});
}
},
placeOrder: async () => {
const { cart, paymentDetails } = get();
set({ state: 'processingOrder', error: null });
try {
await submitOrder(cart, paymentDetails);
set({ state: 'orderComplete' });
} catch (error) {
set({
state: 'confirmOrder',
error: error.message
});
}
}
}));
function OrderProcess() {
const {
state,
cart,
paymentDetails,
error,
addToCart,
goToCheckout,
goBack,
submitPayment,
placeOrder
} = useOrderStore();
// Render UI based on current state
return (
<div>
{error && <ErrorMessage message={error} />}
{state === 'browsing' && (
<CartItems
items={cart}
onAddItem={addToCart}
onCheckout={goToCheckout}
/>
)}
{state === 'paymentEntry' && (
<PaymentForm
onSubmit={submitPayment}
onBack={goBack}
/>
)}
{state === 'processingPayment' && (
<LoadingSpinner message="Processing payment..." />
)}
{state === 'confirmOrder' && (
<OrderSummary
cart={cart}
paymentDetails={paymentDetails}
onConfirm={placeOrder}
onBack={goBack}
/>
)}
{state === 'processingOrder' && (
<LoadingSpinner message="Placing your order..." />
)}
{state === 'orderComplete' && (
<OrderConfirmation orderDetails={{ cart, paymentDetails }} />
)}
</div>
);
}
```
### Key Benefits of the Zustand State Machine Approach:
1. **Explicit states**: The system can only be in one well-defined state at a time
2. **Centralized logic**: All state transitions are defined in one place
3. **Predictable transitions**: State changes follow clear patterns
4. **Simplified UI logic**: Components just render based on the current state
5. **Async handling**: Asynchronous operations are contained within the state transitions
6. **Shared state**: Any component can access the store without prop drilling
7. **Improved debugging**: The current state is always clear and predictable
8. **No impossible states**: Unlike boolean flags that could create invalid combinations
9. **Simpler API**: Compared to XState, Zustand offers a more approachable API for many developers
10. **Smaller bundle size**: Zustand is significantly smaller than XState
This approach gives most of the benefits of formal state machines while maintaining the simplicity and familiarity of React state management. It's a pragmatic middle ground that's often easier to adopt in existing projects.
## 6. Concurrent UI Techniques
❌ Bad (Blocking UI updates without concurrency):
```jsx
// Bad example: Synchronously fetching data in useEffect can cause UI jank.
function SearchResults({ query }) {
const [results, setResults] = React.useState([]);
React.useEffect(() => {
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, [query]);
return (
<div>
{results.map(item => <p key={item.id}>{item.title}</p>)}
</div>
);
}
```
✅ Good (Using useTransition for non-blocking updates):
```jsx
// Good example: Leveraging useTransition to mark state updates as low-priority.
function SearchResults({ query }) {
const [results, setResults] = React.useState([]);
const [isPending, startTransition] = React.useTransition();
React.useEffect(() => {
startTransition(() => {
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
});
}, [query, startTransition]);
return (
<div>
{isPending && <div>Loading...</div>}
{results.map(item => <p key={item.id}>{item.title}</p>)}
</div>
);
}
```
## 7. Routing and Navigation (Not applicable with Next.js apps)
❌ Bad (Manual routing without a dedicated library):
```jsx
// Bad example: Handling routing manually using state and window history.
function App() {
const [route, setRoute] = React.useState(window.location.pathname);
React.useEffect(() => {
const onPopState = () => setRoute(window.location.pathname);
window.addEventListener('popstate', onPopState);
return () => window.removeEventListener('popstate', onPopState);
}, []);
return (
<div>
{route === '/' && <Home />}
{route === '/about' && <About />}
<button onClick={() => {
window.history.pushState({}, '', '/about');
setRoute('/about');
}}>
About
</button>
</div>
);
}
```
✅ Good (Using React Router v6 with lazy-loaded routes):
```jsx
// Good example: Utilizing React Router to manage routes and lazy load components.
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import React, { lazy, Suspense } from 'react';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
```
## 8. Error Handling
❌ Bad (No error boundary; crashes on errors):
```jsx
// Bad example: A component that throws errors without an error boundary.
function MyComponent({ data }) {
if (!data) {
throw new Error("Data not found");
}
return <div>{data}</div>;
}
function App() {
return <MyComponent data={null} />;
}
```
✅ Good (Wrapping components with an Error Boundary):
```jsx
// Good example: Using an ErrorBoundary component to catch and handle errors gracefully.
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error caught in boundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function MyComponent({ data }) {
if (!data) {
throw new Error("Data not found");
}
return <div>{data}</div>;
}
function App() {
return (
<ErrorBoundary>
<MyComponent data={null} />
</ErrorBoundary>
);
}
```
## 9. Form Management
❌ Bad (Manual state handling with no validation):
```jsx
// Bad example: Uncontrolled form without validation; error handling is omitted.
function ContactForm() {
const [formData, setFormData] = React.useState({ name: '', email: '' });
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="Name"
/>
<input
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
/>
<button type="submit">Submit</button>
</form>
);
}
```
✅ Good (Using React Hook Form with Zod for schema validation):
```jsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// 1. Define your schema with Zod
const contactSchema = z.object({
name: z.string().nonempty('Name is required'),
email: z
.string()
.nonempty('Email is required')
.email('Invalid email'),
});
// 2. Infer the TypeScript type of your form data
type ContactFormData = z.infer<typeof contactSchema>;
export function ContactForm() {
// 3. Hook up RHF with the zodResolver
const {
register,
handleSubmit,
formState: { errors },
} = useForm<ContactFormData>({
resolver: zodResolver(contactSchema),
});
const onSubmit = (data: ContactFormData) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<input
{...register('name')}
placeholder="Name"
aria-invalid={!!errors.name}
/>
{errors.name && <p role="alert">{errors.name.message}</p>}
</div>
<div>
<input
{...register('email')}
placeholder="Email"
aria-invalid={!!errors.email}
/>
{errors.email && <p role="alert">{errors.email.message}</p>}
</div>
<button type="submit">Submit</button>
</form>
);
}
```
## 10. Code Splitting and Lazy Loading
❌ Bad (Eagerly importing heavy components, increasing the initial bundle size):
```jsx
// Bad example: Directly importing a large component causing a heavier bundle.
import HeavyComponent from './HeavyComponent';
function App() {
return (
<div>
<HeavyComponent />
</div>
);
}
```
✅ Good (Lazy loading heavy components with React.lazy and Suspense):
```jsx
// Good example: Dynamically importing components to reduce initial load time.
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading Component...</div>}>
<HeavyComponent />
</Suspense>
);
}
```
## 11. Testing Strategies
❌ Bad (Testing implementation details, resulting in brittle tests):
```jsx
// Bad example: Relying on internal DOM structure which may change.
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';
test('MyComponent renders data', () => {
const { container } = render(<MyComponent />);
expect(container.querySelector('.data')).toBeDefined();
});
```
✅ Good (Focusing on user-centric behaviors using React Testing Library):
```jsx
// Good example: Testing component behavior by simulating user interactions.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MyComponent from './MyComponent';
test('MyComponent displays loaded data after user action', async () => {
render(<MyComponent />);
// Check for an initial loading state
expect(screen.getByText(/Loading/i)).toBeInTheDocument();
// Simulate user action that triggers data loading
const button = screen.getByRole('button', { name: /load data/i });
userEvent.click(button);
// Wait and assert that data is displayed
expect(await screen.findByText(/Data loaded/i)).toBeInTheDocument();
});
```
## 12. Accessibility & Theming
❌ Bad (Non-semantic elements and missing ARIA labels):
```jsx
// Bad example: Using a div as a clickable element without semantics or accessibility.
function ThemedButton() {
return <div onClick={() => console.log('Clicked!')}>Click me</div>;
}
```
✅ Good (Semantic button with ARIA attributes and theming via CSS variables):
```jsx
// Good example: Accessible button component that utilizes theming.
function ThemedButton() {
return (
<button
aria-label="Click me"
onClick={() => console.log('Clicked!')}
style={{ padding: '8px 16px', backgroundColor: 'var(--primary-color)', color: '#fff' }}
>
Click me
</button>
);
}
/* CSS (in a separate file or styled-components):
:root {
--primary-color: #007bff;
}
*/
```
# Summary Table
| Concern | Bad AI Pattern | Solid Engineering Practice |
|---------|---------------|----------------------------|
| **State Management** | `useState` everywhere, prop‑drilling | Central store (Zustand / Redux Toolkit) or colocated custom hooks |
| **Data Fetching** | `useEffect` + `fetch`, manual loading/error juggling | React Query / SWR with caching, retries, stale‑while‑revalidate |
| **Side Effects** | Effects without cleanup → race conditions, leaks | AbortController or query library handles cancellation & retries |
| **Logic Reuse** | Copy‑pasted hooks and state in every component | Extract cross‑cutting logic into custom hooks |
| **Workflow / State Machine** | Boolean‑flag soup, impossible states | Deterministic statechart in a single store (Zustand / XState) |
| **Concurrent UI Updates** | Blocking synchronous work inside effects | `useTransition` / `startTransition` for low‑priority updates |
| **Routing & Navigation** | DIY history manipulation, inline route state | React Router v6 (or Next.js built‑ins) with lazy‑loaded routes |
| **Error Handling** | No error boundary → full‑app crashes | Top‑level `ErrorBoundary` plus logging in `componentDidCatch` |
| **Form Management** | Manual state, zero validation or feedback | React Hook Form + Yup/Zod schema for declarative validation |
| **Code Splitting & Lazy Loading** | Eager imports ballooning bundle size | `React.lazy` + `Suspense` for on‑demand loading |
| **Testing Strategy** | Brittle tests keyed to DOM structure | User‑centric tests with React Testing Library & jest‑dom |
| **Accessibility & Theming** | Non‑semantic elements, missing ARIA, inline colors | Semantic HTML, ARIA labels, CSS variables / theme provider |
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "5chan",
"version": "0.6.1",
"version": "0.6.2",
"description": "A bitsocial client with a 4chan UI",
"type": "module",
"author": "Bitsocial Labs",