Textulon

A transient floating editor for inspecting, transforming, previewing, and lightly reshaping text handed to it by another tool.
Powered by RoboCore • macOS 13+

Intent

Textulon is the one-purpose macOS app at the end of the acquire → transform → return text pipeline. It does not select text on its own. Some other tool — a Stream Deck button, a robo keyboard shortcut, a Raycast script — supplies the input. Textulon shows it, lets you reshape it, and hands the result back via the clipboard or stdout.

That separation means the same transform engine works whether the front-end is a Stream Deck key, an editor menu, or just a piped CLI command.

Companion to Mac-A-Tron’s Keyboard Shortcut action. Where the Keyboard Shortcut action does inline buffer manipulation ({grab}{insert before …}{paste}), Textulon is for cases that need a glance, a tweak, or a preview before paste-back.

Quick Start

1) Install it

Textulon is distributed as a signed macOS app. Install Textulon.app in /Applications so it is available from Finder, Stream Deck actions, and command-line aliases.

2) Open it with something

Three ways to put text in front of you:

# From a file
Textulon --input-file ~/notes/messy.json --mode json

# From a pipe
pbpaste | Textulon --input-stdin

# From within the UI: ⌘O opens a file picker
#   (Textulon will ask before discarding non-empty editor contents)

Shell alias. The repo includes install_textulon_alias.sh, which appends an alias tx='…/Textulon' line to ~/.zprofile (zsh) or ~/.bash_profile (bash). Run it once after Textulon.app is in /Applications — it's idempotent, and falls back to Spotlight if Textulon lives elsewhere. After that:

tx ~/some-file.json              # autodetect mode by extension
cat foo.xml | tx --input-stdin   # pipe stdin
tx --help                        # full CLI surface

3) Look at the version

$ Textulon --version
Textulon v0.1.0.0 (built 2026-05-14)
Built against robo v0.1.1.125 (built 2026-02-04)
Copyright © 2025-2026 RoboMac

Textulon reports its signed-app build and the RoboCore version it was linked against.

Modes

The Mode dropdown determines how the editor and preview pane behave. In Auto, Textulon inspects the editor contents on every change and rotates the preview into the right shape.

ModePreview paneWhat it’s for
AutoInferredPasted-in text of unknown shape.
PlainNoneJust the editor; transforms still available.
JSONTree (collapsible value tree), Query (JSONPath expression → matching nodes), or Pretty (via json.beautify)Validate & reformat, click through a parsed structure, or jump to nodes via path expressions. The status row shows the selected node's JSONPath (e.g. $.documents[0].title).
XMLTree (element / attribute / text nodes with positional indexes), Query (XPath expression → matching nodes), or Pretty (via xml.beautify)Validate well-formed XML, navigate the parsed tree, or query with XPath. The status row shows positional XPath (e.g. /catalog/book[2]/title).
MarkdownRendered Markdown (HTML in a sandboxed WKWebView) with inline Mermaid fence supportSource on top, rendered preview below. Fenced mermaid / mmd blocks render as diagrams inside the Markdown document.
MermaidRendered Mermaid diagram (flowchart, sequence, ER, etc)Preview diagrams from .mmd or .mermaid files.
SVGRendered SVG preview with transparent-background controlsAuto mode recognises <svg. The preview bar offers System/Dark/Light backgrounds and a green 1 px frame around the SVG canvas.
HTMLRendered HTML in WKWebViewSource on top; rendered preview below. Network is blocked.
JavaScript / TypeScript / Shell / YAML / TOML / INI / Python / Go / SwiftSource syntax colouringLightweight source editing and inspection; Shell mode also provides rc/profile snippets.
RoboKeysScratch pane that receives live robo keys output.Test key chains before binding them to a Stream Deck button.
ASCIILive string-extraction results with inline filters.Extract readable ASCII / UTF-8 / UTF-16 BE strings from binary noise.
Hex (toggle)Byte grid above mode-native previewInspect/edit bytes for any file. .bin, .dat, .raw open with Hex view on by default.
Resizable split. The editor and preview panes share the window through a draggable divider — pull it up or down to give either pane more room. A Hide Raw toggle in the header collapses the source pane so the rendered view takes the full window; toggle off to bring source back. Settings let you choose separate Hide Raw-on-open defaults for Markdown, Mermaid, HTML, SVG, JSON, XML, Hex, and binary/ASCII-style previews.
Auto detection. File extension is checked first, then content. Auto recognises JSON, XML/HTML, Markdown/Mermaid, SVG (<svg), code/settings files, RoboKeys-looking text, and Parseo-recognised binaries.
Tree → source highlight. In JSON/XML Tree, clicking a row selects the corresponding source range in the editor. If the row is a container (object/array/element with children), the same click also toggles its expand/collapse state; the chevron toggles without moving the editor selection. Parse errors show a red chip with the failing offset.

