Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ package-lock.json

# Non-Claude agent worktrees (created per CLAUDE.md isolation rules)
.worktrees/

# Raw prettyhtml.com capture — third-party copyrighted JS/CSS/HTML, local reference only.
# Clean-room behavior specs live in planning/*.md; the raw files never get committed.
planning/captures/
8 changes: 7 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ Each tool lives in its own directory with a consistent pattern:

### Current Tools

- **`/formatter/`** — HTML Formatter & Tidy. Pipeline: tokenize → indent (two-stage) → tidy → compress. Tidy dropdown is organized into three groups: **Formatting** (lowercase/sort/quote — our extras), **Cleaning (prettyhtml.com)** (the 10 options 1-for-1 with prettyhtml.com — Inline styles, Classes & IDs, Empty tags, Tags with 1 space, Successive spaces, Comments, Tag attributes, To plain text, AI Watermarks, Smart &nbsp;s; first 6 ON by default), and **Extras** (data-attrs, span-unwrap, strip-stray-line-breaks, etc.). Literal ports of `removeTagAttributes`, `aiWatermarkFixer`, and `smartNbsps` live in `formatter/app.mjs` (ES module). `normalizeStrayBreaks` (same module) runs as a pre-pass before `tidy()` to strip `<br>` residue between/around block elements and inside empty blocks (default-ON Extras option `opt-stray-breaks`), replicating prettyhtml.com's TinyMCE normalization layer without a DOM round-trip; tests in `formatter/tests/stray-breaks.test.mjs`. Tests in `formatter/tests/*.test.mjs` run via `npm test` (uses `linkedom` as DOMParser shim in Node). Options in `localStorage` key `htmlTidy_options`. See `planning/2026-05-27-prettyhtml-parity.md` for the algorithm snapshot / insurance documentation, and `planning/2026-06-24-stray-line-break-normalization-{design,plan}.md` for the stray-break feature.
- **`/formatter/`** — HTML Formatter & Tidy. All logic lives in `formatter/app.mjs` (an ES module; there is no `app.js` here). Buttons: Indent (two-stage), Tidy, Compress.
- **Tidy runs through `runTidyPipeline(html, opts)`**, ordered to match prettyhtml.com's `convertText()`: stray-break normalization → whitespace pre-pass → script/style strip → to-plain-text (**first**, as theirs is) → nbsp collapse (option 5) → inter-tag gap joins → `tidy()` → nested-empty fixpoint → block-newline separation → looped whitespace post-pass → tag-attributes → AI-watermarks → smart-punctuation straightening → smart-nbsps → final cleanup. `replaceUntilStable()` mirrors their `helyettesit()` replace-to-idempotence semantics.
- **prettyhtml.com is two layers**: a TinyMCE DOM round-trip, then the string cleaners behind the ten checkboxes. We have no round-trip, so several default-ON **Extras** stand in for layer 1 — `opt-stray-breaks`, `opt-block-newlines`, `opt-nested-empties`, `opt-docs-residue`. Reasoning about their cleaners in isolation gives the wrong answer about what their site outputs; always check end-to-end.
- Dropdown groups: **Formatting** (lowercase/sort/quote), **Cleaning (prettyhtml.com)** (the 10 options 1-for-1; first 6 ON by default), **Extras** (block newlines, nested empties, Google Docs residue, script/style strip, straighten smart punctuation, stray line breaks, data-attrs, span-unwrap).
- **Deliberate divergences** (documented in `app.mjs` above the pipeline): E — options 1/2 parse attributes structurally rather than doing double-quote-only string surgery; G — empty-tag removal exempts `td/th/script/style/media` and requires matching tag names; H — one-space-tag removal accepts `&#160;`; N — curly quotes are accepted as attribute delimiters, which is what makes HTML pasted out of Google Docs survive.
- Tests in `formatter/tests/*.test.mjs` via `npm test` (`linkedom` as DOMParser shim). `parity.test.mjs` runs the pipeline against `tests/fixtures/prettyhtml-golden.json` — black-box input/output pairs captured from the live site; a fixture with an `ours` field is a recorded deliberate divergence. Options persist in `localStorage` key `htmlTidy_options`.
- Docs: `planning/2026-09-03-prettyhtml-complete-capture.md` is the current clean-room spec (supersedes most of `planning/2026-05-27-prettyhtml-parity.md`); `planning/2026-06-24-stray-line-break-normalization-{design,plan}.md` covers stray breaks. The raw third-party capture lives in gitignored `planning/captures/` and must never be committed.
- **`/og-image/`** — OG Image Preview. Platform specs in `platforms.json`. Optional Cloudflare Worker CORS proxy in `functions/fetch-meta.js`. Fallback proxies for CORS.

### Suggested Tools System
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ Beautify, format, and clean up messy or minified HTML instantly.
└── formatter/ # HTML Formatter & Tidy
├── index.html
├── styles.css
└── app.js
├── app.mjs # ES module — pipeline, cleaners, DOM wiring
└── tests/ # node --test, linkedom DOMParser shim
```

Each tool is self-contained in its own directory with its own `index.html`, making it easy to develop, test, and deploy independently.
Expand Down
13 changes: 12 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ module.exports = [
},
},
},
{
files: ['formatter/tests/**/*.mjs'],
languageOptions: {
sourceType: 'module',
globals: {
...globals.node,
},
},
},
{
files: ['og-image/functions/**/*.js'],
languageOptions: {
Expand All @@ -51,6 +60,8 @@ module.exports = [
},
},
{
ignores: ['node_modules/'],
// planning/captures/ holds the raw third-party prettyhtml.com capture. It is
// gitignored reference material, not our source — never lint or ship it.
ignores: ['node_modules/', 'planning/captures/'],
},
];
Loading
Loading