Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ COPY --from=builder --chown=bun:bun /app/server ./server
# Only the src modules the server imports at runtime: sync-api needs sync-schema, and
# pastes needs code.ts (language detection; its one dependency, highlight.js, is in node_modules),
# and billing needs diff.ts (the AI summary is asked about the changed lines).
COPY --from=builder --chown=bun:bun /app/src/sync-schema.ts /app/src/flavors.ts /app/src/emoji.ts /app/src/code.ts /app/src/diff.ts /app/src/sensitive.ts /app/src/sync-crypto.ts /app/src/blob-store.ts ./src/
COPY --from=builder --chown=bun:bun /app/src/sync-schema.ts /app/src/flavors.ts /app/src/emoji.ts /app/src/code.ts /app/src/csv.ts /app/src/diff.ts /app/src/sensitive.ts /app/src/sync-crypto.ts /app/src/blob-store.ts ./src/
COPY --from=deps --chown=bun:bun /app/node_modules ./node_modules
RUN mkdir -p /data && chown bun:bun /data

Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ npx @profullstack/readm3
readm3 # browse markdown under the current directory
readm3 ./docs # browse a directory
readm3 CHANGELOG.md # open a file, browse its directory
readm3 sales.csv # a CSV or TSV opens as a sortable, filterable grid
readm3 --theme nord # pick a palette
```

Expand All @@ -82,14 +83,15 @@ readm3 --print CHANGELOG.md # rendered, colored, to the terminal
readm3 --print README.md | less -R # into a pager
curl -s https://example.com/x.md | readm3 --print
readm3 --print notes.md | grep TODO # no escapes when stdout is a pipe
readm3 --print data.csv # a CSV or TSV prints as an aligned table
```

Color follows the usual rules: on when stdout is a terminal, off when it is not,
and `NO_COLOR` / `FORCE_COLOR` are honored. `--color always|never|auto` overrides
that either way. With `--print`, `--width` sets the render width rather than the
sidebar.

The browser only shows markdown. Directories with nothing beneath them are
The browser shows markdown and tables (`.csv`, `.tsv`, `.tab`, `.psv`). Directories with nothing beneath them are
pruned, `node_modules` and friends are never walked, and README sorts first.

## Keys
Expand All @@ -108,6 +110,43 @@ pruned, `node_modules` and friends are never walked, and README sorts first.
The mouse works too: the wheel scrolls whichever pane it is over, and a click
opens a file.

### Tables

