Local-firstYour text never leaves your browser. No upload, no account, no server.
Most online text tools send whatever you paste to a server, run the work there, and send a result back. Your words sit in someone else's logs, backups and databases, and you have to trust a privacy policy.
Local-first means the opposite: the whole tool is downloaded to your browser and runs on your own device. Nothing you type is transmitted anywhere: not to us, not to anyone. There is no server that could receive it, which is a stronger guarantee than a promise not to look.
Three consequences worth knowing:
Turn characters into HTML entities so they display as text rather than being parsed as markup, or decode entities back into readable characters. Three encoding modes cover the usual needs: escaping only the five characters that change how HTML parses, using named entities where they exist, or escaping everything non-ASCII by codepoint for maximum compatibility. Emoji and astral characters survive intact rather than being split into broken surrogate halves.
The same input in each encoding mode
<b>Café</b> & "quotes"
Minimal: <b>Café</b> & "quotes" Numeric: <b>Café</b> & "quotes"
The ampersand, less-than, greater-than, double quote and apostrophe are the characters that can change how surrounding markup parses, by ending an attribute early, or opening a tag that was meant to be text. Everything else is a rendering preference.
Order matters when escaping them. The ampersand must be replaced first, because every other replacement introduces one: escape the less-than sign first and the < you just produced becomes &lt; on the next pass. Double-escaped output is a common bug and looks like the tool is broken when the real cause is sequence.
HTML-escaping is the correct primitive for inserting untrusted text into a page, but where the text lands decides whether it is enough. Escaped text is safe as element content and inside a quoted attribute value. It is not sufficient inside a script block, inside a style block, or in an unquoted attribute, and it does nothing at all about a javascript: URL sitting in an href.
The practical advice is to let your templating layer escape at the point of insertion, where it knows the context, rather than pre-escaping strings and storing them escaped. Pre-escaped data has a habit of being escaped twice or displayed raw somewhere that did not expect it.
Named entities such as © and — are readable in hand-edited source, which is their entire advantage. Numeric entities need no lookup table, cannot reference a name a parser does not recognise, and are the safer default for generated output. Both decode identically everywhere.
Emoji are where many tools quietly fail. An emoji occupies two UTF-16 code units internally, and a tool iterating by code unit escapes each half separately, producing two entities that no longer reassemble into anything. Iterating by codepoint, which is what happens here, keeps an emoji as one entity that round-trips exactly.
Five: the ampersand, less-than, greater-than, double quote and apostrophe. Those are the ones that can end an attribute or open a tag and so change how the surrounding markup parses. Everything else is a display concern rather than a correctness one, which is what the minimal mode escapes.
Escaping is the right primitive, but where you insert the result matters just as much. HTML-escaped text is safe as element content and inside quoted attributes. It is not sufficient inside a script block, a style block, or an unquoted attribute, and it does nothing for a javascript: URL in an href. Use your framework's own escaping in templates rather than pre-escaping strings.
Numeric is the safer default because it needs no lookup table and cannot hit an entity name a parser does not know. Named entities are more readable in source, so they suit hand-edited HTML. Both decode identically in every modern browser.
Because they iterate the string by UTF-16 code unit rather than by character. An emoji occupies two code units, so escaping each one separately produces two entities that no longer reassemble. This iterates by codepoint, so an emoji becomes a single entity and round-trips exactly.