Editor, Tabs, and Restore

The source pane is an NSTextView-backed editor with Textulon-specific controls for transforms, snippets, matching, and session restore.

FeatureDetails
TabsThe tab bar appears when more than one document is open. Use Tab and Tab to switch documents.
File breadcrumbClick the final file name to copy the full path. Click a folder segment for Open File, Reveal in Finder, and path-copy options including home-relative and current-directory-relative paths when available.
Session restoreTabs marked Keep restore after quit. Stream Deck-originated tabs default to Ephemeral and are not saved. Memory buffers marked Keep are stored as compressed session data.
Find / ReplaceF opens Textulon's source find panel with literal or regex search, case and whole-word options, context rows, jump, replace current, and replace all.
Word wrap / line numbersUse the source status-row switches or W / L.
Current lineThe cursor line can be highlighted with a configurable background from Settings.
Rectangular selection drag selects a source rectangle. Textulon-to-Textulon copy/cut/paste preserves columns by using a private pasteboard payload plus normal plain text.
Match actionsM jumps to the mate; M selects the block. Matching covers brackets, XML/HTML/SVG tags, Markdown heading sections, Mermaid subgraph/end, and settings sections, while skipping common strings/comments lexically.
Undo historyZ shows Textulon's visible edit history. Typing groups close after about four seconds of inactivity; transforms, snippets, paste, cut/delete, and line edits become separate records.
Snippets and colourMarkdown/HTML toolbar buttons wrap selected text. SVG snippets include document scaffolds, shapes, and transform stubs; Shell snippets cover common rc/profile patterns. The shared colour picker inserts colour text in HTML, Mermaid, and SVG.
Image data URIsHTML, Markdown, and Mermaid modes can import an image file as Base64 data:image/... source text. Select an HTML image tag, Markdown image, or selected URI text and choose Export Selected Data URI Image to write it back to a file. Mermaid support is source-level because diagram rendering of data-URI images depends on Mermaid syntax and settings.

Structured Navigation

JSON and XML modes are not just pretty-printers. Textulon parses the source into a tree whose rows map back to source ranges. Click a node to select the matching text in the editor, or use the Query tab to run a JSONPath / XPath expression.

FeatureDetails
TreeCollapsible JSON objects/arrays and XML elements/text nodes. Parse errors show the failing offset.
QueryJSONPath supports exact paths, quoted keys, array indexes, wildcards, and descendant searches. XPath supports absolute paths, positional predicates, attribute equality, and namespace-prefixed names.
Path rowThe source header shows a clickable Path: chain. Click Path: or press F to jump by expression; click a segment to jump to that ancestor.
Titlebar shortcutThe fx glyph opens the same JSONPath / XPath prompt without leaving the mouse on the editor.

Rendered HTML Export

Textulon can export rendered views as standalone HTML. The commands live in the File menu and are intentionally limited to modes where the rendered output adds something beyond saving source text.

CommandUseOutput
Export Rendered HTML (SVG)… Preferred for Markdown and Mermaid portability. Textulon renders the preview, waits for Mermaid, strips renderer scripts, and saves HTML with Mermaid diagrams embedded as inline SVG.
Export Rendered HTML (PNG Images)… Compatibility path for apps that do not import inline SVG reliably, such as Microsoft Word's HTML importer. Rendered Mermaid SVGs are rasterized to 3x PNG data-URI images while keeping their normal display size.
JSON / XML tree export Share a navigable structured view instead of just pretty source. A standalone collapsible HTML tree with Expand All / Collapse All controls.
Export Pageless PDF… Save rendered output without artificial page cuts. One continuous PDF page for Markdown, Mermaid, HTML, JSON tree, or XML tree output. Textulon asks for a remembered export width for wide Mermaid diagrams before saving.
Why two HTML exports? Inline SVG is scalable and faithful, so it is the default for Markdown and Mermaid. PNG data URI export is larger and less editable, but it is useful when another application cannot import inline SVG.

Textulon does not export Pretty JSON/XML as HTML; use the json.beautify or xml.beautify transforms when you want formatted source text.

Hex View

Hex view is a source-pane toggle, not a mode. Turn it on with H or the header switch to inspect bytes while the Render pane stays mode-native. A JSON file can show raw bytes on top and the JSON tree underneath.

Hex capabilityDetails
Byte grid8-digit address column, grouped hex bytes, printable ASCII column, and 0x10 / 0x20 / 0x30 / 0x40 byte row widths.
EditingE edits a byte at an offset, I inserts a 0x00 byte, and deletes the selected byte.
FindF accepts addresses (0x100), hex patterns (48 65 6C), quoted text, or plain UTF-8 text.
Binary files.bin, .dat, and .raw open as bytes. Files above 32 MiB warn before loading; files above 256 MiB are refused.
SavingWhen Hex view is active, Save writes bytes verbatim instead of round-tripping through UTF-8.