A CSV or TSV opens as a grid instead of as text, in the spirit of
[tabiew](https://github.com/shshemi/tabiew): a frozen header, row numbers, a cell
cursor, and columns that scroll in from the side. The delimiter (comma, tab,
semicolon, pipe) and the header row are detected, quoted fields with commas and
line breaks are read correctly, and every column is typed (number, date, yes/no,
text) so sorting and comparisons read values the way people do.

| | |
|---|---|
| `←` `↑` `↓` `→` `h` `j` `k` `l` | move the cell cursor (`←` on the first column returns to the files) |
| `0` `$` | first / last column |
| `o` | sort by the cursor's column: ascending, descending, off |
| `/` | filter the rows |
| `enter` | the row as a sheet, every column on its own line |
| `c` | the column's statistics: rows, empty, distinct, min, max, sum, mean, median, top values |
| `v` | the raw text instead of the grid |
| `e` | edit the text, as with any file |

The filter takes space-separated terms that must all match: `london` (any cell),
`city:london` (that column contains it), `status=open`, `country!=US`, `age>30`,
`price<=9.99`, `joined>=2024-01-01`, `#3>10` (the third column), and `!draft` to
negate. Quote a value with spaces: `city:"new york"`.

The same grid is in the web editor, the desktop app and on paste links: click a
column to sort, Σ for its statistics, a row to see it whole, and **Copy view** /
**Download view** for just the rows you kept. A paste with no file name is
recognised as a table from its content. The terminal grid is hqtui's `dataGrid`
widget; the parsing is `src/csv.ts`, shared by every surface.

Agents get it through the MCP server (`readm3 mcp`) as `table_query`: point it at
CSV text, a local `path`, a paste link or a raw `url`, with an optional `filter`,
`sort`/`descending`, `limit`/`offset` and a `stats` column, and it returns the typed
columns, the match count and just the rows asked for, so a 50,000-row file never
has to go into context whole. It runs locally and needs no account.

### Editing

`e` puts the caret in the file. `esc` takes it out, and the preview re-renders
Expand Down
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"protocols": [{ "name": "readm3", "schemes": ["readm3"] }],
"fileAssociations": [
{ "ext": ["md", "markdown"], "name": "Markdown", "role": "Editor" },
{ "ext": ["txt", "json", "yaml", "yml", "toml", "js", "ts", "py", "sh", "css", "html"], "name": "Text", "role": "Editor" }
{ "ext": ["txt", "json", "yaml", "yml", "toml", "js", "ts", "py", "sh", "css", "html"], "name": "Text", "role": "Editor" },
{ "ext": ["csv", "tsv"], "name": "Table", "role": "Viewer" }
],
"mac": {
"artifactName": "readm3-mac-${arch}.${ext}",
Expand All @@ -58,12 +59,12 @@
"category": "Development",
"synopsis": "A plain, fast text editor for Markdown and code.",
"description": "readm3 is a quiet text editor: line numbers, multiple cursors, find and replace, folding, quick open and a command palette, with Markdown rendered as a document. It opens folders from disk and saves back to them, and works offline.",
"mimeTypes": ["text/plain", "text/markdown", "application/json", "text/x-python", "text/javascript", "text/css", "text/html", "application/xml", "text/x-shellscript", "application/x-yaml", "text/x-c", "text/x-c++", "text/x-go", "text/x-rust", "text/x-java"],
"mimeTypes": ["text/plain", "text/markdown", "application/json", "text/x-python", "text/javascript", "text/css", "text/html", "application/xml", "text/x-shellscript", "application/x-yaml", "text/x-c", "text/x-c++", "text/x-go", "text/x-rust", "text/x-java", "text/csv", "text/tab-separated-values"],
"desktop": {
"entry": {
"Name": "readm3",
"GenericName": "Text Editor",
"Keywords": "text;editor;markdown;code;",
"Keywords": "text;editor;markdown;code;csv;tsv;table;",
"StartupWMClass": "readm3"
}
}
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/readm3",
"version": "0.23.0",
"version": "0.24.0",
"description": "Markdown editing and sharing for people and teams. Web/PWA, CLI, API and MCP with version history, view/edit links, and a terminal reader.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -46,7 +46,7 @@
"@profullstack/auth-system": "^0.8.3",
"@profullstack/cli": "^0.1.0",
"@profullstack/footer": "latest",
"@profullstack/hqtui": "^0.5.0",
"@profullstack/hqtui": "^0.11.0",
"@profullstack/synconfig": "^0.1.3",
"@profullstack/x402-gateway": "^0.11.0",
"highlight.js": "^11.12.0",
Expand Down
33 changes: 33 additions & 0 deletions site/assets/viewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,36 @@ html, body.reader-app { height: 100%; overflow: hidden; }
.reader-app .summary-listen { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 4px 0 8px; }
.reader-app .summary-listen audio { width: 100%; max-width: 420px; height: 36px; }
.reader-app .summary-listen small { color: var(--muted); }

/* Tables (CSV, TSV): a sticky header that sorts, numbered rows, a filter. Same theme variables as code. */
.code-view.table-view { width: 100%; min-width: 0; }
.table-view { font: 13px/1.6 var(--mono); color: var(--fg); display: flex; flex-direction: column; gap: 10px; }
.table-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.table-filter { flex: 1 1 260px; min-width: 0; padding: 7px 10px; font: 12px var(--mono); color: var(--fg); background: var(--surface); border: 1px solid var(--border); border-radius: 5px; }
.table-filter:focus { border-color: var(--primary); outline: none; }
.table-count { color: var(--muted); font-size: 11px; white-space: nowrap; }
.reader-app .table-tools button { padding: 5px 9px; font-size: 11px; color: var(--muted); }
.table-scroll { overflow: auto; max-width: 100%; max-height: calc(100dvh - 260px); border: 1px solid var(--border); border-radius: 6px; }
.data-table { border-collapse: separate; border-spacing: 0; width: max-content; min-width: 100%; }
.data-table th, .data-table td { padding: 3px 12px; border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); border-right: 1px solid color-mix(in srgb, var(--border) 60%, transparent); text-align: left; white-space: pre; max-width: 40ch; overflow: hidden; text-overflow: ellipsis; vertical-align: top; }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); padding: 0; white-space: nowrap; max-width: none; border-bottom: 1px solid var(--border); }
.data-table .rownum { position: sticky; left: 0; z-index: 1; background: var(--surface); color: var(--muted); text-align: right; font-weight: 400; font-size: 11px; user-select: none; }
.data-table thead .rownum { z-index: 3; padding: 3px 12px; }
.data-table tbody tr { cursor: pointer; }
.data-table tbody tr:nth-child(even) td { background: color-mix(in srgb, var(--fg) 2.5%, transparent); }
.data-table tbody tr:hover td, .data-table tbody tr:focus td { background: color-mix(in srgb, var(--primary) 9%, transparent); }
.reader-app .column-sort { border: 0; border-radius: 0; background: transparent; padding: 5px 4px 5px 12px; font: 600 12px var(--mono); color: var(--fg); }
.reader-app .column-sort:hover { background: transparent; border: 0; color: var(--primary); }
.column-type { margin-left: 4px; color: var(--muted); font-weight: 400; font-size: 10px; }
.reader-app .column-sort[aria-sort="ascending"] .column-type, .reader-app .column-sort[aria-sort="descending"] .column-type { color: var(--primary); font-size: 11px; }
.reader-app .column-stats { border: 0; background: transparent; padding: 5px 10px 5px 2px; font-size: 11px; color: var(--muted); visibility: hidden; }
.data-table thead th:hover .column-stats, .reader-app .column-stats:focus-visible { visibility: visible; }
.reader-app .column-stats:hover { background: transparent; border: 0; color: var(--primary); }
.reader-app .table-more { align-self: flex-start; font-size: 11px; color: var(--muted); }
.sheet-list { display: grid; grid-template-columns: minmax(80px, max-content) 1fr; gap: 8px 20px; margin: 20px 0 0; font: 12px/1.6 var(--mono); }
.sheet-list dt { color: var(--primary); overflow-wrap: anywhere; }
.sheet-list dd { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.sheet-list dd.empty::before { content: "(empty)"; color: var(--muted); }
@media (hover: none) { .reader-app .column-stats { visibility: visible; } }
@media (max-width: 700px) { .table-scroll { max-height: calc(100dvh - 300px); } .data-table th, .data-table td { max-width: 24ch; } }
7 changes: 5 additions & 2 deletions site/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,13 @@ function home(): string {
<p>For the files people actually work on: READMEs, project plans, meeting notes, and proposals.</p></div>
<div class="grid">
<div class="card"><h3>Write, preview, repeat.</h3><p>Open a file or start from scratch. Edit the Markdown, switch to a clean preview, and download the source whenever you need it.</p></div>
<div class="card"><h3>Paste anything, no account.</h3><p>Text, code, JSON, a PDF or an image behind a secret link that expires. Markdown renders, code is highlighted and folds, and only a hash of the secret is kept. <a href="/paste">readm3.com/paste</a>, or <code>readm3 paste FILE</code>.</p></div>
<div class="card"><h3>Paste anything, no account.</h3><p>Text, code, JSON, a CSV, a PDF or an image behind a secret link that expires. Markdown renders, code is highlighted and folds, a table is a sortable grid, and only a hash of the secret is kept. <a href="/paste">readm3.com/paste</a>, or <code>readm3 paste FILE</code>.</p></div>
<div class="card"><h3>“Can view” or “can edit.”</h3><p>Share a link with a reviewer or invite someone to make changes. You own your files and decide who gets access.</p></div>
<div class="card"><h3>Keep the story of the document.</h3><p>Every online save creates a version. See who changed it, share a specific version, or restore an earlier draft without losing the history.</p></div>
<div class="card"><h3>A workspace for your team.</h3><p>Organize people into organizations and teams. Share with a whole group, a few collaborators, or anyone with your link.</p></div>
<div class="card"><h3>A plain text editor.</h3><p>Line numbers, multiple cursors, find and replace, folding, <code>Ctrl+P</code> to any file and <code>Ctrl+Shift+P</code> for every command. Open a folder from disk and <code>Ctrl+S</code> saves back to it. <a href="/viewer">Open the editor</a>.</p></div>
<div class="card"><h3>Files up to 5 GB, a million of them.</h3><p>A free account lifts the limits: up to 1,000,000 files (250,000 of them synced), each up to 5 GB, with 5 GB of end-to-end encrypted storage that syncs to every device. Need more? Pay as you go, $0.05 per GB a month, from a CoinPay balance.</p></div>
<div class="card"><h3>CSV and TSV as a table.</h3><p>Open a <code>.csv</code> or <code>.tsv</code> and it reads as a grid, not a wall of commas: click a column to sort, filter rows with <code>city:london age&gt;30 !draft</code>, open a row as a sheet, see a column's min, max, mean and top values, and copy or download just the rows you kept. The same in the terminal (<code>readm3 data.csv</code>), the desktop app, and on a paste link. <a href="/viewer">Try it</a>.</p></div>
<div class="card"><h3>Summarize any document.</h3><p>Press Summarize in the editor for a short abstract and the key points of the open file, written by Claude. The first 10 documents a week are free on a free account; after that, pay as you go from a CoinPay balance, usually a fraction of a cent each. <a href="/viewer">Try it</a>.</p></div>
<div class="card"><h3>Take the editor with you.</h3><p>Install readm3 from your browser, or get the desktop app: Linux <a href="${REPO}/releases/latest/download/readm3-linux-x86_64.AppImage">AppImage</a> or <a href="${REPO}/releases/latest/download/readm3-linux-x86_64.flatpak">Flatpak</a>, macOS for <a href="${REPO}/releases/latest/download/readm3-mac-arm64.dmg">Apple silicon</a> or <a href="${REPO}/releases/latest/download/readm3-mac-x64.dmg">Intel</a>, <a href="${REPO}/releases/latest/download/readm3-windows-x64-setup.exe">Windows</a>. Then <code>readm3 edit .</code> opens a folder from the terminal, like <code>subl .</code>. Local files work offline; shared documents stay connected.</p></div>
<div class="card"><h3>Use the tools you already use.</h3><p>The web editor, CLI, API, and MCP work with the same documents and permissions. Your terminal and your agents can join the workflow.</p></div>
Expand Down Expand Up @@ -414,7 +415,8 @@ const llms = `# readm3
## What it is

readm3 opens a directory of markdown files and renders them in the terminal in truecolor.
The browser pane shows only markdown, prunes directories with nothing beneath them, never
CSV and TSV files open as a grid with a cell cursor, sort, row filter, row sheet and column
statistics. The browser pane shows only markdown and tables, prunes directories with nothing beneath them, never
walks node_modules, and sorts README first. The reader pane renders headings, emphasis,
inline code, links, fenced and indented code blocks, nested and task lists, blockquotes,
tables with per-column alignment, rules, images as labelled placeholders, and YAML front
Expand Down Expand Up @@ -450,6 +452,7 @@ terminal.
- Editor: ${SITE}/viewer (installable, offline text editor for Markdown and code: multiple cursors, find and replace, Ctrl+P quick open, command palette, saves back to files on disk)
- Desktop app: ${REPO}/releases/latest (the same editor for Linux as AppImage and Flatpak, macOS dmg, Windows installer); \`readm3 edit PATH...\` opens files and folders in it
- Summaries: Summarize in ${SITE}/viewer, or POST /api/v1/summaries/document { path, text } signed in (10 free documents a week, then pay as you go)
- Tables: CSV and TSV (also .tab, .psv) open as a sortable, filterable grid in the terminal reader (hqtui data grid), ${SITE}/viewer, the desktop app and paste links; \`readm3 --print data.csv\` prints an aligned table. Filter language: free text, col:text, col=v, col!=v, col>n, col<=n, !negate, #3 for the third column
- Paste: ${SITE}/paste (anonymous pastes of any text, PDF or image behind an expiring secret link; CLI, curl, API and MCP examples)
- Site: ${SITE}
- Source: ${REPO}
Expand Down
3 changes: 2 additions & 1 deletion site/content/welcome.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ now in a browser. Pick a file on the left. Make yourself at home.

Any text file appears in the explorer: Markdown renders as a document, and
JSON, code, config and logs are shown as highlighted, foldable code you can edit.
PDFs and images open in the browser's own viewer. Build output, dependencies,
A CSV or TSV reads as a table: click a column to sort, type in the filter
(`city:london age>30`), click a row to see it whole. PDFs and images open in the browser's own viewer. Build output, dependencies,
and hidden directories stay out of the way. README files sort first.

## A little less reaching for the mouse
Expand Down
Loading
Loading