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.
> 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 `<` not `<`. 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
Next.js Expert
by @Claude Rules
Expert-level Next.js development with App Router, Server Components, and modern patterns.
Python FastAPI Expert
by @Claude Rules
Building high-performance REST APIs with FastAPI, Pydantic, and async Python.
Node.js Express API
by @Claude Rules
Building scalable Node.js REST APIs with Express, middleware, and proper async patterns.
Vue.js Composition API
by @Claude Rules
Modern Vue 3 development with Composition API, Pinia, and TypeScript.
Angular Enterprise
by @Claude Rules
Enterprise Angular development with RxJS, NgRx, standalone components, and best practices.
Django Web Framework
by @Claude Rules
Full-stack Django development with DRF, proper models, and security best practices.
MCP servers for TypeScript
microsoft/markitdown
🎖️ 🐍 🏠 - MCP tool access to MarkItDown -- a library that converts many file formats (local or remote) to Markdown for LLM consumption.
netdata/netdata#Netdata
🎖️ 🏠 ☁️ 📟 🍎 🪟 🐧 - Discovery, exploration, reporting and root cause analysis using all observability data, including metrics, logs, systems, containers, processes, and network connections
upstash/context7
📇 ☁️ - Up-to-date code documentation for LLMs and AI code editors.
Browse by Tag
Get the Claude Code Starter Pack
Top CLAUDE.md rules for Next.js, TypeScript, Python, Go, and React — free.