Binary Metadata (Parseo)

Textulon embeds Parseo to inspect binary file metadata — MP3/ID3 tags, MP4 atoms, PNG chunks, PDF info, MPEG-TS packets, OneNote sections, and other bundled formats. Open a supported file from Finder or O; Parseo auto-detects the format when possible.

Parseo is a peer checkout next to mac-a-tron; from Textulon's package directory the dependency path is ../../Parseo. Its specs are compiled into the Textulon binary at build time.

ASCII Mode

ASCII mode runs RoboCore's string extractor against the current source. For binary files it scans the loaded bytes; for normal text it scans the editor's UTF-8 bytes. The same engine powers the ascii transform, the Stream Deck action, and the {ascii} robo keys token.

ControlUse
Min lengthDrop strings shorter than the chosen length. Default is 6.
Alpha %Require a percentage of letters, digits, and common punctuation. Set to 0 to disable.
UTF-8 / UTF-16Accept multi-byte UTF-8 and ASCII-range UTF-16 BE pairs.
OffsetsPrefix each result with the starting byte offset.
Suppress / Ignore / SearchStrip matching tokens, reject chosen characters as ASCII, or keep only strings containing a case-insensitive search term.

Transforms

Every transform is implemented once, in RoboCore’s TransformEngine, and exposed via the shared TransformRegistry. The Transform dropdown is ranked: in Auto mode, the most applicable transform is offered first.

idWhat it does
rot13ROT-13 — reciprocal letter substitution.
rot18ROT-13 for letters + ROT-5 for digits (reciprocal).
json.beautifyJSON beautify (sorted keys).
json.minifyJSON minify.
xml.beautifyXML beautify via XMLDocument.
html.escapeEncode &, <, >, ", '.
html.unescapeDecode named & numeric HTML entities.
url.encodeRFC 3986 percent-encoding.
url.decodePercent-decoding.
base64.encodeBase64 encode (UTF-8 input).
base64.decodeBase64 decode → UTF-8.
lines.trimTrim each line.
lines.sortSort lines (localized standard compare).
lines.uniqueUnique lines, preserving first-seen order.
lines.sortUniqueSort + unique.
text.uppercaseUppercase the whole text.
text.lowercaseLowercase the whole text.
text.trimTrim leading/trailing whitespace from the whole text.
url.removeQueryStrip everything from ? onward (URL host+path).
asciiExtract ASCII / UTF-8 / UTF-16 BE strings from binary noise.
autoDetect the content kind and apply the best-fit transform.
Curly-brace forms. Every transform above is also addressable from robo keys and the Keyboard Shortcut action by its {token} form — for example {json beautify}, {url decode}, {uppercase}, {remove query}, and {ascii}. The complete list lives in cmds.txt next to Textulon, and the alias map is the single source of truth in TransformRegistry.keysCommandAliases.

Invocation

Textulon accepts a small CLI surface so that any front-end can launch it. The simplest form:

Textulon [<path> | --file <path> | -i <path>]     # autodetect mode by extension+contents
        [--input-file <path>  | --input-stdin]   # plain text in, --mode controls mode
        [--payload-file <path>| --payload-stdin] # full JSON payload (§5)
        [--mode auto|plain|json|xml|markdown|mermaid|svg|html|robokeys|hex|ascii]
        [--output-behavior copyOnly|pasteBack|viewOnly]
        [--preferred-transform <id>]
        [--on-top yes|no]
        [--version | -v]
        [--help | -h]

Input vs. payload. The --input-* flags pass just the text; everything else (mode, output behaviour, etc.) comes from other CLI flags. The --payload-* flags pass an entire §5 JSON document — with sourceText, mode, outputBehavior, originatingAppBundleId, originFrame, etc. — which is what an automation front-end (Stream Deck, Raycast, robo) sends when it wants to carry origin metadata. A bare <path>, --file, and -i are the friendly forms: load a file and let Textulon pick the mode.

In the running app:

