remove superseded design notes
CLAUDE.md already told readers these were "older design notes" and to treat the code as
the source of truth. Keeping them is worse than that warning: they are detailed enough to
be believed, and every one of them describes an app that has since changed.
- OFFICERDEV_BACKEND.md, OFFICERDEV_FRONTEND.md — architecture snapshots from February;
CLAUDE.md and the code cover this now.
- SETUP_ANALYSIS.md — an analysis of problems in setup.sh, superseded by `bun setup`.
- event-handler-instances.md — the result of a one-off scan ("found 167 instances"),
regenerable with a grep in less time than reading it.
- HOOKS.md — a bare list of hook file paths, likewise.
- docs/per-user-api-keys.md — an architecture analysis for per-user API keys, which
contradicts the single-user hard invariant in CLAUDE.md.
Kept deliberately, though all are old: SECURITY_AUDIT.md and SECURITY_FIXES.md (a
findings record and its remediation log — not the kind of thing to bin on a hunch),
PHONE_APP.md (a native app is being built), MARKETING_WEBSITE.md and
docs/DOCKERIZATION_PLAN.md (plans that may not have been carried out yet), and
docs/jobs-unification.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
## Authentication
|
||||
|
||||
src/apps/officer-web/Screens/Authentication/ForgotPassword/useResetPassword.ts
|
||||
src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts
|
||||
|
||||
## Files
|
||||
|
||||
src/apps/officer-web/Screens/Dashboard/Files/state/usePinnedFiles.ts
|
||||
src/apps/officer-web/Screens/Dashboard/Files/state/useRecentFiles.ts
|
||||
|
||||
## Officer-web State
|
||||
|
||||
src/apps/officer-web/state/useChatGroups.ts
|
||||
src/apps/officer-web/state/useChatSessions.ts
|
||||
src/apps/officer-web/state/useInitialData.ts
|
||||
src/apps/officer-web/state/useLandingPage.ts
|
||||
src/apps/officer-web/state/useModels.ts
|
||||
src/apps/officer-web/state/usePlans.ts
|
||||
src/apps/officer-web/state/useProjectsState.ts
|
||||
src/apps/officer-web/state/useRecentModels.ts
|
||||
src/apps/officer-web/state/useResources.ts
|
||||
src/apps/officer-web/state/useServerSettings.ts
|
||||
src/apps/officer-web/state/useSettings.ts
|
||||
src/apps/officer-web/state/useThemeSync.ts
|
||||
src/apps/officer-web/state/useUserState.ts
|
||||
src/apps/officer-web/state/useWorkspacesState.ts
|
||||
|
||||
## Chat (apps/Chat)
|
||||
|
||||
src/workspaces/apps/Chat/useChatSessions.ts
|
||||
src/workspaces/apps/Chat/useChatSession.ts
|
||||
src/workspaces/apps/Chat/usePi.ts
|
||||
src/workspaces/apps/Chat/useSlashCommands.ts
|
||||
|
||||
## Other Workspaces
|
||||
|
||||
src/workspaces/apps/CodeEditor/useEditorState.ts
|
||||
src/workspaces/apps/FileBrowser/useFiles.ts
|
||||
src/workspaces/apps/FileBrowser/useTasks.ts
|
||||
src/workspaces/components/DataTable/useFixedHeightPagination.ts
|
||||
src/workspaces/components/ui/hooks/use-mobile.tsx
|
||||
src/workspaces/components/ui/hooks/use-toast.ts
|
||||
src/workspaces/components/ui/use-toast.ts
|
||||
src/workspaces/i18n/src/useTranslation.ts
|
||||
src/workspaces/injector/use-client.ts
|
||||
Done!
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,310 +0,0 @@
|
||||
# Officer Setup Flow Analysis
|
||||
|
||||
## Overview
|
||||
The setup.sh script installs all dependencies for Officer, but there are several issues that can cause problems, especially with nvm Node.js environments.
|
||||
|
||||
## Setup Flow
|
||||
|
||||
```
|
||||
1. Detect package manager (apt/pacman/brew)
|
||||
↓
|
||||
2. Install core system packages (git, zip, curl, zsh, build-essential, etc.)
|
||||
↓
|
||||
3. Install archive utilities (7z, unrar)
|
||||
↓
|
||||
4. Install ffmpeg
|
||||
↓
|
||||
5. Configure sudoers for service user
|
||||
↓
|
||||
6. Install Node.js 22 (or warn if not found)
|
||||
↓
|
||||
7. Configure npm global prefix (~/.npm-global)
|
||||
↓
|
||||
8. Install Bun
|
||||
↓
|
||||
9. Install Go 1.23.6
|
||||
↓
|
||||
10. Install Rust
|
||||
↓
|
||||
11. Install PulseAudio (for audio)
|
||||
↓
|
||||
12. Build cliamp from source (Go music player)
|
||||
↓
|
||||
13. Install Neovim
|
||||
↓
|
||||
14. Install terminal tools (starship, oh-my-zsh, eza, lazygit)
|
||||
↓
|
||||
15. Install yt-dlp (optional)
|
||||
↓
|
||||
16. Install npm global packages (Pi, Claude Code, pm2)
|
||||
↓
|
||||
17. Run bun install (project dependencies)
|
||||
↓
|
||||
18. Setup remote desktop (XFCE + VNC)
|
||||
↓
|
||||
19. Setup PTY sidecar (systemd service)
|
||||
↓
|
||||
20. Verification
|
||||
```
|
||||
|
||||
## Issues Found
|
||||
|
||||
### 1. **nvm Node.js Not Properly Documented**
|
||||
|
||||
**Location:** `scripts/setup.sh` (line ~238)
|
||||
|
||||
**Problem:**
|
||||
```bash
|
||||
if has node; then
|
||||
NODE_VER=$(node -v 2>/dev/null | tr -d 'v')
|
||||
NODE_MAJOR=$(echo "$NODE_VER" | cut -d. -f1)
|
||||
if [ "$NODE_MAJOR" = "22" ]; then
|
||||
skip "node v$NODE_VER"
|
||||
else
|
||||
warn "Node $NODE_VER found but v22 is required"
|
||||
warn "Use nvm: nvm install 22 && nvm use 22"
|
||||
fi
|
||||
else
|
||||
warn "Node.js not found — install v22 via nvm:"
|
||||
warn " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash"
|
||||
warn " nvm install 22"
|
||||
fi
|
||||
```
|
||||
|
||||
**Issue:**
|
||||
- Only warns about nvm but doesn't ensure it's sourced in the current shell
|
||||
- When installing pm2 and other npm global packages, nvm might not be available
|
||||
- When services (pm2, systemd) later run, they won't have nvm initialized
|
||||
|
||||
**Impact:**
|
||||
- Users install nvm, run setup.sh in that shell, but when pm2/systemd runs later, it uses the wrong node or no node
|
||||
|
||||
---
|
||||
|
||||
### 2. **PTY Sidecar Service Uses Unbounded `which node`**
|
||||
|
||||
**Location:** `scripts/setup-pty-sidecar.sh` (line ~16)
|
||||
|
||||
**Problem:**
|
||||
```bash
|
||||
NODE_BIN="$(which node)"
|
||||
```
|
||||
|
||||
**Issue:**
|
||||
- If nvm is not sourced in the current shell, `which node` returns nothing or the system node
|
||||
- The systemd service will run with the wrong node binary
|
||||
- When systemd runs, nvm environment is not available anyway
|
||||
|
||||
**Impact:**
|
||||
- PTY sidecar service fails to start or runs with wrong node
|
||||
- Terminal functionality breaks in Officer
|
||||
|
||||
---
|
||||
|
||||
### 3. **PM2/Ecosystem Config Doesn't Handle nvm**
|
||||
|
||||
**Location:** `ecosystem.config.cjs`
|
||||
|
||||
**Problem:**
|
||||
```javascript
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'officer',
|
||||
script: 'bun',
|
||||
args: 'start',
|
||||
watch: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
**Issue:**
|
||||
- No environment setup for nvm
|
||||
- PM2 runs with whatever node is in system PATH
|
||||
- If user installed node via nvm, PM2 won't find it
|
||||
- This is why you had to restart the server after installing nvm
|
||||
|
||||
**Impact:**
|
||||
- Officer server fails to start after fresh nvm installation
|
||||
- No clear error message about nvm not being available
|
||||
|
||||
---
|
||||
|
||||
### 4. **No Documentation on Node Installation Methods**
|
||||
|
||||
**Location:** `scripts/setup.sh` (lines 238-250)
|
||||
|
||||
**Problem:**
|
||||
- Script warns about nvm but doesn't explain the workflow
|
||||
- No mention of snap node incompatibility
|
||||
- No mention of system apt/NodeSource installation
|
||||
- No guidance on which method to use when
|
||||
|
||||
**Impact:**
|
||||
- Users can choose any installation method
|
||||
- Some methods (snap) don't work with Officer
|
||||
- New issues arise from incompatible setups
|
||||
|
||||
---
|
||||
|
||||
### 5. **Pi Installation Doesn't Validate nvm Environment**
|
||||
|
||||
**Location:** `scripts/setup.sh` (lines ~408-420)
|
||||
|
||||
**Problem:**
|
||||
```bash
|
||||
if has pi; then
|
||||
skip "pi (@mariozechner/pi-coding-agent)"
|
||||
else
|
||||
npm install -g @mariozechner/pi-coding-agent
|
||||
if has pi; then ok "pi installed"; else warn "pi install failed"; fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Issue:**
|
||||
- Installs pi with `npm install -g`, but npm might be different than later shells
|
||||
- No validation that pi works (should test `pi --list-models`)
|
||||
- No check that Pi was installed to the right npm location
|
||||
|
||||
**Impact:**
|
||||
- Pi appears installed but fails at runtime when shell environment differs
|
||||
|
||||
---
|
||||
|
||||
## Fixes Required
|
||||
|
||||
### Fix 1: Source nvm Before Installing Global Packages
|
||||
|
||||
```bash
|
||||
# At the start of setup.sh, after detecting package manager
|
||||
echo ""
|
||||
echo "── Node.js Environment ──"
|
||||
|
||||
# Check if nvm needs to be sourced
|
||||
if [ -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use 22 || nvm install 22
|
||||
ok "nvm activated: $(node -v)"
|
||||
elif ! has node; then
|
||||
fail "Node.js not found and nvm not installed"
|
||||
fail "Install nvm first: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Fix 2: Update PTY Sidecar Setup to Use Correct Node
|
||||
|
||||
```bash
|
||||
# In scripts/setup-pty-sidecar.sh
|
||||
if [ -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use 22 2>/dev/null || true
|
||||
fi
|
||||
|
||||
NODE_BIN="$(which node)"
|
||||
if [ ! -f "$NODE_BIN" ]; then
|
||||
echo "ERROR: Node.js not found in PATH"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Fix 3: Update PM2 Ecosystem Config
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'officer',
|
||||
script: 'bun',
|
||||
args: 'start',
|
||||
watch: false,
|
||||
// Source nvm before running
|
||||
exec_mode: 'cluster',
|
||||
instances: 1,
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
// This helps systemd find the right node
|
||||
NVM_DIR: '$HOME/.nvm',
|
||||
},
|
||||
// For systemd service, use a wrapper script
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Or better: Create a wrapper script for PM2:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# bin/start.sh
|
||||
set -euo pipefail
|
||||
|
||||
# Source nvm if available
|
||||
if [ -s "$HOME/.nvm/nvm.sh" ]; then
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
fi
|
||||
|
||||
# Now start Officer
|
||||
NODE_ENV=production bun src/server.tsx
|
||||
```
|
||||
|
||||
Then in ecosystem.config.cjs:
|
||||
```javascript
|
||||
{
|
||||
name: 'officer',
|
||||
script: 'bin/start.sh',
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Fix 4: Validate Pi Installation
|
||||
|
||||
```bash
|
||||
# After installing Pi
|
||||
if has pi; then
|
||||
if pi --list-models > /dev/null 2>&1; then
|
||||
ok "pi installed and working"
|
||||
else
|
||||
warn "pi installed but --list-models failed"
|
||||
warn "Try: nvm use && npm install -g @mariozechner/pi-coding-agent"
|
||||
fi
|
||||
else
|
||||
warn "pi install failed"
|
||||
fi
|
||||
```
|
||||
|
||||
### Fix 5: Add Setup Documentation
|
||||
|
||||
Create `SETUP_GUIDE.md` with clear instructions on:
|
||||
1. Choose ONE Node installation method (recommend nvm)
|
||||
2. Source nvm in shell before running setup.sh
|
||||
3. Setup.sh will validate node and npm are available
|
||||
4. Services (PM2, systemd) will inherit nvm environment
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Make nvm sourcing automatic** at the start of setup.sh
|
||||
2. **Add environment wrapper script** for PM2 that sources nvm
|
||||
3. **Document the three Node installation options** with pros/cons:
|
||||
- nvm (recommended, flexible versions)
|
||||
- NodeSource (system package, simple)
|
||||
- apt (if available in repo)
|
||||
- ❌ snap (broken, don't use)
|
||||
4. **Validate Pi works** before marking setup complete
|
||||
5. **Create a post-setup check script** that verifies everything works
|
||||
|
||||
---
|
||||
|
||||
## Current Workaround
|
||||
|
||||
If setup.sh already ran with snap node:
|
||||
1. Remove snap: `sudo snap remove node`
|
||||
2. Install nvm: `curl -o- ... | bash` (reload shell)
|
||||
3. Install node: `nvm install 22 && nvm use 22`
|
||||
4. Reinstall pm2 packages: `npm install -g pm2`
|
||||
5. Restart pm2/officer
|
||||
|
||||
This is what you just did!
|
||||
@@ -1,92 +0,0 @@
|
||||
# Per-User API Keys — Architecture Analysis
|
||||
|
||||
## Current State
|
||||
|
||||
- Single shared `auth.json` at `~/.pi/agent/auth.json` (server-side)
|
||||
- Pi CLI supports `--api-key` flag — we already use this in `pi-bridge.ts` via `resolveApiKeyForModel()`
|
||||
- User settings have unused fields: `ai.enabledModels`, `ai.enabledProviders`, `ai.disabledProviders`
|
||||
- 13 providers supported: anthropic, openai, google, groq, mistral, xai, openrouter, minimax, huggingface, azure-openai-responses, opencode, zai, cerebras
|
||||
|
||||
## Architecture
|
||||
|
||||
### Layer 1: Key Resolution
|
||||
|
||||
**Precedence:** User key > System key > fail
|
||||
|
||||
- Store user keys in `user_settings.ai.apiKeys` (JSONB column, no new DB table needed)
|
||||
- Format: `{ "ai": { "apiKeys": { "openai": "sk-...", "anthropic": "sk-ant-..." } } }`
|
||||
- `resolveApiKeyForModel()` in `pi-bridge.ts` checks user keys first, falls back to system `auth.json`
|
||||
- Key passed to Pi via `--api-key` CLI flag (already implemented for system keys)
|
||||
- Keys never touch the container filesystem — all resolution is server-side
|
||||
|
||||
### Layer 2: Model Visibility
|
||||
|
||||
- System access policy defines base available providers/models
|
||||
- User's own provider keys unlock additional providers
|
||||
- Existing `enabledProviders`/`enabledModels` settings fields drive filtering
|
||||
- Model listing becomes a union: system models + user's provider models
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **No new DB table** — use existing `user_settings` JSONB column
|
||||
2. **No per-user auth.json files** — all key resolution server-side via `--api-key` flag
|
||||
3. **Eventually remove PI_CONFIG_DIR mount** — auth.json in container no longer needed for keys (still needed for local providers/models.json)
|
||||
4. **Model listing = union** of system + user models
|
||||
|
||||
## What This Enables
|
||||
|
||||
- Admin deploys with their keys → all users can chat
|
||||
- User adds their own key → gets access to that provider
|
||||
- User's key takes priority (user pays for their own usage)
|
||||
- No credential leakage (keys in DB, passed as CLI args, never on container filesystem)
|
||||
|
||||
## Files to Change
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/servers/api/pi/pi-bridge.ts` | Update `resolveApiKeyForModel()` to check user settings first |
|
||||
| `src/servers/api/settings/settings.ts` | API endpoint for saving/deleting user API keys (encrypted at rest ideally) |
|
||||
| `src/workspaces/state/src/useSettings.ts` | Add `ai.apiKeys` to `UserSettings` type |
|
||||
| `src/servers/api/pi/list-models.ts` | Union system + user provider models |
|
||||
| `src/workspaces/officerdev/src/hooks/usePiModels.ts` | Filter models based on user's available providers |
|
||||
| `src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/AIModels.tsx` | UI for managing per-user API keys |
|
||||
| `src/servers/api/server-settings/pi-mono.ts` | Distinguish admin vs user key management |
|
||||
|
||||
## Deep-Dive: Pi CLI & Auth
|
||||
|
||||
### CLI Flags
|
||||
- `--provider` — force provider
|
||||
- `--model` — force model (format: `provider/model-name`)
|
||||
- `--api-key` — force API key (takes precedence over auth.json and env vars)
|
||||
- `--system-prompt` — system prompt
|
||||
|
||||
### Supported Environment Variables
|
||||
`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`, `XAI_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||
|
||||
### auth.json Format
|
||||
```json
|
||||
{
|
||||
"provider_id": {
|
||||
"type": "api_key",
|
||||
"key": "sk-..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Resolution Precedence (inside Pi)
|
||||
`--api-key` flag > auth.json entry > environment variable > built-in providers
|
||||
|
||||
### Pi Spawn Flow (pi-bridge.ts)
|
||||
1. Determine model from user settings or default
|
||||
2. `resolveApiKeyForModel(model)` extracts provider from `provider/model-name`
|
||||
3. Looks up key in auth.json (currently system-level only)
|
||||
4. Passes `--api-key <key>` when spawning Pi process
|
||||
5. Container never sees raw credentials
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Add key storage** — extend `UserSettings` type, add API endpoint for CRUD
|
||||
2. **Update key resolution** — `resolveApiKeyForModel(model, userId?)` checks user DB first
|
||||
3. **Update model listing** — merge system models with user-unlocked provider models
|
||||
4. **Build settings UI** — API key input per provider in AI settings page
|
||||
5. **Remove PI_CONFIG_DIR mount** (later) — once models.json is also handled server-side
|
||||
@@ -1,361 +0,0 @@
|
||||
# Event Handler Instances: `{() => handler(arg)}` Pattern
|
||||
|
||||
Found 167 instances across the codebase.
|
||||
|
||||
---
|
||||
|
||||
## Workspaces / Shared Components
|
||||
|
||||
### `src/workspaces/components/MarkdownEditor.tsx`
|
||||
- Line 48: `onClick={() => setShowPreview(false)}`
|
||||
- Line 52: `onClick={() => setShowPreview(true)}`
|
||||
|
||||
### `src/workspaces/components/SearchInput.tsx`
|
||||
- Line 49: `onClick={() => handleSearch('')}`
|
||||
|
||||
### `src/workspaces/components/ErrorDialogs/CustomError.tsx`
|
||||
- Line 25: `onClick={() => onOpenChange(false)}`
|
||||
|
||||
### `src/workspaces/components/ErrorDialogs/ForbiddenDialog.tsx`
|
||||
- Line 27: `onClick={() => onOpenChange(false)}`
|
||||
|
||||
### `src/workspaces/components/ErrorDialogs/UnauthorizedDialog.tsx`
|
||||
- Line 29: `onClick={() => onOpenChange(false)}`
|
||||
|
||||
### `src/workspaces/components/ErrorDialogs/ServerError.tsx`
|
||||
- Line 25: `onClick={() => onOpenChange(false)}`
|
||||
|
||||
### `src/workspaces/components/DataTable/PaginationBar.tsx`
|
||||
- Line 83: `onClick={() => changePage(1)}`
|
||||
- Line 93: `onClick={() => changePage(currentPage - 1)}`
|
||||
- Line 103: `onClick={() => changePage(currentPage + 1)}`
|
||||
- Line 113: `onClick={() => changePage(pageCount)}`
|
||||
|
||||
### `src/workspaces/components/DataTable/DataTable.tsx`
|
||||
- Line 89: `onClick={() => sortBy(sortKey as Extract<keyof T, string> | undefined)}`
|
||||
|
||||
### `src/workspaces/components/ui/mode-toggle.tsx`
|
||||
- Line 16: `onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}`
|
||||
|
||||
---
|
||||
|
||||
## Editor App
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/index.tsx`
|
||||
- Line 73: `onCollapse={() => setLeftCollapsed(true)}`
|
||||
- Line 74: `onExpand={() => setLeftCollapsed(false)}`
|
||||
- Line 97: `onCollapse={() => setRightCollapsed(true)}`
|
||||
- Line 98: `onExpand={() => setRightCollapsed(false)}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/ScreenshotDialog.tsx`
|
||||
- Line 114: `onClick={() => setView('list')}`
|
||||
- Line 131: `onClick={() => setView('editor')}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/Header/index.tsx`
|
||||
- Line 110: `onClick={() => setDevice(d)}`
|
||||
- Line 134: `onClick={() => setDevice(d)}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/Header/GlobalCodeMenu.tsx`
|
||||
- Line 32: `onClick={() => openEditor('js')}`
|
||||
- Line 33: `onClick={() => openEditor('css')}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/Header/ElementSelector.tsx`
|
||||
- Line 59: `onClick={() => setIsOpen(false)}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/RightSidebar/ElementInfo.tsx`
|
||||
- Line 23: `setTimeout(() => setCopiedHierarchical(false), 2000)`
|
||||
- Line 31: `setTimeout(() => setCopiedClassBased(false), 2000)`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/RightSidebar/Breadcrumb.tsx`
|
||||
- Line 60: `onClick={() => selectMember(index)}`
|
||||
- Line 100: `onClick={() => selectMember(index)}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/RightSidebar/fields/PropertyColor.tsx`
|
||||
- Line 29: `return () => clearInterval(interval)`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/LeftSidebar/HierarchyTab.tsx`
|
||||
- Line 25: `onClick={() => onSelect(index)}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/LeftSidebar/ChangesTab.tsx`
|
||||
- Line 46: `onMouseEnter={() => handleMouseEnter(change)}`
|
||||
- Line 48: `onClick={() => selectByChange(change)}`
|
||||
|
||||
### `src/apps/editor/app/src/EditorV2/Preview/index.tsx`
|
||||
- Line 118: `onClick={() => setInteractive(!interactive)}`
|
||||
- Line 132: `onClick={() => setMoveAnywhere(!moveAnywhere)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/CodeEditorWindow/index.tsx`
|
||||
- Line 31: `onClick={() => setIsFullscreen(false)}`
|
||||
- Line 33: `onClick={() => setIsFullscreen(true)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/ContextMenu/index.tsx`
|
||||
- Line 20: `onClick={() => setIsContextOpen(false)}`
|
||||
- Line 26: `onClick={() => openEditor('html')}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/GlobalCode/index.tsx`
|
||||
- Line 22: `onClick={() => openEditor('js')}`
|
||||
- Line 27: `onClick={() => openEditor('css')}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/VariantSelector/index.tsx`
|
||||
- Line 44: `onClick={() => setIsOpen(true)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/VariantSelector/CreateNewVariant.tsx`
|
||||
- Line 23: `onClose={() => setIsOpen(false)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/QuerySelector/index.tsx`
|
||||
- Line 49: `onClick={() => setIsOpen(false)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/DeviceSelector/index.tsx`
|
||||
- Line 23: `onSelect={() => setDevice(device)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/MoveAnywhere/index.tsx`
|
||||
- Line 12: `onClick={() => setMoveAnywhere((curr) => !curr)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/InteractivitySelector/index.tsx`
|
||||
- Line 13: `onClick={() => setInteractive((curr) => !curr)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/Changes/ChangesList.tsx`
|
||||
- Line 26: `onMouseEnter={() => selectByChange(change)}`
|
||||
- Line 48: `onClick={() => removeChange(change)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/Screenshot/ScreenshotList.tsx`
|
||||
- Line 38: `onMouseEnter={() => setHovered(screenshot.id)}`
|
||||
- Line 39: `onMouseLeave={() => setHovered(null)}`
|
||||
- Line 56: `onClick={() => setEditing(null)}`
|
||||
- Line 64: `onClick={() => setEditing(screenshot.id)}`
|
||||
- Line 68: `onClick={() => setWantsToDelete(screenshot.id)}`
|
||||
- Line 75: `onConfirm={() => onDeleteConfirm(screenshot.id)}`
|
||||
- Line 76: `onClose={() => setWantsToDelete(null)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/Screenshot/Controls.tsx`
|
||||
- Line 87: `onClick={() => handleUpload(comment)}`
|
||||
|
||||
### `src/apps/editor/app/src/Editor/BottomToolbar/Screenshot/index.tsx`
|
||||
- Line 38: `onClick={() => setView('list')}`
|
||||
- Line 47: `onClick={() => setView('upload')}`
|
||||
|
||||
### `src/apps/editor/extension/src/Auth/SigninPage.tsx`
|
||||
- Line 18: `setTimeout(() => navigate('/'), 100)`
|
||||
|
||||
### `src/apps/editor/extension/src/Screens/ExperimentList/ExperimentVariantList/SearchFilter.tsx`
|
||||
- Line 22: `onClick={() => onSearchChange('')}`
|
||||
|
||||
---
|
||||
|
||||
## Dashboard App
|
||||
|
||||
### `src/apps/dashboard/hooks/use-toast.tsx`
|
||||
- Line 143: `const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id })`
|
||||
|
||||
### `src/apps/dashboard/Screens/AdminTools/TestExperiments/TestExperiments.tsx`
|
||||
- Line 38: `onClick={() => setShowDuplicateModal(true)}`
|
||||
- Line 48: `onClose={() => setShowDuplicateModal(false)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Monitor/TrendTable.tsx`
|
||||
- Line 99: `onToggle={() => toggleRow(`trend-${exp.id}`)}`
|
||||
- Line 114: `onClick={() => setCurrentPage(1)}`
|
||||
- Line 120: `onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}`
|
||||
- Line 128: `onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}`
|
||||
- Line 136: `onClick={() => setCurrentPage(totalPages)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Monitor/GroupedExperimentsTable.tsx`
|
||||
- Line 75: `onToggle={() => toggleRow(`grouped-${exp.id}`)}`
|
||||
- Line 91: `onClick={() => setCurrentPage(1)}`
|
||||
- Line 97: `onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}`
|
||||
- Line 105: `onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}`
|
||||
- Line 113: `onClick={() => setCurrentPage(totalPages)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Home/ExperimentsOverviewTableMobile.tsx`
|
||||
- Line 23: `onClick={() => sort('id')}`
|
||||
- Line 28: `onClick={() => sort('status')}`
|
||||
- Line 34: `onClick={() => sort('sessions')}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Home/ConfirmDialogs.tsx`
|
||||
- Line 36: `onOpenChange={() => setDialogAction(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Home/RowActions.tsx`
|
||||
- Line 31: `onClick={() => navigate(`/experiments/${id}`)}`
|
||||
- Line 32: `onClick={() => setDialogAction('duplicate')}`
|
||||
- Line 34: `onClick={() => setDialogAction('archive')}`
|
||||
- Line 37: `onClick={() => setDialogAction('delete')}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Clients/index.tsx`
|
||||
- Line 76: `onClick={() => setIsCreating(true)}`
|
||||
- Line 108: `onClick={() => setIsCreating(true)}`
|
||||
- Line 119: `onClick={() => setOrganization(value as number)}`
|
||||
- Line 146: `onClick={() => handleSaveEdit(item.id)}`
|
||||
- Line 163: `onClick={() => handleEditName(item.id, item.friendlyName ?? '')}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/Timeline.tsx`
|
||||
- Line 111: `onClick={() => setMobileOpen((v) => !v)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Library/ActionModals.tsx`
|
||||
- Line 37: `onCancel={() => setDialogAction(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Library/TopHeader.tsx`
|
||||
- Line 44: `onClick={() => setIsCreating(true)}`
|
||||
- Line 69: `onClick={() => setIsCreating(true)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Library/ActionsCell.tsx`
|
||||
- Line 29: `onClick={() => navigate(`/experiments/${id}`)}`
|
||||
- Line 30: `onClick={() => setDialogAction('duplicate')}`
|
||||
- Line 32: `onClick={() => setDialogAction('archive')}`
|
||||
- Line 35: `onClick={() => setDialogAction('delete')}`
|
||||
- Line 44: `onClose={() => setDialogAction(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Library/CreateExperimentModal.tsx`
|
||||
- Line 88: `onCancel={() => setIsCreating(false)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/Header.tsx`
|
||||
- Line 86: `onClick={() => setDialogAction('duplicate')}`
|
||||
- Line 89: `onClick={() => setDialogAction('archive')}`
|
||||
- Line 92: `onClick={() => setDialogAction('delete')}`
|
||||
- Line 199: `onCancel={() => setDialogAction(null)}`
|
||||
- Line 214: `onCancel={() => setDialogAction(null)}`
|
||||
- Line 232: `onCancel={() => setDialogAction(null)}`
|
||||
- Line 246: `onCancel={() => setDialogAction(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Settings/Billing/index.tsx`
|
||||
- Line 137: `onClick={() => createCheckoutSession(+price.id)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Settings/AgencyTeam/index.tsx`
|
||||
- Line 125: `onClick={() => setInviteDialogOpen(true)}`
|
||||
- Line 136: `onClick={() => setInviteDialogOpen(true)}`
|
||||
- Line 176: `onClick={() => resendInvite(item.email)}`
|
||||
- Line 217: `() => unblockUser(item.id)`
|
||||
- Line 230: `() => blockUser(item.id)`
|
||||
- Line 239: `onClick={() => resendInvite(item.email)}`
|
||||
- Line 247: `() => deleteUser(item.id)`
|
||||
- Line 303: `onClick={() => setInviteDialogOpen(false)}`
|
||||
- Line 315: `onCancel={() => setConfirmDialog({ ...confirmDialog, open: false })}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Settings/OrganizationTeam/index.tsx`
|
||||
- Line 163: `onClick={() => setInviteDialogOpen(true)}`
|
||||
- Line 176: `onClick={() => setInviteDialogOpen(true)}`
|
||||
- Line 217: `onClick={() => resendInvite(item.email)}`
|
||||
- Line 259: `() => unblockUser(item.id)`
|
||||
- Line 272: `() => blockUser(item.id)`
|
||||
- Line 281: `onClick={() => resendInvite(item.email)}`
|
||||
- Line 289: `() => deleteUser(item.id)`
|
||||
- Line 361: `onClick={() => setInviteDialogOpen(false)}`
|
||||
- Line 373: `onCancel={() => setConfirmDialog({ ...confirmDialog, open: false })}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentSettings/VariantsConfig.tsx`
|
||||
- Line 57: `onClick={() => setShowAddVariant(true)}`
|
||||
- Line 167: `onClose={() => setEditingVariant(null)}`
|
||||
- Line 176: `onClose={() => setRenamingVariant(null)}`
|
||||
- Line 180: `onClose={() => setShowAddVariant(false)}`
|
||||
- Line 193: `onCancel={() => setDeletingVariant(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentSettings/CookieTargeting.tsx`
|
||||
- Line 50: `onClick={() => deleteCookieTargeting(item.id)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentSettings/DeviceTargeting.tsx`
|
||||
- Line 54: `onClick={() => deleteDeviceTargeting(device.id)}`
|
||||
- Line 65: `onClick={() => setIsAddOpen(true)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentSettings/GoalsConfig.tsx`
|
||||
- Line 85: `onCancel={() => setIsDialogOpen(false)}`
|
||||
- Line 97: `onCheckedChange={() => toggleGoal(tag.name)}`
|
||||
- Line 113: `onCheckedChange={() => toggleGoal(tag.name)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentSettings/UrlTargeting.tsx`
|
||||
- Line 95: `onClick={() => openEditDialog(item)}`
|
||||
- Line 98: `onClick={() => deleteUrlTargeting(item.id)}`
|
||||
- Line 119: `onCancel={() => setEditItem(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentSettings/EditWeightsModal.tsx`
|
||||
- Line 25: `useState(() => String(Math.round((currentWeight || 0) / 100)))`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentInfo/Description.tsx`
|
||||
- Line 44: `onClick={() => setIsEditing(true)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentStatistics/StatisticsTable.tsx`
|
||||
- Line 209: `onClick={() => setWantsToDeploy(item)}`
|
||||
- Line 227: `onCancel={() => setWantsToDeploy(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentInfo/Screenshots/ScreenshotItem.tsx`
|
||||
- Line 62: `onClick={() => setIsOpen(true)}`
|
||||
- Line 65: `onLoad={() => setImageLoaded(true)}`
|
||||
- Line 90: `onClick={() => setEditing(null)}`
|
||||
- Line 107: `onClick={() => setWantsToDelete(true)}`
|
||||
- Line 114: `onClose={() => setIsOpen(false)}`
|
||||
- Line 119: `onCancel={() => setWantsToDelete(false)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentInfo/Screenshots/ScreenshotList.tsx`
|
||||
- Line 49: `onMouseEnter={() => setHovered(screenshot.id)}`
|
||||
- Line 49: `onMouseLeave={() => setHovered(null)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Websites/List/index.tsx`
|
||||
- Line 71: `onClick={() => setIsCreating(true)}`
|
||||
- Line 100: `onClick={() => setIsCreating(true)}`
|
||||
- Line 112: `onClick={() => setWebsite(value as number)}`
|
||||
- Line 167: `onClick={() => refreshPropertyTags(item)}`
|
||||
- Line 190: `onClick={() => recheckPermissions(item.ganPropertyId!)}`
|
||||
- Line 220: `onClick={() => handleDelete(value as number)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Websites/List/ServerContainerUrl.tsx`
|
||||
- Line 18: `setTimeout(() => setNewServerContainerUrl(null), 400)`
|
||||
- Line 45: `onClick={() => setNewServerContainerUrl(null)}`
|
||||
- Line 53: `onClick={() => setNewServerContainerUrl(website.serverContainerUrl || '')}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Websites/List/ScriptsModal.tsx`
|
||||
- Line 18: `setTimeout(() => setCopiedText(null), 3000)`
|
||||
- Line 23: `onClick={() => setIsOpen(true)}`
|
||||
- Line 74: `onClick={() => setIsOpen(false)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/Websites/GoogleAnalytics/GoogleAnalyticsScreen.tsx`
|
||||
- Line 91: `onClick={() => refresh(item.email)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/DashboardLayout/index.tsx`
|
||||
- Line 17: `useGlobal<string>('PAGE_TITLE', () => getPageTitle(location.pathname, MENU_LINKS))`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/DashboardLayout/GlobalAlerts/index.tsx`
|
||||
- Line 14: `onClose={() => dismiss('Extension')}`
|
||||
- Line 15: `onClose={() => dismiss('Passkeys')}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/DashboardLayout/AppSidebar/Navigation.tsx`
|
||||
- Line 61: `onClick={() => setExpandedMenuItem(expandedMenuItem === item.key ? '' : item.key)}`
|
||||
|
||||
### `src/apps/dashboard/Screens/Dashboard/DashboardLayout/AppSidebar/AccountWebsiteSelectors.tsx`
|
||||
- Line 42: `onClick={() => setOpenPanel('account')}`
|
||||
- Line 55: `onClick={() => setOpenPanel('website')}`
|
||||
- Line 315: `onClick={() => chooseOptionValue(option.value as number)}`
|
||||
|
||||
---
|
||||
|
||||
## Summary by Pattern Type
|
||||
|
||||
### 1. Boolean State Setters (e.g., `setState(true/false)`)
|
||||
Most common pattern - 60+ instances
|
||||
|
||||
### 2. Value State Setters (e.g., `setState(someValue)`)
|
||||
~40 instances
|
||||
|
||||
### 3. Handlers with Arguments (e.g., `handleClick(id)`)
|
||||
~35 instances
|
||||
|
||||
### 4. Callbacks to null (e.g., `setItem(null)`)
|
||||
~25 instances
|
||||
|
||||
### 5. Navigation (e.g., `navigate(path)`)
|
||||
~5 instances
|
||||
|
||||
### 6. setTimeout callbacks
|
||||
~5 instances
|
||||
|
||||
---
|
||||
|
||||
## High Priority (Inside loops/maps)
|
||||
|
||||
These create N function instances per render:
|
||||
|
||||
- `src/apps/editor/app/src/Editor/BottomToolbar/Screenshot/ScreenshotList.tsx:38-76`
|
||||
- `src/apps/editor/app/src/EditorV2/LeftSidebar/ChangesTab.tsx:46-48`
|
||||
- `src/apps/editor/app/src/EditorV2/RightSidebar/Breadcrumb.tsx:60,100`
|
||||
- `src/apps/editor/app/src/EditorV2/LeftSidebar/HierarchyTab.tsx:25`
|
||||
- `src/apps/dashboard/Screens/Dashboard/Experiments/Details/ExperimentInfo/Screenshots/ScreenshotList.tsx:49`
|
||||
- `src/apps/dashboard/Screens/Dashboard/Settings/AgencyTeam/index.tsx` (multiple in table rows)
|
||||
- `src/apps/dashboard/Screens/Dashboard/Settings/OrganizationTeam/index.tsx` (multiple in table rows)
|
||||
- `src/apps/dashboard/Screens/Dashboard/Websites/List/index.tsx` (multiple in table rows)
|
||||
- `src/apps/dashboard/Screens/Dashboard/Clients/index.tsx` (multiple in table rows)
|
||||
Reference in New Issue
Block a user