A sticky-note pegboard for your terminal. Mouse-first, keyboard-friendly, transparent over your tmux theme. ASCII-textured cork, dangling red strings, smooth zoom, and multiple named boards you can switch between.
- A small TUI meant to live in a tmux pane, always open.
- A cork pegboard rendered entirely in ASCII — your terminal background shows through, no solid fill.
- Sticky notes you drag with the mouse or nudge with the keyboard. Colored paper, red pin, ornate woven borders, paper-fiber flecks, drop shadow, fold glyph.
- Red strings between notes (or pinned to bare cork). Slack or tight. In-front-of or behind the notes. A weighted "swoop" while you pull the free end.
- Multiple named boards you cycle through like tmux windows
(
work,personal,ideas, …). Each board has its own notes, strings, zoom, font, highlight color, and a unique cork grain. - Configurable background — two independent axes from a live-preview
popup (
b): an ASCII-cork on/off toggle, and a fill color (transparent so the terminal/tmux theme shows through, a curated palette of dark and light editor themes, or a custom hex). Mix freely — cork over a dark color, or cork off for a clean solid. One global choice for the workspace. - Light and dark palettes — the board renders foreground-only, so its
colors have to match your terminal background. redthread detects that
background at startup and picks the matching palette;
Tflips it, and--theme=light|darkorRT_THEMEforces it when detection can't see through tmux or ssh. See Theme. - Workspace zoom — 5 levels (
-3..+1) that scale note sizes and positions, so you can pack many stickies into an overview or lean in on a few. - 3D zoom-to-edit — a smooth scale + lift + shadow animation that settles into a card with a live textarea.
- Or edit in
$EDITOR—ehands the note to your real editor as a markdown file and reads it back when you quit. See External editor. - Optional vim bindings —
--vimturns the note card modal: normal / insert / visual, the motions and operators you actually use. Off by default. See Vim mode. - 9 paper tints + 9 highlight colors + 8 text styles (Unicode math alphabets: plain, bold, italic, bold-italic, script, fraktur, double-struck, monospace).
- Undo everything — a 50-step undo/redo stack covering every change:
paste, edit, tint, move, string, note delete, even a deleted board.
uundoes,ctrl+rredoes. - Persists to
$XDG_DATA_HOME/redthread/notes.jsonwith debounced writes, keeping the last 20 distinct versions underbackups/.
Requires Go 1.24+.
git clone https://github.com/B33pBeeps/redthread.git
cd redthread
go build -o redthread ./cmd/redthread
./redthreadOr:
go install github.com/B33pBeeps/redthread/cmd/redthread@latest
redthreadFirst run with no save file gets a small demo board. redthread --fresh
ignores the save and reseeds.
The board sets only foreground colors and lets your terminal background show through, so the palette has to know whether that background is dark or light. On startup redthread asks the terminal for its background color and picks the matching palette.
Plenty of setups swallow that query — tmux without allow-passthrough,
some ssh sessions, anything that isn't a TTY. When the answer doesn't come
back, redthread assumes dark. Override it:
redthread --theme=light # force the light palette
redthread --theme=dark # force the dark palette
redthread --theme=auto # detect (the default)
export RT_THEME=light # same, for your shell rc or tmux configPrecedence is --theme → RT_THEME → the saved choice → detection.
Inside the app, T toggles light/dark and remembers it, so the next launch
skips detection entirely. The choice is stored per workspace in
notes.json.
--theme=auto is an override like the other two, not the absence of one:
it re-detects and forgets a saved choice, so it's the way back to
detection after pressing T. (Deleting the theme key from notes.json
does the same thing.)
Both palettes carry the same nine tints and nine highlight colors under the same names, so switching themes never disturbs a note's saved colors.
Click without moving for a small jiggle, drag for a drop bounce, tab
cycles with the same jiggle, enter zooms into a card with a smooth 3D
transition, esc lands back with a thud.
Pull strings between notes (or to bare cork as wall-pins) — the free end
swoops behind the cursor with weight. [ ] cycles the hovered string;
t toggles tight / slack; f toggles in-front-of / behind notes; x
cuts. Strings stay visibly attached when you drag a note.
Top tab bar shows all boards; each has its own cork grain so they feel
distinct. > / < cycle, B creates and drops you straight into rename,
R renames, D D deletes (two-press confirm).
| click a note | select + raise |
| drag | move (drops with a bounce) |
| drag bottom-right corner | resize the note |
| double-click | zoom-to-edit |
| click empty cork while pulling a string | place a wall-pin |
| key | action |
|---|---|
tab / shift+tab |
cycle note selection |
← ↑ ↓ → or hjkl |
nudge (overshoots in motion direction) |
shift + ← ↑ ↓ → |
nudge ×5 |
H / L |
shrink / grow selected note width |
K / J |
shrink / grow selected note height |
enter |
zoom-to-edit |
n / d / r |
new / delete / raise note |
e |
open the selected note in $EDITOR (see External editor) |
u |
undo (multi-step: covers every change, not just deletes) |
ctrl+r |
redo |
ctrl+y / ctrl+p |
copy / paste the selected note |
1 – 9 |
tint the selected note (yellow, pink, blue, green, purple, orange, teal, cream, coral) |
c |
cycle the global highlight (border) color |
T |
toggle the light / dark palette (remembered across launches) |
a |
open the font menu (live preview, enter to commit, esc to cancel) |
b |
open the background menu — toggle cork (←/→) and pick a fill color (transparent, a curated palette of dark and light editor themes, or custom hex); live preview, enter to commit, esc to cancel |
- / = / 0 |
zoom out / in / reset (5 levels) |
s |
start pulling a red string (arrows/hjkl nudge endpoint, tab snap to next note, enter commit, esc cancel) |
[ / ] |
cycle the hovered string |
t |
toggle tight / slack on the hovered string |
f |
toggle in-front-of / behind notes |
x |
cut the hovered string |
X |
cut every string on the selected note |
? |
toggle the help panel (slides up from the bottom) |
q / ctrl+c |
quit (saves) |
| key | action |
|---|---|
> (or .) |
next board |
< (or ,) |
previous board |
{ / } |
re-order: move the active board left / right (wraps) |
B |
new board (drops you into rename) |
R |
rename the active board |
D |
delete the active board (press twice within 2s; refuses if it's the last one) |
| key | action |
|---|---|
| typing | edit body (first non-empty line is the title; text renders in the board's font) |
ctrl+y / ctrl+p |
copy the note / paste at cursor (system clipboard) |
ctrl+e |
hand the current text to $EDITOR |
| drag mouse | native terminal selection — copy with your terminal's hotkey |
esc |
close + save with reverse transition |
ctrl+s |
save without closing |
With --vim, the card is modal — see Vim mode.
The built-in card is a textarea — fine for a few lines, less so for a long
note. e on a selected note (or ctrl+e from inside the card) writes it
to a temp .md file, suspends the TUI, and runs your editor:
export EDITOR=nvim # or: hx, micro, "code -w", "emacsclient -nw"VISUAL wins over EDITOR if both are set, by the usual convention. The
value is a command line, not just a program name, so flags work — code -w and subl -w need their wait flags or the editor returns immediately.
With neither variable set, e says so instead of guessing at vi.
The file uses the same convention as the built-in editor: the first non-empty line is the title, the rest is the body.
write rfc
- auth spec
- sync protocolThe .md extension is deliberate — it's what makes your editor turn on
markdown highlighting.
Nothing is written back unless the file actually changed, so quitting
without saving is a no-op. The note is also left alone if the editor exits
non-zero (:cq in vim), if the file can't be read, or if it comes back
empty — a stray :q! on the wrong buffer shouldn't blank a note.
Off by default. Turn it on for a run, or for good:
redthread --vim # this run (and remembered afterwards)
redthread --vim=false # turn it off again (also remembered)
export RT_VIM=1 # for a shell rc or tmux configThe card then opens in normal mode, and the footer shows which mode you are in. What's implemented:
| motions | h j k l, w b e, 0 $, gg G, arrow keys |
| counts | 3l, 2dd, 5G, and so on |
| insert | i a I A o O, esc back to normal |
| delete | x, dd, D, dw, d$, d0 |
| change | cw, cc, C |
| yank/put | yy, p, P, and dd fills the same register (so ddp swaps lines) |
| visual | v, then a motion, then d / c / y |
esc in normal mode places the note back on the board, the same as esc
without vim enabled. ctrl+s, ctrl+y, ctrl+p, and ctrl+e keep their
meanings in every mode — they are app controls, not editing.
Anything not in that table is ignored rather than approximated. A key that silently does something close to what vim would do is worse than one that does nothing.
The behavior above was checked against real vim: internal/app/vim_test.go
contains a table of buffers and key sequences whose expected results were
produced by running them through vim -Nu NONE -es. That turned up five
bugs which hand-written tests had happily agreed with, including dw on a
line's last word deleting nothing and cw swallowing the following space.
Notes live at $XDG_DATA_HOME/redthread/notes.json, defaulting to
~/.local/share/redthread/notes.json. Writes are debounced (400 ms) so
rapid drags coalesce into one flush.
A legacy brainfartadhdfixerupper/ directory auto-migrates on first
launch under the new name. v3 single-board files migrate forward into
the v4 workspace envelope.
u undoes and ctrl+r redoes, up to 50 steps. The stack covers every
change — pasting over a note, an edit, a tint, a move, a string, a deleted
note, a deleted board — not only deletions. A whole editing session
(enter … esc) undoes as one step, and a held arrow key coalesces into
one step rather than forty.
Because pasting replaces a note's entire contents, ctrl+p over a note
that already has text asks first: the footer reads ctrl+p again to replace <title>, and only a second press within 3 seconds commits.
Undo lives in memory, so for anything that outlives the session there are
on-disk backups. Each save that actually changes something first copies
the previous notes.json to:
~/.local/share/redthread/backups/notes-YYYYMMDD-HHMMSS.nnnnnnnnn.json
The newest 20 are kept; saves that would write identical content don't
rotate, so idling doesn't flush real history out of the ring. To recover,
copy one back over notes.json while redthread is closed:
cd ~/.local/share/redthread
ls backups/ # newest last
cp backups/notes-20260814-160355.812734000.json notes.jsonThe workspace also stores a global background preference with two independent
fields: cork (the ASCII texture overlay, on/off) and color (a hex fill, or
omitted/"" for transparent). For example {"cork":false,"color":"#1a1b26"} is
a solid Tokyo Night background with no cork. Files written before this option
load as cork-on + transparent, unchanged.
Schema (v6):
{
"schemaVersion": 6,
"activeIdx": 0,
"background": { "cork": true },
"theme": "light",
"boards": [
{
"name": "main",
"grainSeed": 1777118907643042768,
"zoom": 0,
"textMode": 0,
"highlightColor": 0,
"notes": [ { "id": "…", "title": "…", "body": "…",
"x": 3, "y": 1, "tint": "yellow",
"created": "…", "updated": "…" } ],
"strings": [ { "a": { "note": "idA" }, "b": { "note": "idB" },
"tight": false, "front": false } ]
}
]
}theme is "light", "dark", or absent for auto-detect. v5 files simply
lack the key and load as auto, so nothing needs migrating.
Colors live in internal/app/palette.go, as two Palette values:
darkPalette()/lightPalette()— the full color set for each terminal background, includingTints(the 9 paper colors),SelBorderChoices(the 9 highlight colors), the cork shades, the shadow, and the chrome.ApplyTheme(light bool)installs one of them into the package-level vars every draw site reads.
Text styles and character pools stay in internal/app/theme.go:
TextModes— the Unicode-alphabet font options.StarChars/PoreChars/BlotchChars— cork-texture char pools.
Edit any of those, rebuild, and the whole app picks up the new values (help panel + menu + cycle included).
When adding colors, note the rule the palettes follow: text needs
enough contrast to read (the light palette keeps ink under ~0.5 Rec-601
luma), but texture — cork specks, shadow dither — must instead sit the
same distance from the background as its dark counterpart sits from
black. Making texture merely "dark enough" turns the board into noise.
theme_test.go enforces both rules.
cmd/redthread/
main.go thin entry — calls internal/app.Run()
internal/app/
app.go Run(): flag parsing, load/seed workspace, launch
model.go MVU (Model/Update/View, key + mouse routing,
tab bar, help panel, rename mode)
notes.go Note, Board, Workspace, StringConn, cork gen
draw.go cork + note rendering (shadow, tape, borders,
text, fibers)
wire.go red-string curve rendering + pull physics
render.go cell grid + coalesced ANSI emitter (transparent bg)
theme.go color type, dither, halftone, text styles
palette.go the dark + light palettes and ApplyTheme
theme_mode.go --theme / RT_THEME parsing + background detection
anim.go zoom transition timeline + easings
edit.go canvas-drawn edit frame + bubbles/textarea splice
menu.go font-picker + background picker popups
extedit.go $EDITOR hand-off (temp file, exec, read back)
vim.go modal editing state machine (buffer + cursor)
vimedit.go renders the vim buffer into the card
storage.go XDG JSON persistence, migrations, backup rotation
history.go undo/redo stack over workspace snapshots
docs/
hero.gif, feel.gif, strings.gif, boards.gif the demos above
Latest — v0.7.3
The first tagged release, and the first with outside contributions. Thank you @saforem2, @ambareeshav and @webbrain-one.
Features
- Light terminal palette with auto-detection,
--theme=light|dark|auto,RT_THEME, and aTtoggle that is remembered; five light fills in the background picker. The dark palette is unchanged. (#3, @saforem2) - Undo everything — a 50-step undo/redo stack (
u/ctrl+r) covering every change, including deleted boards;ctrl+pover a non-empty note asks first; each content-changing save keeps the previousnotes.jsonunderbackups/, newest 20. (#4, @saforem2) - Open a note in
$EDITOR—eon the board orctrl+ein the card hands the note to your editor as a markdown file and reads it back; every failure path leaves the note alone. (#5, @saforem2) - Optional vim bindings in the note card:
--vim/RT_VIM=1, off by default,--vim=falseturns it off again. (#6, @saforem2) - Resizable notes — drag the bottom-right corner, or
H/LandK/J; sizes persist and scale with zoom. (#7, @ambareeshav) - README fix for the menu.go description. (#2, @webbrain-one)
Fixes
- Keyboard nudges and resizes are undoable like mouse moves; a refused last-board delete no longer consumes an undo slot.
- Returning from
$EDITORgives the mouse back and no longer doubles the animation tick. ctrl+pin vim normal/visual mode pastes into the live buffer instead of a hidden one.- With a solid fill color, the open card and footer now use the fill instead of the terminal background.
- The outgoing board in a board-switch crossfade is drawn with its own highlight color.
- The debounced saver no longer reads the live workspace from its timer goroutine (a data race under heavy editing); it snapshots on the UI thread instead.
- Tests can no longer touch the real
notes.json: the package redirectsXDG_DATA_HOMEto a temp dir for every test.
Previous
Features
- Background is now configurable — press
bfor a picker with two independent axes: a cork on/off toggle (←/→) and a fill color — transparent (your terminal/tmux background shows through, the default), a curated dark-theme palette (github dark, tokyo night, catppuccin, dracula, nord, …), or a custom hex. Mix freely; the choice is global to the workspace and persists. (#1)
Earlier
Fixes
- Fullwidth font removed — it never rendered correctly.
}(move-active-board-right) was a no-op; it now actually shifts the active board.- Opening a freshly created blank note no longer drops the cursor two rows below the title.
Features
- New notes start empty (only the initial seed has demo content).
- Open notes now render in the board's selected font.
- Strings are fully keyboard-drivable from
s— arrows /hjklnudge the free end,tabsnaps to next note,entercommits,esccancels. - Boards re-orderable in the tab bar with
{and}. - Native terminal text selection inside open notes (mouse capture is released in edit mode).
ctrl+ycopies the selected note's text;ctrl+ppastes — into the cursor in edit mode, or onto the selected note in board mode.uundoes the most recent note delete (restores connected strings too).- Subtle dim-crossfade + horizontal slide when switching boards.
Personal project. MIT — use it, fork it, do whatever.



