https://github.com/cablehead/stacks.nu
Event-sourced "clip" manager: xs + http-nu + datastar.
https://github.com/cablehead/stacks.nu
Last synced: about 2 months ago
JSON representation
Event-sourced "clip" manager: xs + http-nu + datastar.
- Host: GitHub
- URL: https://github.com/cablehead/stacks.nu
- Owner: cablehead
- Created: 2026-04-27T15:16:41.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-05-11T15:52:14.000Z (3 months ago)
- Last Synced: 2026-06-18T21:31:43.964Z (about 2 months ago)
- Language: Nushell
- Homepage:
- Size: 478 KB
- Stars: 4
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
stacks.nu
A "clip" manager where a clip can be a README, an image, a random thought,
a JSON dataset. Pipe clips through Nushell pipelines. Built on
[xs](https://github.com/cablehead/xs) for the log,
[http-nu](https://github.com/cablehead/http-nu) for the routes,
[Datastar](https://data-star.dev) for the UI.
The page holds no state. Selection, mode, the visible HTML -- all projected
on the server from frames in the event store and patched into `` over
SSE.
https://github.com/user-attachments/assets/10d6b5e8-d6a3-4e4d-ba85-79c7a803b94b
## Run
```sh
http-nu --datastar --store ./store :4777 -w ./www/serve.nu
```
Open .
First boot drops in a `Tutorial` stack whose clips walk you through the
keymap; delete it when you're done.
## What's a clip
Any byte sequence with a mime type. Text clips render inline; images render
via `/cas/` with `Cache-Control: immutable` (identical content across
stacks shares a URL and a cache entry). New mime types fall through to a
generic preview without code changes.
Stacks group clips. Sort modes:
- `auto` -- ordered by activity, newest first. Edits float a clip to the top.
- `manual` -- fractional `position` strings; user-curated order.
## Piping
Press `P` on a clip to open the pipe panel. Type any Nushell pipeline; `$in`
is the clip's body. `Mod+Enter` runs. Result renders below; errors surface
with source excerpts pointing at the offending span.
```nushell
$in | from json | get users | length
str upcase
lines | where {|l| $l =~ "TODO"}
```
History is persisted to the log -- arrow up or start typing to recall.
Implemented via http-nu's `.run` so any change to the Nushell language or
library is immediately available.
## Trash
`clip.delete` and `stack.delete` snapshot what was removed into projection
state. `Shift+T` (or the actions panel) opens the trash; Enter restores.
Deleting a stack with previously-deleted clips inside is handled -- restore
the stack first, then the orphaned clips become restorable.
## Event protocol
Persistent topics (the durable log):
| Topic | Meta |
| --------------- | ----------------------------------------------------- |
| `stack.add` | `{name, sort}` |
| `stack.update` | `{id, name?, sort?}` |
| `stack.delete` | `{id}` |
| `stack.restore` | `{target}` (id of the original `stack.delete` frame) |
| `clip.add` | `{stack_id, mime_type, position?}` + body -> CAS |
| `clip.update` | `{id}` + body -> CAS (clip identity stays stable) |
| `clip.patch` | `{id, stack_id?, position?, mime_type?, ...}` |
| `clip.delete` | `{id}` |
| `clip.restore` | `{target}` (id of the original `clip.delete` frame) |
| `pipe.command` | `{source, clip_id}` (pipe history) |
A `stack.add` frame's id _is_ the stack id; same for `clip.add`. Stacks are
ordered by recent activity. Prior versions of a clip are reachable by
walking earlier `clip.add`/`clip.update` frames with the same id.
Ephemeral UI signals (selection, mode toggles, transient input, pipe
results) go through http-nu's [Local Bus](https://github.com/cablehead/http-nu#local-bus) --
in-process pub/sub, no persistence, no cross-host wire traffic. The
`/updates` SSE handler interleaves `.cat -f` with `.bus sub` and folds both
through the same projection.
## Keys
Mode-aware. The status bar at the bottom always lists the active mode's
keys; clicking a binding fires the same action a keypress would. `Mod` is
`Cmd` on macOS, `Ctrl` elsewhere (resolved per-request via
`sec-ch-ua-platform` / User-Agent).
Main:
| Key | Action |
| --------------------- | ---------------------------- |
| `J` / `K` | Next / prev clip |
| `Shift+J` / `Shift+K` | Next / prev stack |
| `N` | New clip |
| `Shift+N` | New stack (timestamp name) |
| `E` | Edit selected clip |
| `P` | Pipe selected clip |
| `R` | Rename selected stack |
| `DEL` | Delete selected clip |
| `Shift+DEL` | Delete selected stack |
| `Shift+T` | Open trash |
| `Mod+K` | Open actions panel |
Compose / edit / rename / pipe modals: `Mod+Enter` commits, `Esc` cancels.
## Layout
```
www/
serve.nu route table + view-model
projection.nu pure fold over the event stream
test.nu projection + route round-trip tests
templates/ minijinja templates rendered server-side
static/ keys.js, action-panel.js, base.css
stacks/mod.nu protocol builders (`stack add`, `clip move`, ...)
scripts/
lint.nu topiary fmt on every .nu
check.sh verify formatted
shoot-design.sh screenshot /design and post as a clip
tests-browser/ playwright + system chromium e2e
```
## Tests
```sh
http-nu eval --store /tmp/test ./www/test.nu # projection + routes
node tests-browser/pipe-history.test.mjs # browser e2e
```
## Formatting
```sh
nu scripts/lint.nu # format every .nu via topiary
scripts/check.sh # verify (exit non-zero if drift)
```