${{ matrix.commit.message }}" >> message.txt
-# - run: echo "" >> message.txt
-# - run: echo "by ${{ matrix.commit.author.username }}" >> message.txt
-# - run: echo "${{ matrix.commit.url }}" >> message.txt
-# - run: date -d "${{ matrix.commit.timestamp }}" +"%d/%m/%y at %H:%M" >> message.txt
-
-# # send message, @plebbit telegram chat id is -1001665335693
-# - name: "telegram notification"
-# uses: appleboy/telegram-action@master
-# with:
-# to: -1001665335693
-# token: ${{ secrets.TELEGRAM_TOKEN }}
-# # commit links don't show anything useful in preview
-# disable_web_page_preview: true
-# format: html
-# message_file: message.txt
-
- release:
- if: ${{ github.event_name == 'release' }}
- runs-on: ubuntu-latest
- steps:
- # - name: debug
- # env:
- # DEBUG: ${{ toJSON(github) }}
- # run: echo "$DEBUG"
-
- # write message to file
- - run: echo "${{ github.event.repository.name }} ${{ github.event.release.name }}" >> message.txt
- - run: echo "" >> message.txt
- - run: echo "${{ github.event.release.body }}" >> message.txt
- - run: echo "${{ github.event.release.html_url }}" >> message.txt
-
- # send message, @plebbit telegram chat id is -1001665335693
- - name: "telegram notification"
- uses: appleboy/telegram-action@master
- with:
- to: -1001665335693
- token: ${{ secrets.TELEGRAM_TOKEN }}
- format: html
- message_file: message.txt
-
- issue:
- if: ${{ github.event_name == 'issues' }}
- runs-on: ubuntu-latest
- steps:
- # - name: debug
- # env:
- # DEBUG: ${{ toJSON(github) }}
- # run: echo "$DEBUG"
-
- # write message to file
- - run: echo "${{ github.event.issue.title }}" >> message.txt
- - run: echo "" >> message.txt
- - run: echo "by ${{ github.event.issue.user.login }}" >> message.txt
- - run: echo "${{ github.event.issue.html_url }}" >> message.txt
-
- # send message, @plebbit telegram chat id is -1001665335693
- - name: "telegram notification"
- uses: appleboy/telegram-action@master
- with:
- to: -1001665335693
- token: ${{ secrets.TELEGRAM_TOKEN }}
- format: html
- message_file: message.txt
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7b302121..d72782ba 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -60,9 +60,9 @@ jobs:
fail-fast: false
matrix:
include:
- - runner: macOS-13 # Intel x64
+ - runner: macos-15-intel # Intel x64
arch: x64
- - runner: macOS-14 # Apple Silicon arm64
+ - runner: macos-latest # Apple Silicon arm64
arch: arm64
runs-on: ${{ matrix.runner }}
permissions:
@@ -82,6 +82,12 @@ jobs:
with:
python-version: '3.12'
+ # install missing dep for sqlite (use setup-python to avoid PEP 668 externally-managed-environment error)
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ - run: pip install setuptools
+
- name: Install dependencies (with Node v22)
run: yarn install --frozen-lockfile --ignore-engines
# make sure the ipfs executable is executable
@@ -177,8 +183,7 @@ jobs:
- run: sed -i "s/versionName \"1.0\"/versionName \"$(node -e "console.log(require('./package.json').version)")\"/" ./android/app/build.gradle
- run: cat ./android/app/build.gradle
# build apk
- - run: npx cap update
- - run: npx cap copy
+ - run: npx cap sync android
- run: cd android && gradle bundle
- run: cd android && ./gradlew assembleRelease --stacktrace
# optimize apk
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 68c4ee9c..887ba26c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -25,8 +25,8 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/electron
- key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
- restore-keys: ${{ runner.os }}-electron-
+ key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies
run: |
@@ -37,37 +37,45 @@ jobs:
[ "$i" = "3" ] && exit 1
done
- - name: Download IPFS
- run: node electron/download-ipfs && chmod +x bin/linux/ipfs
-
- name: Build React App
run: yarn build
env:
CI: ''
- - name: Build Electron App (Linux)
- run: yarn electron:build:linux
+ - name: Build Preload Script
+ run: yarn build:preload
- - name: Smoke Test
+ - name: Verify build outputs
run: |
- echo "Testing AppImage startup..."
- APPIMAGE=$(find dist -name "*.AppImage" | head -n 1)
- echo "Found AppImage: $APPIMAGE"
- chmod +x "$APPIMAGE"
- # Use --appimage-extract-and-run to avoid FUSE requirement in CI
- # Run with timeout and expect it to start (will be killed after timeout)
- timeout 10s "$APPIMAGE" --appimage-extract-and-run --no-sandbox &
- APP_PID=$!
- sleep 5
- # Check if process is still running (means it started successfully)
- if kill -0 $APP_PID 2>/dev/null; then
- echo "✓ App started successfully"
- kill $APP_PID 2>/dev/null || true
- exit 0
- else
- echo "✗ App failed to start"
- exit 1
- fi
+ echo "=== Checking build/ directory ==="
+ ls -la build/ || exit 1
+ echo "=== Checking build/electron/preload.cjs ==="
+ ls -la build/electron/preload.cjs || exit 1
+ echo "=== Checking build/index.html ==="
+ ls -la build/index.html || exit 1
+
+ - name: Verify forge config
+ run: |
+ echo "=== forge.config.js exists ==="
+ ls -la forge.config.js
+ echo "=== Validating forge config can be loaded ==="
+ node -e "import('./forge.config.js').then(c => console.log('Config loaded, makers:', c.default.makers?.length || 0)).catch(e => { console.error(e); process.exit(1); })"
+
+ - name: Package Electron App
+ timeout-minutes: 30
+ run: |
+ set -o pipefail
+ yarn electron-forge package 2>&1 | tee forge-output.log
+ echo "=== Checking out/ directory ==="
+ ls -la out/
+
+ - name: Verify Executable
+ run: |
+ ls -la out/
+ EXE=$(node scripts/find-forge-executable.js)
+ echo "Found executable: $EXE"
+ # Verify the file exists and is executable
+ test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
test-mac-intel:
name: Test Mac (Intel)
@@ -76,24 +84,22 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
-
+
- name: Setup Node.js v22
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'yarn'
-
+
- name: Install setuptools for native modules
- run: |
- # Use pip with --break-system-packages for CI environment
- pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
+ run: pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
- name: Cache electron binaries
uses: actions/cache@v4
with:
path: ~/Library/Caches/electron
- key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
- restore-keys: ${{ runner.os }}-electron-
+ key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies
run: |
@@ -104,54 +110,42 @@ jobs:
[ "$i" = "3" ] && exit 1
done
- - name: Download IPFS
- run: node electron/download-ipfs && chmod +x bin/mac/ipfs
-
- name: Build React App
run: yarn build
env:
CI: ''
- - name: Build Electron App
- # Use --dir to build only the .app bundle without DMG
- # This avoids flaky hdiutil "Resource busy" errors in CI
- run: yarn build && yarn build:preload && yarn electron-builder build --publish never -m --dir
+ - name: Build Preload Script
+ run: yarn build:preload
- - name: Smoke Test
+ - name: Verify build outputs
run: |
- if [ -d "dist/mac/seedit.app" ]; then
- echo "Testing dist/mac/seedit.app..."
- # Run the app in background - it will start IPFS which takes time
- # We just verify it launches without crashing
- ./dist/mac/seedit.app/Contents/MacOS/seedit &
- APP_PID=$!
- sleep 10
- # Check if process is still running (means it started successfully)
- if kill -0 $APP_PID 2>/dev/null; then
- echo "✓ App started successfully"
- kill $APP_PID 2>/dev/null || true
- # Also kill any child processes (IPFS)
- pkill -P $APP_PID 2>/dev/null || true
- pkill -f ipfs 2>/dev/null || true
- exit 0
- else
- echo "✗ App failed to start or crashed"
- exit 1
- fi
- else
- echo "Could not find dist/mac/seedit.app to test"
- ls -R dist
- exit 1
- fi
+ ls -la build/ || exit 1
+ ls -la build/electron/preload.cjs || exit 1
+ ls -la build/index.html || exit 1
+
+ - name: Package Electron App
+ timeout-minutes: 30
+ run: |
+ set -o pipefail
+ yarn electron-forge package 2>&1 | tee forge-output.log
+ ls -la out/
+
+ - name: Verify Executable
+ run: |
+ ls -la out/
+ EXE=$(node scripts/find-forge-executable.js)
+ echo "Found executable: $EXE"
+ test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
test-mac-arm:
name: Test Mac (Apple Silicon)
- runs-on: macOS-14
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
-
+
- name: Setup Node.js v22
uses: actions/setup-node@v4
with:
@@ -159,16 +153,14 @@ jobs:
cache: 'yarn'
- name: Install setuptools for native modules
- run: |
- # Use pip with --break-system-packages for CI environment
- pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
+ run: pip3 install --break-system-packages setuptools || pip3 install --user setuptools || true
- name: Cache electron binaries
uses: actions/cache@v4
with:
path: ~/Library/Caches/electron
- key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
- restore-keys: ${{ runner.os }}-electron-
+ key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies
run: |
@@ -179,50 +171,33 @@ jobs:
[ "$i" = "3" ] && exit 1
done
- - name: Download IPFS
- run: node electron/download-ipfs && chmod +x bin/mac/ipfs
-
- name: Build React App
run: yarn build
env:
CI: ''
- - name: Build Electron App
- # On M1 runner, this should produce arm64 build
- # Use --dir to build only the .app bundle without DMG
- # This avoids flaky hdiutil "Resource busy" errors in CI
- run: yarn build && yarn build:preload && yarn electron-builder build --publish never -m --dir
+ - name: Build Preload Script
+ run: yarn build:preload
- - name: Smoke Test
+ - name: Verify build outputs
run: |
- if [ -d "dist/mac-arm64/seedit.app" ]; then
- APP_PATH="dist/mac-arm64/seedit.app"
- elif [ -d "dist/mac/seedit.app" ]; then
- APP_PATH="dist/mac/seedit.app"
- else
- echo "Could not find seedit.app to test"
- ls -R dist
- exit 1
- fi
-
- echo "Testing $APP_PATH..."
- # Run the app in background - it will start IPFS which takes time
- # We just verify it launches without crashing
- "./$APP_PATH/Contents/MacOS/seedit" &
- APP_PID=$!
- sleep 10
- # Check if process is still running (means it started successfully)
- if kill -0 $APP_PID 2>/dev/null; then
- echo "✓ App started successfully"
- kill $APP_PID 2>/dev/null || true
- # Also kill any child processes (IPFS)
- pkill -P $APP_PID 2>/dev/null || true
- pkill -f ipfs 2>/dev/null || true
- exit 0
- else
- echo "✗ App failed to start or crashed"
- exit 1
- fi
+ ls -la build/ || exit 1
+ ls -la build/electron/preload.cjs || exit 1
+ ls -la build/index.html || exit 1
+
+ - name: Package Electron App
+ timeout-minutes: 30
+ run: |
+ set -o pipefail
+ yarn electron-forge package 2>&1 | tee forge-output.log
+ ls -la out/
+
+ - name: Verify Executable
+ run: |
+ ls -la out/
+ EXE=$(node scripts/find-forge-executable.js)
+ echo "Found executable: $EXE"
+ test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
test-windows:
name: Test Windows
@@ -231,7 +206,7 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
-
+
- name: Setup Node.js v22
uses: actions/setup-node@v4
with:
@@ -242,8 +217,8 @@ jobs:
uses: actions/cache@v4
with:
path: ~/AppData/Local/electron/Cache
- key: ${{ runner.os }}-electron-${{ hashFiles('**/yarn.lock') }}
- restore-keys: ${{ runner.os }}-electron-
+ key: ${{ runner.os }}-electron-v2-${{ hashFiles('**/yarn.lock') }}
+ restore-keys: ${{ runner.os }}-electron-v2-
- name: Install dependencies
shell: bash
@@ -255,41 +230,35 @@ jobs:
[ "$i" = "3" ] && exit 1
done
- - name: Download IPFS
- run: node electron/download-ipfs
-
- name: Build React App
+ shell: bash
run: yarn build
env:
CI: ''
- - name: Build Electron App
- run: yarn electron:build:windows
- timeout-minutes: 30
+ - name: Build Preload Script
+ shell: bash
+ run: yarn build:preload
- - name: Smoke Test
+ - name: Verify build outputs
shell: bash
run: |
- # Try to find the unpacked executable first as it's easiest to run
- if [ -d "dist/win-unpacked" ]; then
- echo "Testing unpacked exe..."
- # Run with timeout - app starts IPFS so won't exit on its own
- timeout 15s ./dist/win-unpacked/seedit.exe &
- APP_PID=$!
- sleep 8
- # Check if process started successfully
- if kill -0 $APP_PID 2>/dev/null; then
- echo "✓ App started successfully"
- taskkill //F //PID $APP_PID 2>/dev/null || true
- # Kill any IPFS processes
- taskkill //F //IM ipfs.exe 2>/dev/null || true
- exit 0
- else
- echo "✗ App failed to start"
- exit 1
- fi
- else
- echo "No unpacked directory found"
- ls -R dist
- exit 1
- fi
+ ls -la build/ || exit 1
+ ls -la build/electron/preload.cjs || exit 1
+ ls -la build/index.html || exit 1
+
+ - name: Package Electron App
+ shell: bash
+ timeout-minutes: 30
+ run: |
+ set -o pipefail
+ yarn electron-forge package 2>&1 | tee forge-output.log
+ ls -la out/
+
+ - name: Verify Executable
+ shell: bash
+ run: |
+ ls -la out/
+ EXE=$(node scripts/find-forge-executable.js)
+ echo "Found executable: $EXE"
+ test -f "$EXE" && test -x "$EXE" && echo "✓ Executable is valid"
diff --git a/.gitignore b/.gitignore
index 865c0404..6a04da5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -97,6 +97,9 @@ out
.nuxt
dist
+# Electron Forge output
+squashfs-root
+
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
diff --git a/AGENTS.md b/AGENTS.md
index 6ddbc6a3..c5481351 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -31,8 +31,9 @@ yarn electron # Run Electron app
## Code Style
- TypeScript strict mode
-- Prettier for formatting (runs on pre-commit)
-- Follow DRY principle—extract reusable components
+- **oxfmt** for formatting (runs on pre-commit via husky; recommend setting up AI hooks too)
+- **oxlint** for linting, **tsgo** for type-checking
+- **DRY principle**: Always follow the DRY principle when possible. 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 +79,94 @@ 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
+```
+
+## AI Agent Hooks (Recommended)
+
+If you're using an AI coding assistant (Cursor, Claude Code, Codex, etc.), set up hooks to automatically enforce code quality. Most modern AI agents support lifecycle hooks.
+
+### Recommended Hooks
+
+Set up these hooks for this project:
+
+| Hook | Command | Purpose |
+|------|---------|---------|
+| `afterFileEdit` | `npx oxfmt
_Telegram group for this repo https://t.me/seeditreact_
# Seedit
-Seedit is a serverless, adminless, decentralized reddit alternative. Seedit is a client (interface) for the Plebbit protocol, which is a decentralized social network where anyone can create and fully own unstoppable communities. Learn more: https://plebbit.com
+Seedit is a serverless, adminless, decentralized and open-source (old)reddit alternative built on the [Bitsocial protocol](https://bitsocial.net). Like reddit, anyone can create a seedit community, It features a similar homepage structure as reddit, but with a crucial difference: **anyone can create and own communities, and multiple communities can compete for each default community slot**.
- Seedit web version: https://seedit.app — or, using Brave/IPFS Companion: https://seedit.eth
### Downloads
-- Seedit desktop version (full p2p plebbit node, seeds automatically): available for Mac/Windows/Linux, [download link in the release page](https://github.com/plebbit/seedit/releases/latest)
-- Seedit mobile version: available for Android, [download link in the release page](https://github.com/plebbit/seedit/releases/latest)
+- Seedit desktop version (full p2p bitsocial node, seeds automatically): available for Mac/Windows/Linux, [download link in the release page](https://github.com/bitsocialhq/seedit/releases/latest)
+- Seedit mobile version: available for Android, [download link in the release page](https://github.com/bitsocialhq/seedit/releases/latest)
## How to create a community
-In the plebbit protocol, a seedit community is called a _subplebbit_. To run a subplebbit, you can choose between two options:
+To run a community, you can choose between two options:
-1. If you prefer to use a **GUI**, download the desktop version of the Seedit client, available for Windows, MacOS and Linux: [latest release](https://github.com/plebbit/seedit/releases/latest). Create a subplebbit using using the familiar old.reddit-like UI, and modify its settings to your liking. The app runs an IPFS node, meaning you have to keep it running to have your board online.
-2. If you prefer to use a **command line interface**, install plebbit-cli, available for Windows, MacOS and Linux: [latest release](https://github.com/plebbit/plebbit-cli/releases/latest). Follow the instructions in the readme of the repo. When running the daemon for the first time, it will output WebUI links you can use to manage your subplebbit with the ease of the GUI.
+1. If you prefer to use a **GUI**, download the desktop version of the Seedit client, available for Windows, MacOS and Linux: [latest release](https://github.com/bitsocialhq/seedit/releases/latest). Create a community using using the familiar old.reddit-like UI, and modify its settings to your liking. The app runs an IPFS node, meaning you have to keep it running to have your board online.
+2. If you prefer to use a **command line interface**, install bitsocial-cli, available for Windows, MacOS and Linux: [latest release](https://github.com/bitsocialhq/bitsocial-cli/releases/latest). Follow the instructions in the readme of the repo. When running the daemon for the first time, it will output WebUI links you can use to manage your community with the ease of the GUI.
-Peers can connect to your subplebbit using any plebbit client, such as Plebchan or Seedit. They only need the subplebbit's address, which is not stored in any central database, as plebbit is a pure peer-to-peer protocol.
+Peers can connect to your bitsocial community using any bitsocial client, such as Seedit or [5chan](https://github.com/bitsocialhq/5chan). They only need the community address, which is not stored in any central database, as bitsocial is a pure peer-to-peer protocol.
-### How to add a community to the default list (p/all)
-The default list of communities, used on p/all on Seedit, is plebbit's [temporary default subplebbits](https://github.com/plebbit/lists) list. You can open a pull request in that repo to add your subplebbit to the list, or contact devs via telegram [@plebbit](https://t.me/plebbit). In the future, this process will be automated by submitting proposals to a plebbit DAO, using the [plebbit token](https://etherscan.io/token/0xea81dab2e0ecbc6b5c4172de4c22b6ef6e55bd8f).
+### How to add a community to the default list (s/all)
+The default list of communities, used on s/all on Seedit, is bitsocial's [default-multisub.json list](https://github.com/bitsocialhq/lists/blob/master/default-multisub.json). You can open a pull request in that repo to add your community to the list.
## To run locally
@@ -44,4 +49,4 @@ The default list of communities, used on p/all on Seedit, is plebbit's [temporar
### Build:
-The linux/windows/mac/android build scripts are in https://github.com/plebbit/seedit/blob/master/.github/workflows/release.yml
+The linux/windows/mac/android build scripts are in https://github.com/bitsocialhq/seedit/blob/master/.github/workflows/release.yml
diff --git a/android/plebbit.keystore b/android/plebbit.keystore
index 2acc2e33..4f98bb21 100644
Binary files a/android/plebbit.keystore and b/android/plebbit.keystore differ
diff --git a/docs/react-guide.md b/docs/react-guide.md
deleted file mode 100644
index 957de419..00000000
--- a/docs/react-guide.md
+++ /dev/null
@@ -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 (
- <>
- setInput(e.target.value)} />
-
- {item.title}
)} -{item.title}
)} -{children}
; }, - img: ({ src, alt }) => { + img: ({ src, alt }: { src?: string; alt?: string }) => { const displayText = src || alt || 'image'; return {displayText}; }, - video: ({ src }) => {src}, - iframe: ({ src }) => {src}, - source: ({ src }) => {src}, - spoiler: ({ children }) =>