Back to Rules
📋

Flxify CLAUDE.md

Flxify is a Swiss Army knife for developers — a lightweight, privacy-first text transformation tool built with no frameworks and zero external dependencies.

ahmedeltaweel

by @ahmedeltaweel

Sourced from ahmedeltaweel/flxify — MIT

View profile
CLAUDE.md
> Sourced from [ahmedeltaweel/flxify](https://github.com/ahmedeltaweel/flxify) — [MIT](https://github.com/ahmedeltaweel/flxify/blob/d9543003f9439d7bf61517ab84e52b8dba753180/tui/CLAUDE.md).

# Flxify - Project Knowledge

## What is Flxify?

Flxify (flxify.dev) is a web-based developer text utility tool. It provides a syntax-highlighted code editor with 115 executable scripts accessible via a command palette (Cmd/Ctrl+B). Users paste text, run a script, and get transformed output. Think "Swiss Army knife for text transformations" — JSON formatting, Base64 encoding, hashing, case conversion, sorting, and more.

## Sub-projects

This repo contains three independently deployable products. Claude Code auto-loads each directory's `CLAUDE.md` when you work there:

| Working on... | Auto-loaded file |
|---|---|
| Web app (root — `index.html`, `app.js`, `scripts/`) | This file |
| TUI (`tui/`) | `tui/CLAUDE.md` |
| VS Code extension (`vscode-extension/`) | `vscode-extension/CLAUDE.md` |

## Key Architecture Decisions

### Single-page app, no framework, build step for bundling
The app is pure HTML/CSS/JS. CodeMirror 6 is the only external dependency, loaded from esm.sh CDN. A Node.js build script (`build_app.js`) bundles all scripts and lib modules into a single self-contained `app.js`. This ensures the app works offline, from `file://` URLs, and with no web server required (except for CodeMirror CDN on first load).

### All scripts and modules are inlined in app.js
- **115 scripts** are auto-discovered from `scripts/*.js` and inlined as pre-compiled functions in `app.js`.
- **7 lib modules** are auto-discovered from `scripts/lib/*.js` and inlined as direct executable code in `app.js`.
- The source-of-truth for all scripts lives in `scripts/` and for lib modules in `scripts/lib/`. After editing these files, run `node build_app.js` to regenerate `app.js`.

### Module system mimics CommonJS
Scripts can `require('@flxify/moduleName')` to import bundled libraries. The shim wraps each module in a CommonJS sandbox (`module.exports`). The 7 lib modules are third-party JS files whose source lives in `scripts/lib/` but are inlined into `app.js` at build time.

### BoopState is the script API
Every script's `main(state)` function receives a `BoopState` instance. The key insight is the smart `state.text` property: it reads/writes `state.selection` if text is selected, otherwise `state.fullText`. This lets most scripts be one-liners (`state.text = transform(state.text)`) that automatically handle both selection and full-document modes.

### Scripts are pre-compiled at build time (CSP-compatible)
Scripts are inlined as real JavaScript functions at build time — no `new Function()` or `eval()` in the runtime. Each script becomes a `scripts.push({ ..., execute: function(require, state) { ... } })` call. This makes the app compatible with strict Content Security Policy headers that disallow `unsafe-eval`.

## How to Add a New Script

1. Create a `.js` file in `scripts/` following this format:
```javascript
/**
  {
    "api": 1,
    "name": "My Script",
    "description": "What it does",
    "author": "Flxify",
    "icon": "icon-name",
    "tags": "search,terms"
  }
**/

function main(state) {
  state.text = state.text.toUpperCase(); // your transform here
}
```

2. Run `node build_app.js` to regenerate `app.js` (scripts are auto-discovered, no need to edit build_app.js)
3. The script will be available in the command palette on next page load

## How to Add a New Library Module

1. Place the CommonJS-compatible `.js` file in `scripts/lib/`
2. Run `node build_app.js` to regenerate `app.js` (lib modules are auto-discovered, no need to edit build_app.js)
3. Scripts can then use `require('@flxify/moduleName')`

## Critical Files — Do Not Break

| File | Why it matters |
|------|----------------|
| `app.js` (generated) | Self-contained bundle — module system, all 115 scripts, BoopState, executor. **Do not edit directly; edit sources and run `node build_app.js`** |
| `build_app.js` | Generates app.js + all SEO pages. If broken, can't rebuild. Contains the app's runtime code as a template AND the SEO page generation pipeline |
| `index.html` module script | CodeMirror 6 setup. Exposes `window.cmEditor` which app.js depends on. Also the SEO template source — tool pages copy the CM module script from here |
| `scripts/*.js` | Source of truth for all 115 scripts |
| `scripts/lib/` | Source of truth for the 7 library modules. All scripts using `require()` depend on these |
| `seo-data.json` | Category mappings + custom SEO metadata for high-value tools. Build script reads this to generate tool pages |
| `tools/` (generated) | 115 tool pages + directory page. **Do not edit directly; generated by `node build_app.js`** |
| `sitemap.xml` (generated) | XML sitemap with all 115 URLs. **Generated by build** |
| `robots.txt` (generated) | Crawl directives. **Generated by build** |

## Common Patterns

### Script that transforms text
```javascript
function main(state) {
  state.text = state.text.split('').reverse().join('');
}
```

### Script that uses a library
```javascript
const { camelCase } = require('@flxify/lodash.boop')
function main(state) {
  state.text = camelCase(state.text)
}
```

### Script that reports info without modifying text
```javascript
function main(state) {
  state.postInfo(state.text.length + ' characters');
}
```

### Script that handles errors
```javascript
function main(state) {
  try {
    state.text = JSON.stringify(JSON.parse(state.text), null, 2);
  } catch(e) {
    state.postError("Invalid JSON");
  }
}
```

### Script that generates new content (generator)
```javascript
function main(state) {
  var result = generateSomething();
  if (state.isSelection) {
    state.text = result;
  } else {
    state.insert(result);
  }
}
```

## Gotchas and Lessons Learned

1. **NEVER use `fetch()` for loading scripts/modules at runtime.** Browsers block `fetch()` on `file://` URLs due to CORS policy. Always bundle — never rely on runtime `fetch()` for core functionality.

2. **`app.js` is a generated file — do not edit directly.** Edit source files, then run `node build_app.js`. Changes to `app.js` directly are lost on next build.

3. **Lib modules are inlined before scripts in the generated output.** This ensures modules are available when scripts that use `require()` are loaded.

4. **Scripts are inlined as plain JavaScript — no escaping needed.** No template literal escaping or `new Function()` involved.

5. **`lodash.boop` module name has a dot.** Scripts reference it as `require('@flxify/lodash.boop')`. The require shim strips `@flxify/` and `.js` to get the key name `lodash.boop`.

6. **Trailing commas in script metadata.** The parser handles this with a lenient regex: `.replace(/,\s*([\]}])/g, '$1')`.

7. **CodeMirror is exposed globally.** The CM6 editor is set up in a `<script type="module">` block in index.html and exposed as `window.cmEditor`. CodeMirror fires a `cm-ready` event to signal readiness.

8. **`<script type="module">` vs `<script defer>` timing.** Module scripts are delayed by CDN network imports. The app works because script registration is synchronous and the palette is only opened by user interaction (after both scripts load).

9. **Fuzzy search weights.** Command palette scores: name (0.9), tags (0.6), description (0.2). Good `tags` metadata is important for discoverability.

10. **Scripts run in non-strict mode.** The generated `app.js` IIFE does not use `'use strict'` — some scripts use implicit globals. Do not add `'use strict'` to the IIFE.

11. **CodeMirror needs internet on first load.** CM6 is loaded from esm.sh CDN. Works offline after browser caches the modules.

12. **Scripts and lib modules are auto-discovered.** Just drop a file in `scripts/` or `scripts/lib/` and rebuild — no hardcoded file lists.

13. **SEO pages are generated at build time.** Do not edit files in `tools/` directly — regenerated on every build.

14. **Tool pages copy the CodeMirror script from index.html.** If you change the CM setup in index.html, all tool pages get the update on next build.

15. **seo-data.json has two sections.** `_categories` maps category names to script file keys. `_customMeta` provides custom title/metaDescription/keywords for high-value tools.

16. **Tool page slugs are auto-generated from script names.** "Format JSON" → "format-json". The slug determines the URL at `/tools/[slug]/`.

17. **`window.flxifyAutoScript` enables tool page pre-selection.** Each tool page sets this before app.js loads. Users can still access all scripts via Cmd/Ctrl+B.

18. **localStorage persists editor content.** Saved under `flxify-editor-content` with 300ms debounce. Works on homepage and tool pages.

19. **Git case-sensitivity matters for deployment.** macOS is case-insensitive but Linux (PaaS) is case-sensitive. `scripts/` MUST be lowercase in git.

20. **Browser caching can hide deploys.** Hard refresh (Cmd+Shift+R) clears cache. Consider cache-busting query strings if recurring.

21. **Generator scripts must use `state.insert()`, not `state.text =`.** Check `state.isSelection` first: if selected, `state.text = result`; if not, `state.insert(result)`. Using `state.text =` with no selection replaces the entire document.

22. **`manifest.json` fails on `file://` protocol.** The `<link rel="manifest">` tag causes CORS errors locally. Load it conditionally: `if(location.protocol!=='file:')` inject the link tag via JS.

23. **Some scripts throw on arbitrary input instead of using `state.postError()`.** MinifyJSON, SumAll, and HexToASCII throw errors on non-matching input. Bulk tests must exclude these via `THROWS_ON_ARBITRARY_INPUT` set; they're tested individually in `tests/scripts/specific/`.

24. **The `he` library (HTMLEncode) produces hex entities not named entities.** `he.encode('<')` returns `&#x3C;` not `&lt;`. Tests must assert hex format.

25. **`FormatCSV` is listed in seo-data.json but no script file exists.** Handled via `KNOWN_MISSING_FILES` set in `all-scripts.test.js`.

26. **Content filtering policies may block sub-agents on policy documents.** Fall back to direct implementation by the orchestrator when this happens.

27. **CSS `[data-theme]` selector collision.** Theme CSS variables use `html[data-theme="..."]` selectors, NOT bare `[data-theme="..."]`. The theme dropdown buttons also have `data-theme` attributes. A bare selector matches those buttons too. Always prefix with `html`.

28. **Anti-FOUC must use `document.documentElement`, not `document.body`.** The anti-FOUC inline script in `<head>` runs before `<body>` exists. Must set `data-theme` on `document.documentElement`. `applyTheme()` must also use `documentElement` for consistency.

29. **CM6 custom themes need both `EditorView.theme()` and `HighlightStyle.define()`.** A theme without `HighlightStyle` only styles the editor chrome but NOT syntax colors. Both must be bundled as an array and passed to `themeConf.reconfigure()`.

30. **Themes are web-only.** The VS Code extension and TUI use their own theming. The 6 Flxify CSS themes only apply to the web app and generated tool pages.

31. **Sidebar layout: use flex sibling, not fixed+margin.** The sidebar is a flex sibling of `#editor-wrapper` inside `#main-layout` (flex row). Never use `position: fixed` + `margin-left` on the top-bar — the logo shifts. Only `#category-bar` and `#editor-wrapper` get `margin-left: 220px` when sidebar opens.

32. **Onboarding overlay z-index stacking context trap.** `#onboarding-overlay` and `#onboarding-box` MUST be siblings in the DOM — never parent/child. A `position: fixed; z-index: 9000` parent creates a stacking context that traps all children. `.onboarding-highlight` elements at `z-index: 9002` in root context paint ABOVE the entire overlay stacking context. Fix: make the box a sibling `<div>`, not a child. z-index hierarchy: 9000 = backdrop, 9002 = highlights (`pointer-events: none`), 9003 = box.

33. **CSS class-based visibility can be stale from browser cache.** Use inline styles directly for critical show/hide: `overlay.style.display = 'flex'` / `'none'`. This bypasses the CSS cascade entirely.

34. **localStorage key versioning for onboarding resets.** Bump the localStorage key (e.g., `flxify-onboarded` → `flxify-onboarded-v2`) when onboarding content changes significantly. Without this, users who completed the tour never see updated content.

35. **Palette Left/Right arrow keys navigate categories — intercept on the search input.** Add `ArrowLeft`/`ArrowRight` handler inside `#search` `keydown` listener. Call `e.preventDefault()` first. Find current category index in `CATEGORIES`, increment/decrement with wraparound, set `activeCategory`, re-render chips + filter. Call `scrollIntoView({ behavior: 'smooth', inline: 'nearest' })` on `.cat-chip.active` after render.

36. **Sidebar keyboard navigation — use a closure-local index, not a global.** Declare `var sidebarActiveIdx = -1` inside `initSidebar()`. `↑`/`↓` cycles `<a>` items + `scrollIntoView`, `Enter` clicks, `Esc` clears input + `renderSidebar()`. `input` event resets index to `-1` on each keystroke.

37. **`Cmd/Ctrl+Shift+T` conflicts with Chrome on ALL platforms.** Chrome uses this to reopen closed tabs. The browser intercepts it before `preventDefault()` can fire. Use `Option/Alt+B` for new tab shortcut instead.

38. **macOS Option key produces Unicode characters.** `Option+N` = tilde (~), `Option+B` = integral sign (∫). When handling `altKey` shortcuts on Mac, also match the produced character in `e.key` (e.g., `e.key === '∫'`).

39. **`beforeunload` is essential for tab persistence.** The 500ms sync interval + 300ms debounced save can miss saves on page close. Add a `beforeunload` handler that calls `snapshotCurrentTab()` + `_saveNow()` synchronously.

40. **Tab label counter must not use monotonic ID.** `nextId` persists in localStorage and grows forever even after tabs are closed. For "Untitled" labels, just use "Untitled" without numbering, or scan existing labels for the highest N.

## Development Workflow

- **To modify app runtime code:** Edit the template string inside `build_app.js`, then run `node build_app.js`
- **To modify the editor/UI:** Edit `index.html` or `style.css` directly
- **To add/edit scripts:** Add or edit `.js` files in `scripts/`, then run `node build_app.js` (auto-discovered)
- **To add/edit lib modules:** Add or edit `.js` files in `scripts/lib/`, then run `node build_app.js`
- **To modify SEO metadata:** Edit `seo-data.json`, then run `node build_app.js`
- **To test:** Open `index.html` directly in a browser (works with `file://`)
- **To validate JS syntax:** `node --check app.js`
- **To rebuild everything:** `node build_app.js`
- **To run tests:** `npm test` — runs Vitest (metadata validation, execution, category coverage)
- **To validate everything:** `npm run validate` — runs tests + build + syntax check
- **TUI:** see `tui/CLAUDE.md` — `node tui/bin/flxify.js`
- **VS Code extension:** see `vscode-extension/CLAUDE.md`

## SEO Architecture

### Build Pipeline
`node build_app.js` runs two phases:
1. **Phase 1: app.js generation** — Reads scripts/ and scripts/lib/, generates self-contained app.js bundle
2. **Phase 2: SEO page generation** — Generates:
   - `tools/[slug]/index.html` for each of 115 scripts
   - `tools/index.html` (tool directory with categories and search)
   - `sitemap.xml` (117 URLs: homepage + directory + 115 tools)
   - `robots.txt`

### SEO Data Flow
```
seo-data.json (_categories, _customMeta)
  + script metadata (name, description, tags)
  → auto-generated: slug, title, metaDescription, keywords, howToSteps, useCaseContent, FAQs
  → tool page HTML with JSON-LD (WebApplication, FAQPage, HowTo), OG tags, Twitter Card
```

### Adding a New Script (SEO-aware)
1. Create `.js` file in `scripts/` with metadata block
2. Optionally add category mapping in `seo-data.json` `_categories`
3. Optionally add custom SEO metadata in `seo-data.json` `_customMeta`
4. Run `node build_app.js` — script appears in app.js AND gets its own tool page

## Syntax Highlighting Architecture

The editor uses CodeMirror 6 with automatic language detection via `detectLanguageId(text)` (returns a stable string ID). A `languageConf` Compartment allows dynamic switching without recreating the editor. Detection is debounced at 500ms.

**Detection priority:** Markdown → JSON → HTML → XML → SQL → Python → CSS → TypeScript → JavaScript → YAML

**CM6 imports:** `@codemirror/lang-json`, `lang-javascript`, `lang-html`, `lang-css`, `lang-xml`, `lang-sql`, `lang-yaml`, `lang-python`, `lang-markdown`, `@codemirror/language-data`

### Theme System (6 Themes)
Themes: `standard-light`, `standard-dark`, `cyber-neon`, `nordic-frost`, `monokai-pro`, `oled-stealth` via `html[data-theme="..."]` CSS selectors.

- CSS variables in `style.css` under `html[data-theme="..."]` (MUST use `html` prefix — see gotcha 27)
- `:root` fallback block mirrors `standard-dark` for pre-JS rendering
- Anti-FOUC inline `<script>` in `<head>` sets `data-theme` on `document.documentElement`
- `applyTheme(themeKey)` sets attribute on `documentElement`, saves to `localStorage('flxify-theme')`, reconfigures CM6 theme
- A `themeConf` Compartment manages 6 CM6 themes via `cmThemeMap` in app.js
- Standard Light/Dark reuse existing CM6 themes; other 4 have custom `EditorView.theme()` + `HighlightStyle.define()` in `index.html`
- CM6 imports for custom themes: `HighlightStyle` from `@codemirror/language@6`, `tags` from `@lezer/highlight@1`
- **Persistence:** `localStorage('flxify-theme')`, default `'standard-dark'`

## Multi-Tab / Workspace System

The web app supports multiple editor tabs. Architecture: single CM6 instance (`window.cmEditor`) with state swapping — `TabState` stores content, cursor, scroll, and language per tab; `TabManager` swaps state in/out on tab switch.

**Key files:**
- `build_app.js` template: `TabState`, `TabManager`, `renderTabBar()`, keyboard shortcuts
- `index.html`: `#tab-bar` element (first child of `#editor-wrapper`), 5 language-related window globals
- `style.css`: 12 tab CSS variables per theme, flex layout for editor-wrapper

**localStorage:** `flxify-tabs` key stores `{ tabs, activeTabId, nextId }`. `beforeunload` handler does synchronous save via `_saveNow()`. Migrates from legacy `flxify-editor-content` key. 20-tab cap.

**Keyboard shortcuts:** `Option/Alt+B` (new tab), `Cmd/Ctrl+Shift+W` (close), `Cmd/Ctrl+Shift+[/]` (prev/next). Double-click tab label to rename.

**Tool pages:** `#tab-bar` is NOT in the tool page template. Tab init skipped when `window.flxifyAutoScript` is set.

**Discoverability:** Bottom-right `#new-tab-hint` shows shortcut, auto-dismissed via `flxify-new-tab-hint-dismissed` localStorage key. Onboarding tour step 3 (desktop) covers tabs.

## Agent Workflow

This project uses four custom Claude Code agents:
- **project-orchestrator** — Plans work, delegates to dev, QA, and theme agents, tracks progress.
- **plan-developer** — Implements features. Has web, TUI, and VS Code extension knowledge.
- **qa-plan-validator** — Read-only validation against plan requirements. Never modifies code.
- **flxify-theme-architect** — Designs themes and color palettes. Use BEFORE developer for any visual work.

Workflow: orchestrator → (optional: theme-architect) → plan-developer → qa-plan-validator

**When spawning agents for sub-projects:** include "Read tui/CLAUDE.md" or "Read vscode-extension/CLAUDE.md" in the prompt so the agent gets the right context.

### Agent Best Practices
- Give agents exact file paths, method names, and acceptance criteria — vague prompts produce vague code.
- Developer agent must run `node --check` and `npx vitest run` before reporting done.
- QA agent is read-only — if issues found, describe precisely (file:line, expected vs actual).
- Agents may claim fixes are applied when they only analyzed the code. Verify by reading actual files.

## Open Source Infrastructure

### Test Suite
Uses Vitest v3+ with `globals: true` (no imports needed in test files). Config: `vitest.config.mjs`.

- `tests/helpers/mock-state.js` — MockBoopState class
- `tests/helpers/script-loader.js` — Loads scripts with metadata parsing and require shim
- `tests/scripts/all-scripts.test.js` — Bulk tests: metadata validation, basic execution, category coverage
- `tests/scripts/specific/` — 10 targeted test files for scripts needing specific input

### GitHub Templates
- `.github/ISSUE_TEMPLATE/` — Bug report and feature request templates
- `.github/PULL_REQUEST_TEMPLATE.md` — PR checklist

### CI Pipeline
- `.github/workflows/ci.yml` — Node.js 18, 20, 22 on ubuntu-latest
- Steps: checkout → npm ci → npm test → npm run build → node --check app.js

Add to your project

Paste into your project's CLAUDE.md or ~/.claude/CLAUDE.md for global rules.

More for TypeScript

MCP servers for TypeScript

Browse all MCP servers →

Browse by Tag

Get the Claude Code Starter Pack

Top CLAUDE.md rules for Next.js, TypeScript, Python, Go, and React — free.