ShortcutAction
OOpen File — asks before discarding unsaved changes.
SSave back to the originating file (Save As if none).
SSave As — extension defaults from the current Mode.
NNew untitled tab.
Tab / TabSwitch to next / previous document tab.
P / PPrint rendered/default view / Print Source. The standard macOS print dialog includes PDF export.
WIn the editor: dismiss the find panel or Query tab first; close the window only when no overlay is up. In Help/Settings: close that window.
EscCloses the Help window. In the editor: dismisses the find panel or Query tab; otherwise no-op.
QQuit Textulon, regardless of which overlay is up.
FFind / Replace. In source text: literal or regex search with context rows, jump, replace current, and replace all. In Hex view: byte-aware find.
GFind next in the active find target.
JJump to line.
FJSONPath / XPath jump prompt in JSON or XML mode.
M / MJump to match / select matching block.
Apply Transform — operates on the selection if any; in RoboKeys mode, runs the keys command into the scratch pane.
B / IIn Markdown and HTML modes, insert Bold / Italic at the cursor (or wrap the selection).
] / [Indent / outdent selected or current source lines.
ZShow visible undo history and roll back to a selected record.
dragRectangular source selection; copy/cut/paste preserves columns inside Textulon.
? / F1 / ?Open the Help window.
PToggle Stay on Top (Pin).
MFocus the Mode picker (then Space / arrows to change).
TFocus the Transform picker (then Space / arrows to change).
IOpen the Snippets menu (Markdown, HTML, and other snippet-capable modes only); then to insert, or for a sub-group item.
H / TEnter Hex view / return to Text view.
EHex: Edit Byte at Offset.
I / Hex: Insert Byte / Delete Byte.
R / 0Toggle Hide Raw / force-show the source pane.
W / LToggle source word wrap / line numbers.
+ / = / - / 0Zoom in, zoom out, or reset to actual size.

A richer JSON payload mirrors §5 of the original spec:

{
  "sourceText": "<p>Hi</p>",
  "mode": "html",
  "preferredTransform": "html.escape",
  "outputBehavior": "copyOnly",
  "originatingAppBundleId": "com.apple.Safari",
  "originatingWindowTitle": "Some tab",
  "originFrame": { "x": 100, "y": 200, "width": 500, "height": 30 }
}

On Copy or Paste Back, the final text is placed on the system pasteboard and printed to stdout (newline-terminated). On Cancel, Textulon exits with code 1 and leaves the clipboard alone.

Samples

The Textulon/Samples/ directory ships files designed to exercise transforms, preview modes, and structured parsing. The same files drive TextulonTests, so transform regressions fail the suite.

FileTry
messy.jsonJSON Beautify → JSON Minify (round-trips).
compact.xmlXML Beautify — adds line breaks and indentation.
example.htmlSee the rendered HTML preview; HTML Escape ↔ HTML Unescape.
example.mdSee the rendered Markdown preview.
AdSignaler-Spec.mdMarkdown with inline Mermaid diagrams; try rendered HTML export as SVG or PNG images.
example.mmdSee a Mermaid flowchart diagram rendered in the preview.
example-sequence.mmdSee a Mermaid sequence diagram rendered in the preview.
example-er.mmdSee a Mermaid ER diagram rendered in the preview.
*.svgUse SVG mode to inspect transparent backgrounds, frame the canvas, insert SVG snippets, and tune transforms.
encoded.urlURL Decode reveals query params and JSON.
secret.base64Base64 Decode reveals plaintext.
unsorted.txtSort Lines / Unique Lines / Sort Unique Lines.
rot13.txtROT-13 — apply twice to verify round-trip.
complex.jsonExplore the JSON tree, path row, JSONPath query, and collapsible HTML tree export.
complex.xmlExplore the XML tree, XPath query, positional paths, and collapsible HTML tree export.
legacy-word-binary.docSynthetic legacy-DOC-like binary fixture; opens in Hex by extension and provides readable strings for ASCII extraction.
broken.json / broken.xmlConfirm parser error display and source offsets.

Screenshots

Drop captures into docs/img/ with the filenames below to fill these slots.

Main window — JSON beautify

Textulon showing JSON beautify on messy.json

Markdown — rendered preview

Textulon rendering Markdown

HTML — rendered preview

Textulon rendering HTML in preview

Structured JSON — tree, path row, and query

Textulon showing JSON tree, path row, and query results

XML — XPath navigation

Textulon showing XML tree and XPath navigation

Hex view — byte grid and byte-aware find

Textulon showing Hex view with byte grid and find

Privacy

Textulon is designed to handle secrets safely:

Integration

Textulon depends on the same RoboCore library as Mac-A-Tron and SnapMatic. Three useful pairings:

From a Stream Deck button

The Transform Text action can paste back, copy to clipboard, or open the acquired text in Textulon. It has separate single-click and double-click transform blocks; when double-click is enabled, the key graphic shows the primary title at top and the double-click title at bottom.

# Conceptually:
robo keys "{cmd+c}{pause 100}"
pbpaste | Textulon --input-stdin --mode auto --output-behavior copyOnly

From a CLI pipe

curl -s https://example.org/api/notes | Textulon --input-stdin --mode json

Programmatically

The TransformRegistry in RoboCore can be used directly from any Swift project that depends on it — Textulon is just one front-end:

import RoboCore

let beautify = TransformRegistry.shared.byId("json.beautify")!
let pretty   = try beautify.transform(messy)
Window placement. Textulon remembers the last window size, but launch placement prefers the current focused-window screen or the mouse screen. The saved position is used as a fallback only when it is still fully visible.