docs: bootstrap vault, roadmap and research
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
|||||||
|
# === Obsidian app state (notes themselves ARE tracked in git) ===
|
||||||
|
.obsidian/
|
||||||
|
.trash/
|
||||||
|
|
||||||
|
# === Rust / Cargo ===
|
||||||
|
/target/
|
||||||
|
debug/
|
||||||
|
**/*.rs.bk
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# === Secrets & tokens (never commit) ===
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.token
|
||||||
|
*_token.txt
|
||||||
|
|
||||||
|
# === OS / editor noise ===
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# viexp
|
||||||
|
|
||||||
|
A fast, modular terminal file manager for Linux, written in Rust — a spiritual successor to
|
||||||
|
[ranger](https://github.com/ranger/ranger) that fixes its weak spots: slow startup, fragile
|
||||||
|
image previews, and a hard-to-extend Python core.
|
||||||
|
|
||||||
|
**Status:** planning / learning project (see the [roadmap](docs/roadmap.md)).
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Blazing-fast navigation and previews (kitty graphics protocol first, SIXEL / ueberzug++ / chafa fallbacks).
|
||||||
|
- Out-of-the-box support for images, video, audio, PDF, DJVU, EPUB and Markdown — with every
|
||||||
|
file-type module easy to **disable or replace** at runtime, no recompilation required.
|
||||||
|
- Extension model that feels native to Linux users: drop-in scripts in any language first,
|
||||||
|
embedded Lua scripting later.
|
||||||
|
- Vim-style ergonomics: modal keys, tabs, marks, bulk rename, `:` command line.
|
||||||
|
|
||||||
|
## This repo is also a learning lab
|
||||||
|
|
||||||
|
viexp is deliberately built as a guided learning project: Rust from scratch and technical
|
||||||
|
English, one 20-hour sprint at a time (Josh Kaufman's "First 20 Hours" method).
|
||||||
|
All documentation lives in English inside the [Obsidian vault](docs/index.md) in this repo.
|
||||||
|
|
||||||
|
- [Knowledge index (start here)](docs/index.md)
|
||||||
|
- [Roadmap: 20-hour sprints](docs/roadmap.md)
|
||||||
|
- [Research notes](docs/research/)
|
||||||
|
- [Architecture decisions (ADRs)](docs/decisions/)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
TBD (decided before the first public release).
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
type: article
|
||||||
|
status: draft
|
||||||
|
created: {{date}}
|
||||||
|
updated: {{date}}
|
||||||
|
tags: [article]
|
||||||
|
sources: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{title}}
|
||||||
|
|
||||||
|
> **TL;DR** — one or two sentences: what the reader should learn from this article.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
Why this article exists and who it is for.
|
||||||
|
|
||||||
|
## Main part
|
||||||
|
|
||||||
|
### Section
|
||||||
|
|
||||||
|
## Conclusions
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
-
|
||||||
|
|
||||||
|
---
|
||||||
|
*This article doubles as English writing practice: prefer short, simple sentences; add new vocabulary to the glossary.*
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
type: decision
|
||||||
|
adr:
|
||||||
|
status: proposed
|
||||||
|
date: {{date}}
|
||||||
|
supersedes:
|
||||||
|
tags: [adr]
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
The problem or force that makes this decision necessary.
|
||||||
|
|
||||||
|
## Options considered
|
||||||
|
| Option | Pros | Cons |
|
||||||
|
|--------|------|------|
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
What we chose and why — one paragraph, no hedging.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
- Positive:
|
||||||
|
- Negative / risks:
|
||||||
|
- Mitigations:
|
||||||
|
|
||||||
|
<!-- status: proposed → accepted → superseded by a newer ADR (set `supersedes` on the new one) -->
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
type: devlog
|
||||||
|
date: {{date}}
|
||||||
|
sprint:
|
||||||
|
minutes:
|
||||||
|
tags: [devlog]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dev log — {{date}}
|
||||||
|
|
||||||
|
## What I did
|
||||||
|
-
|
||||||
|
|
||||||
|
## Code review notes
|
||||||
|
Reviewer feedback and what I changed because of it.
|
||||||
|
|
||||||
|
## Rust notes
|
||||||
|
New syntax, borrow-checker battles (→ ErrorJournal), aha-moments.
|
||||||
|
|
||||||
|
## English practice
|
||||||
|
New words and phrases I used today:
|
||||||
|
- *word / phrase* — перевод, my own example sentence
|
||||||
|
|
||||||
|
## Next session
|
||||||
|
-
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
type: error
|
||||||
|
created: {{date}}
|
||||||
|
tags: [error-journal, rust]
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## The error
|
||||||
|
```text
|
||||||
|
paste the exact compiler / runtime error here
|
||||||
|
```
|
||||||
|
|
||||||
|
## What I expected
|
||||||
|
|
||||||
|
## What actually caused it
|
||||||
|
|
||||||
|
## The fix
|
||||||
|
|
||||||
|
## Self-correction cue
|
||||||
|
One-line rule to recognize and fix this error class next time on my own.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
type: glossary
|
||||||
|
created: {{date}}
|
||||||
|
tags: [glossary, english]
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{title}}
|
||||||
|
|
||||||
|
| Term / phrase | Translation | Where I met it | My example sentence |
|
||||||
|
| ------------- | ----------- | -------------- | ------------------- |
|
||||||
|
| | | | |
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
type: research
|
||||||
|
status: inbox
|
||||||
|
confidence: low
|
||||||
|
created: {{date}}
|
||||||
|
question:
|
||||||
|
tags: [research]
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Research question
|
||||||
|
What exactly do we need to learn, and why does it matter for viexp?
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
| # | Source | Kind | Freshness | Key takeaway |
|
||||||
|
|---|--------|------|-----------|--------------|
|
||||||
|
| 1 | | docs / code / issue / reddit / blog | | |
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
-
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
- [ ]
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
Which decisions, tasks or articles came out of this research (link them).
|
||||||
|
|
||||||
|
<!-- status: inbox → in-progress → analyzed → archived · confidence: low → medium → high -->
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
type: sprint
|
||||||
|
number:
|
||||||
|
status: planned
|
||||||
|
started:
|
||||||
|
finished:
|
||||||
|
hours-planned: 20
|
||||||
|
hours-logged: 0
|
||||||
|
tags: [sprint]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sprint {{title}}
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
One line: the single sub-skill + app feature this sprint is about.
|
||||||
|
|
||||||
|
## App deliverable
|
||||||
|
The concrete, demoable thing that must exist at the end of the sprint.
|
||||||
|
|
||||||
|
## Rust focus — the vital 20%
|
||||||
|
Syntax and concepts to practice, each mapped to real app code.
|
||||||
|
|
||||||
|
## Practice drills (20–30 min each)
|
||||||
|
- [ ] Drill 1
|
||||||
|
- [ ] Drill 2
|
||||||
|
- [ ] Drill 3
|
||||||
|
|
||||||
|
## English focus
|
||||||
|
What and how we practice this sprint (README section, commit messages, article…).
|
||||||
|
|
||||||
|
## Session rhythm (45 min)
|
||||||
|
5 min — review yesterday's log and errors → 30 min — focused practice → 10 min — DevLog + ErrorJournal.
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
- [ ] Deliverable works
|
||||||
|
- [ ] `cargo test` green
|
||||||
|
- [ ] clippy clean
|
||||||
|
- [ ] Retro filled in below
|
||||||
|
|
||||||
|
## Retrospective
|
||||||
|
### Shipped
|
||||||
|
### Frustrations (link ErrorJournal entries)
|
||||||
|
### Hours logged / English notes
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
type: task
|
||||||
|
status: todo
|
||||||
|
priority: P2
|
||||||
|
sprint:
|
||||||
|
gitea-issue:
|
||||||
|
created: {{date}}
|
||||||
|
due:
|
||||||
|
tags: [task]
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{title}}
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
One sentence describing what must exist when this task is done.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- [ ] Criterion 1
|
||||||
|
- [ ] Criterion 2
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
Context, links to research notes and decisions, sketches.
|
||||||
|
|
||||||
|
## Log
|
||||||
|
- {{date}} — task created.
|
||||||
|
|
||||||
|
<!-- status: todo | in-progress | blocked | review | done | cancelled · priority: P0 urgent … P3 nice-to-have -->
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
type: article
|
||||||
|
status: published
|
||||||
|
created: 2026-10-17
|
||||||
|
updated: 2026-10-17
|
||||||
|
tags: [article, comparison, file-manager]
|
||||||
|
sources:
|
||||||
|
- "[[research/ranger-deep-dive]]"
|
||||||
|
- "[[research/competitor-landscape]]"
|
||||||
|
- "[[research/rust-preview-stack]]"
|
||||||
|
- "[[research/plugin-architecture]]"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Terminal file managers in 2026: what viexp should learn from them
|
||||||
|
|
||||||
|
> **TL;DR** — Ranger defined the interaction vocabulary; yazi proved a Rust rewrite can be both
|
||||||
|
> faster and more extensible; nnn and lf showed that scripts-as-APIs beat embedded interpreters
|
||||||
|
> for Linux users; joshuto and hunter showed how scope and toolchain choices kill projects.
|
||||||
|
> viexp takes ranger's UX, yazi's architecture patterns, and an extension model that starts
|
||||||
|
> declarative and grows to scripts, then Lua.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Before writing viexp we studied nine terminal file managers (ranger, yazi, nnn, lf, joshuto,
|
||||||
|
felix, hunter, vifm, broot, mc). This article condenses the four research notes above into
|
||||||
|
the product decisions behind viexp. Full data lives in the linked research notes.
|
||||||
|
|
||||||
|
## The three eras
|
||||||
|
|
||||||
|
1. **The blueprint (ranger, 2009–)**. Miller columns, vim keys, `:bulkrename`, rifle opener
|
||||||
|
rules, `--choosefile`. Still the most feature-complete — and still slow for structural
|
||||||
|
reasons: a synchronous Python loop that stats every file inline, `statvfs` on every frame
|
||||||
|
(4–10 s j/k lag on network mounts, issue #1025), ~1 s interpreter startup, and an image
|
||||||
|
preview stack built on an abandoned X11 tool (ueberzug).
|
||||||
|
2. **The reaction (nnn, lf, vifm, 2015–)**. Minimalism and Unix philosophy. nnn fits in
|
||||||
|
150 KiB with zero config and extends through executables; lf keeps the UI responsive with
|
||||||
|
async I/O and pushes previews to an external script; vifm goes the opposite way and makes
|
||||||
|
config a programming language. Each proves a point: speed is a feature; process contracts
|
||||||
|
are a complete extension API; but none ships native graphics, so preview ecosystems
|
||||||
|
fragmented into incompatible script zoos.
|
||||||
|
3. **The synthesis (yazi, broot, 2023–)**. yazi is what happens when you take ranger's UX and
|
||||||
|
rebuild it on tokio + ratatui: paged preloading (only visible rows get work), prioritized
|
||||||
|
worker pools, three cancellation mechanisms, protocol auto-detection (kitty/iterm2/sixel/
|
||||||
|
ueberzug++/chafa), TOML config with `prepend_/append_` merge, and Lua plugins through the
|
||||||
|
same dispatch tables as built-ins. broot rethinks navigation itself around an interruptible,
|
||||||
|
composable tree search.
|
||||||
|
|
||||||
|
## The five decisions viexp takes from this
|
||||||
|
|
||||||
|
| # | Decision | Source | ADR |
|
||||||
|
|---|----------|--------|-----|
|
||||||
|
| 1 | ratatui + crossterm UI, single-threaded actor fed by background workers | yazi, felix | ADR-001 |
|
||||||
|
| 2 | Extension model in three phases: TOML rules → external script contract → Lua | yazi tables, nnn/lf contracts, joshuto's failure | ADR-002 |
|
||||||
|
| 3 | Every file-type module (image/video/audio/pdf/djvu/epub/md) is a registry entry with a runtime `enabled` switch; cargo features only trim binaries | yazi, Cargo book | ADR-003 |
|
||||||
|
| 4 | Image protocol chain: kitty → iterm2 → sixel → ueberzug++ → chafa/halfblocks, detected at runtime | yazi-adapter | ADR-004 |
|
||||||
|
| 5 | Async adopted gradually: sync core first, threads for previews, tokio for the full pipeline | lf (threads suffice) → yazi (tokio) | ADR-005 |
|
||||||
|
|
||||||
|
## What we deliberately do not copy
|
||||||
|
|
||||||
|
- **Full ranger parity in one release** — the joshuto trap (5 config files, half-built command
|
||||||
|
line, 14-month release gap). viexp ships a narrow polished core first.
|
||||||
|
- **Nightly Rust or linked C media frameworks** — the hunter trap. Stable toolchain; media via
|
||||||
|
spawned `ffmpeg`/`ffprobe` processes.
|
||||||
|
- **A bespoke config DSL** — the lf trap. TOML now, Lua later, never a private language.
|
||||||
|
- **GPL-linked crates in the binary** (`epub` crate) — spawned GPL tools (`pdftoppm`, `ddjvu`,
|
||||||
|
ueberzug++) are fine; statically linked GPL is not.
|
||||||
|
|
||||||
|
## Conclusions
|
||||||
|
|
||||||
|
The market gap viexp aims at: ranger's ergonomics with yazi-grade speed, a **script-first
|
||||||
|
extension model** (Lua last, not first), and documentation-driven module replaceability —
|
||||||
|
built as a learning project that teaches Rust and technical English at 20 hours per sprint.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
See the four linked research notes; each ends with its own verified source list.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
type: decision
|
||||||
|
adr: 001
|
||||||
|
status: accepted
|
||||||
|
date: 2026-10-17
|
||||||
|
supersedes:
|
||||||
|
tags: [adr, ratatui, tui]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-001: ratatui + crossterm as the TUI foundation
|
||||||
|
|
||||||
|
## Context
|
||||||
|
viexp needs a terminal UI with a three-pane Miller layout, an event loop, and later image
|
||||||
|
graphics. The Rust TUI ecosystem has one de-facto standard and several niche options.
|
||||||
|
|
||||||
|
## Options considered
|
||||||
|
| Option | Pros | Cons |
|
||||||
|
|--------|------|------|
|
||||||
|
| **ratatui + crossterm** | De-facto standard (yazi, felix, broot); 0.30 modular facade; ratatui-image companion; huge docs/community | Immediate-mode model to learn; subcrate churn in 2026 |
|
||||||
|
| cursive | widget-toolkit feel | slower cadence, different paradigm |
|
||||||
|
| tui-realm | retained mode on ratatui | extra complexity for one maintainer |
|
||||||
|
| tui-rs | — | dead upstream |
|
||||||
|
| hand-rolled on crossterm | total control | reinvents widgets; hunter died with this approach |
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
Use **ratatui 0.30 (facade) with the crossterm backend**. Pin minor versions in Cargo.toml;
|
||||||
|
adopt the ratatui-org `ratatui-image` crate when previews arrive (Sprint 6).
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
- Positive: best-documented path; skills transfer to yazi/helix-adjacent tooling; image widget exists.
|
||||||
|
- Negative: immediate-mode rendering must be learned early (owned by Sprint 2); 0.30 subcrate
|
||||||
|
split may cause dependency churn.
|
||||||
|
- Mitigations: pin versions; single render function per pane; UI state isolated in one struct.
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
type: decision
|
||||||
|
adr: 002
|
||||||
|
status: accepted
|
||||||
|
date: 2026-10-17
|
||||||
|
supersedes:
|
||||||
|
tags: [adr, plugins, architecture]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-002: three-phase extension architecture (TOML → scripts → Lua)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
viexp must let Linux users disable, replace and add file-type behaviors easily. Research
|
||||||
|
([[research/plugin-architecture]]) shows: declarative rules carry configuration, external
|
||||||
|
process contracts carry ~90% of real-world extension, and embedded interpreters are the most
|
||||||
|
expensive layer to stabilize (yazi is still "beta" partly because of it). WASM (zellij) costs
|
||||||
|
host complexity that a single-maintainer learning project cannot afford.
|
||||||
|
|
||||||
|
## Options considered
|
||||||
|
| Option | Pros | Cons |
|
||||||
|
|--------|------|------|
|
||||||
|
| Phased: TOML rules → script contract → Lua (mlua) | each phase shippable; users' language of choice; no unsafe early | three layers to document eventually |
|
||||||
|
| Lua from day 1 | yazi-grade power early | API design + FFI before first working product; high frustration risk |
|
||||||
|
| WASM components (wasmtime/extism) | best sandboxing | WIT churn, users must compile components; zellij-scale effort |
|
||||||
|
| External-only | simplest | no stateful in-process previews/fetchers ever |
|
||||||
|
|
||||||
|
## Decision (confirmed with the project owner, 2026-10-17)
|
||||||
|
1. **v0.1 — declarative registry**: `modules.toml` maps mime/glob → previewer/opener; per-module
|
||||||
|
`enabled` switch; user rules merge via `prepend_*`/`append_*` (yazi semantics). All built-in
|
||||||
|
modules dispatch through the same tables.
|
||||||
|
2. **v0.2 — external process contract (frozen, documented)**: previewer argv = `(path, width,
|
||||||
|
height, x, y)`; env context (mime, selection file, session dir); write-back via FIFO/socket
|
||||||
|
(nnn-style opcodes); `cleaner` hook; documented exit codes + SIGPIPE semantics.
|
||||||
|
Reference plugins in bash and Python ship in-repo.
|
||||||
|
3. **v1.0+ — embedded Lua** via **mlua with vendored Lua**, registered through the same tables;
|
||||||
|
only for what scripts cannot do (stateful/in-process previews). Re-evaluate Luau if sandbox
|
||||||
|
isolation becomes a requirement. **WASM is explicitly rejected for v1.x.**
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
- Positive: shippable increments; extension by Linux-familiar means; Lua postponed until the
|
||||||
|
host API is stable enough to expose.
|
||||||
|
- Negative: three layers of documentation eventually; write-back protocol must be designed
|
||||||
|
carefully once and frozen.
|
||||||
|
- Mitigations: one dispatch model for all layers (yazi lesson); contract docs written as part
|
||||||
|
of the v0.2 sprint DoD, not after.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
type: decision
|
||||||
|
adr: 003
|
||||||
|
status: accepted
|
||||||
|
date: 2026-10-17
|
||||||
|
supersedes:
|
||||||
|
tags: [adr, modules, config]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-003: file-type modules are registry entries, disableable at runtime
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Requirement: images, video, audio, PDF, DJVU, EPUB and Markdown previews work out of the box,
|
||||||
|
but a user can disable or replace any of them without recompiling. Cargo features alone fail
|
||||||
|
this: feature unification breaks packagers, and removing a default feature is a breaking
|
||||||
|
change ([[research/plugin-architecture]]).
|
||||||
|
|
||||||
|
## Options considered
|
||||||
|
| Option | Pros | Cons |
|
||||||
|
|--------|------|------|
|
||||||
|
| Runtime registry + per-module `enabled` flag (+ optional cargo features for binary size) | one-line disable; replaceable by script rule; packager-safe | slightly larger default binary |
|
||||||
|
| Cargo features only | smallest binaries | recompile to disable; unification traps; breaking-change semantics |
|
||||||
|
| Everything external scripts (ranger scope.sh model) | max flexibility | out-of-box experience degrades; 700-line bash nobody dares edit |
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
Every file-type capability is a **module** registered in a TOML registry:
|
||||||
|
`name`, `match` (mime/glob), `previewer` (built-in id or external command), `opener`,
|
||||||
|
`enabled` (default true). Precedence: user rules → built-in rules, with `prepend_/append_`
|
||||||
|
merge. Built-in implementations live behind additive cargo features (default-on: `image`,
|
||||||
|
`pdf`, `media`, …) purely to trim binaries; a feature-disabled module degrades to a TOML rule
|
||||||
|
pointing at an external tool (e.g. `pdftotext`) instead of disappearing.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
- Positive: `enabled = false` in one line; third-party replacement without recompile; matches
|
||||||
|
ADR-002 phase 1 exactly.
|
||||||
|
- Negative: two config concepts (registry + features) need clear documentation.
|
||||||
|
- Mitigations: features documented as "binary size only, not functionality"; docs page
|
||||||
|
"Disabling and replacing modules" is part of the v0.1 docs DoD.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
type: decision
|
||||||
|
adr: 004
|
||||||
|
status: accepted
|
||||||
|
date: 2026-10-17
|
||||||
|
supersedes:
|
||||||
|
tags: [adr, preview, kitty, sixel]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-004: image preview protocol chain (kitty first)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Ranger's biggest UX liability is image preview depending on unmaintained X11-only ueberzug.
|
||||||
|
Research ([[research/rust-preview-stack]]): terminals support one of several incompatible
|
||||||
|
graphics protocols; no single one covers everything. The owner's daily terminal is **kitty
|
||||||
|
without a multiplexer**, but viexp targets all Linux users.
|
||||||
|
|
||||||
|
## Options considered
|
||||||
|
| Option | Pros | Cons |
|
||||||
|
|--------|------|------|
|
||||||
|
| Multi-protocol chain behind one trait, runtime detection (yazi model) | works everywhere; upgrade path | most implementation work |
|
||||||
|
| kitty-only | minimal work for owner's setup | useless for everyone else; vifm/nnn trap |
|
||||||
|
| Always chafa/halfblocks | works everywhere incl. ssh | ugly; not a differentiator |
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
Implement a `GraphicsAdapter` trait with a **priority chain, first match wins**:
|
||||||
|
1. **kitty graphics** (unicode-placeholder mode) — kitty ≥0.28, Ghostty — implemented first
|
||||||
|
2. **iTerm2 inline** images
|
||||||
|
3. **sixel** (via ratatui-image/quantette)
|
||||||
|
4. **ueberzug++ subprocess** (X11 + wlroots Wayland)
|
||||||
|
5. **chafa/halfblocks** — universal floor
|
||||||
|
6. plain text (name, size, mime)
|
||||||
|
|
||||||
|
Detection: `$TERM`/`$TERM_PROGRAM`/`$XDG_SESSION_TYPE` env + terminal capability queries;
|
||||||
|
shortcut via `ratatui-image` `Picker`, with ueberzug++/chafa as our tiers 4–5.
|
||||||
|
Thumbnails: decode once at preview size → disk cache → in-memory LRU → neighbor preloading.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
- Positive: owner gets tier-1 quality from Sprint 6; everyone else degrades gracefully;
|
||||||
|
no dead X11 dependency.
|
||||||
|
- Negative: five adapters is real work; tmux/alacritty edge cases documented, not solved.
|
||||||
|
- Mitigations: chain built incrementally (kitty in Sprint 6, sixel/ueberzug++ in the backlog);
|
||||||
|
adapter chosen once per session, cached.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
type: decision
|
||||||
|
adr: 005
|
||||||
|
status: accepted
|
||||||
|
date: 2026-10-17
|
||||||
|
supersedes:
|
||||||
|
tags: [adr, async, tokio, threads]
|
||||||
|
---
|
||||||
|
|
||||||
|
# ADR-005: adopt async gradually — sync core, then threads, then tokio
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Ranger's fatal flaw is a synchronous loop that stats every file inline ([[research/ranger-deep-dive]]).
|
||||||
|
yazi proves tokio solves it but is a lot of machinery to learn while also learning Rust.
|
||||||
|
lf (Go goroutines) shows threads alone already keep a UI responsive.
|
||||||
|
|
||||||
|
## Options considered
|
||||||
|
| Option | Pros | Cons |
|
||||||
|
|--------|------|------|
|
||||||
|
| Gradual: sync → std threads + channels → tokio | learnable steps; working product at every stage | one refactor of the preview pipeline in Sprint 8 |
|
||||||
|
| tokio from day 1 | final architecture sooner | async lifetimes + borrow checker + TUI at once = frustration barrier |
|
||||||
|
| threads-only forever | simpler mental model | ecosystem (notify bridges, timeouts, process spawning) is tokio-first |
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
- **Sprints 1–5**: synchronous core. The TUI loop owns all state; directory loading and file
|
||||||
|
ops are fast enough synchronously for a first product.
|
||||||
|
- **Sprints 6–7**: previews move to `std::thread` + `std::sync::mpsc` — UI stays responsive,
|
||||||
|
ownership lessons are explicit.
|
||||||
|
- **Sprint 8**: adopt **tokio** for the full preview/media pipeline (yazi architecture:
|
||||||
|
runtime workers marshal results to the single-threaded UI actor via a channel). Introduce
|
||||||
|
cancellation via a generation-counter ("ticket") pattern before adopting tokio `abort`.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
- Positive: each concurrency model is learned in isolation; no big-bang async rewrite of the UI.
|
||||||
|
- Negative: preview pipeline is written twice (thread → task) — accepted as a deliberate
|
||||||
|
learning repetition (Kaufman: quantity of practice beats premature perfection).
|
||||||
|
- Mitigations: keep the preview pipeline behind a small interface (`Previewer` trait, ADR-002)
|
||||||
|
so the swap is local, not global.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
type: devlog
|
||||||
|
date: 2026-10-17
|
||||||
|
sprint: 0
|
||||||
|
minutes: 120
|
||||||
|
tags: [devlog]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dev log — 2026-10-17
|
||||||
|
|
||||||
|
Project bootstrap day: research, vault setup and roadmap.
|
||||||
|
|
||||||
|
## What I did
|
||||||
|
- Defined the product: a ranger-inspired terminal file manager in Rust, extensible by Linux users.
|
||||||
|
- Ran market research across ranger, yazi, nnn, lf, joshuto, vifm, broot and others.
|
||||||
|
- Set up the Obsidian vault: templates in `TMP/`, docs structure, workflows.
|
||||||
|
- Chose the direction: ratatui + crossterm, kitty graphics first, script-based plugins first
|
||||||
|
with Lua later (ADR-001 … ADR-003).
|
||||||
|
- Wrote the 20-hour sprint roadmap.
|
||||||
|
|
||||||
|
## English practice
|
||||||
|
- *bootstrap* — первичная настройка, «раскрутка» проекта; *we bootstrapped the vault*.
|
||||||
|
- *milestone* — контрольная точка, веха; *M1 is the daily-driver milestone*.
|
||||||
|
- *trade-off* — компромисс; *Lua plugins are a trade-off between power and complexity*.
|
||||||
|
|
||||||
|
## Next session
|
||||||
|
- Start Sprint 0: Rust toolchain, cargo project, Gitea board and CI.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# viexp — Knowledge Index
|
||||||
|
|
||||||
|
This folder is the project's Obsidian vault: all long-lived knowledge about viexp lives here,
|
||||||
|
in English. The Obsidian app config (`.obsidian/`) is gitignored; the notes themselves are part
|
||||||
|
of the repository.
|
||||||
|
|
||||||
|
## Entry points
|
||||||
|
|
||||||
|
- [[roadmap]] — the master plan: 20-hour learning sprints (Josh Kaufman, "First 20 Hours")
|
||||||
|
- [[workflows/research-workflow|Research workflow]] — how we collect and analyze research artifacts
|
||||||
|
- [[workflows/gitea-workflow|Gitea workflow]] — how the board, labels, milestones and issues are organized
|
||||||
|
- [[workflows/english-workflow|English workflow]] — how the project doubles as English practice
|
||||||
|
|
||||||
|
## Folders
|
||||||
|
|
||||||
|
| Folder | Contents | Template |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| `research/` | Raw research notes: one question per note | `TMP/Research.md` |
|
||||||
|
| `articles/` | Polished syntheses, comparisons, guides | `TMP/Article.md` |
|
||||||
|
| `decisions/` | Architecture Decision Records (ADRs) | `TMP/Decision.md` |
|
||||||
|
| `sprints/` | One plan+retro note per 20-hour sprint | `TMP/Sprint.md` |
|
||||||
|
| `tasks/` | Task notes (mirrored as Gitea issues) | `TMP/Task.md` |
|
||||||
|
| `devlog/` | Daily practice journal | `TMP/DevLog.md` |
|
||||||
|
| `learning/` | Glossary, error journal, cheat sheets | `TMP/ErrorJournal.md`, `TMP/Glossary.md` |
|
||||||
|
| `attachments/` | Images and other research artifacts | — |
|
||||||
|
| `TMP/` | Obsidian templates (vault root) | — |
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Note titles in `kebab-case` English; frontmatter `type`/`status` is required (used for filtering).
|
||||||
|
- Headings are stable across notes of one type, so Obsidian folding always works the same way.
|
||||||
|
- Every decision must trace back to at least one `research/` note; every task to a decision or the roadmap.
|
||||||
|
- New templates live in `TMP/` (Obsidian: Settings → Templates → folder = `TMP`).
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
type: glossary
|
||||||
|
created: 2026-10-17
|
||||||
|
tags: [glossary, english]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Glossary
|
||||||
|
|
||||||
|
Terms met while working on viexp. Reuse each term in your own sentence before moving it to
|
||||||
|
the "active" mental column.
|
||||||
|
|
||||||
|
| Term / phrase | Translation | Where I met it | My example sentence |
|
||||||
|
| ------------- | ----------- | -------------- | ------------------- |
|
||||||
|
| bootstrap | первичная настройка | devlog 2026-10-17 | We bootstrapped the repo before writing code. |
|
||||||
|
| milestone | веха, контрольная точка | roadmap | Sprint 3 ends at the first daily-driver milestone. |
|
||||||
|
| trade-off | компромисс | research notes | Every plugin system is a trade-off. |
|
||||||
|
| dogfooding | использование своего продукта ежедневно | fm research | We start dogfooding after Sprint 3. |
|
||||||
|
| out of the box | «из коробки», без настройки | README | Image preview works out of the box. |
|
||||||
|
| fallback | запасной вариант | preview research | chafa is the fallback when graphics protocols fail. |
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
type: research
|
||||||
|
status: analyzed
|
||||||
|
confidence: high
|
||||||
|
created: 2026-10-17
|
||||||
|
question: Which terminal file managers exist in 2026, what does each do best, and which ideas should viexp adopt or avoid?
|
||||||
|
tags: [research, file-manager, yazi, nnn, lf, vifm, broot]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Competitor landscape (October 2026)
|
||||||
|
|
||||||
|
## Feature matrix
|
||||||
|
|
||||||
|
| Manager | Lang | TUI | Image preview | Extensions | Config | Async | State (last release) |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| **yazi** | Rust | ratatui | native kitty/iterm2/sixel/ueberzug++/chafa | Lua (mlua, vendored) + `ya pack` | TOML ×3 | tokio, fully | Very active, v26.9.1 (2026-09) |
|
||||||
|
| **nnn** | C | ncurses | via FIFO plugins | any executable; `NNN_PIPE`/`NNN_FIFO` protocol | none (flags+env) | no (tiny by design) | Active, v5.3 (2026-08) |
|
||||||
|
| **lf** | Go | own | sixel native; else scripts | shell cmds, `push`, remote socket API | lfrc DSL | async I/O | Active, r42 (2026-07) |
|
||||||
|
| **joshuto** | Rust | tui-rs/termion | external scripts | config only | 5 TOMLs | partial | Semi-stalled, v0.9.9 (2025-05) |
|
||||||
|
| **felix** | Rust | termion | chafa + bat | none | YAML | partial | Dormant since 2025-04 |
|
||||||
|
| **hunter** | Rust | own | halfblock/sixel/kitty | mime actions | conf+keymap | heavily threaded | Dead (2020/2022) |
|
||||||
|
| **vifm** | C | ncurses | community scripts only | vim-like `:commands` + Lua | vifmrc | no | Active, v0.14.4 (2026-05) |
|
||||||
|
| **broot** | Rust | crossterm+termimad | kitty graphics | verbs, skins | TOML | interruptible search | Very active, v1.61 (2026-10) |
|
||||||
|
| **mc** | C | own | none | extfs VFS helpers | menus | background ops | Active, 4.8.33 |
|
||||||
|
|
||||||
|
## What the winners do right
|
||||||
|
|
||||||
|
- **yazi** (~43k stars): ~25-crate workspace, everything on tokio; micro/macro worker pools with
|
||||||
|
priorities (mime/previews preempt copying); **paged preloading** — only visible rows get work;
|
||||||
|
three cancellation mechanisms (tokio abort, atomic tickets for CPU work, instruction-count
|
||||||
|
checks for Lua); two-pass image pipeline (downscale+cache → fit to terminal); DDS pub/sub
|
||||||
|
between instances without a daemon. v25.4 doubled startup speed via config parser rewrite.
|
||||||
|
- **nnn**: 150 KiB binary, zero config; plugins = executables with env-var contract
|
||||||
|
(`$1` hovered file, `$2` cwd, `$3` picker) and a tiny write-back protocol via `NNN_PIPE`;
|
||||||
|
`NNN_FIFO` streams hovered paths so external scripts render live previews.
|
||||||
|
- **lf**: external `previewer(file, w, h, x, y)` + `cleaner` contract with exit-code-controlled
|
||||||
|
caching; `push` simulates keystrokes making mappings composable; server model over Unix socket.
|
||||||
|
- **broot**: every keystroke interrupts the running search; fuzzy+regex+content combined with
|
||||||
|
boolean operators; collapsed tree respecting .gitignore.
|
||||||
|
- **felix**: built-in trash + undo/redo + zoxide `z` jump — small features with huge daily value.
|
||||||
|
|
||||||
|
## Ideas worth stealing (ranked)
|
||||||
|
|
||||||
|
1. yazi's paged task scheduling + prioritized workers + cancellation tickets (perceived speed)
|
||||||
|
2. Protocol auto-detection adapter layer behind one trait (kitty/iterm2/sixel/ueberzug++/chafa)
|
||||||
|
3. lf's previewer/cleaner argv contract — cheapest path to infinite formats
|
||||||
|
4. nnn's FIFO hover event stream for external live previews
|
||||||
|
5. yazi's TOML tables with `prepend_*`/`append_*` merge semantics for rules
|
||||||
|
6. TOML for config + (later) Lua for behavior + pinned plugin packages
|
||||||
|
7. broot's interruptible, composable search
|
||||||
|
8. lf's `push` key-simulation primitive
|
||||||
|
9. felix's trash + undo + `z` jump
|
||||||
|
10. Performance as a marketable feature (both nnn and yazi publish benchmarks)
|
||||||
|
|
||||||
|
## Anti-patterns to avoid
|
||||||
|
|
||||||
|
| Trap | Victim | Lesson |
|
||||||
|
|------|--------|--------|
|
||||||
|
| Full ranger parity alone | joshuto | 5 config files, janky command line, 14-month release gap → audience left for yazi. Ship narrow and polished. |
|
||||||
|
| Nightly compiler + heavy C deps | hunter | GStreamer + nightly = unbuildable for users. Stable Rust, spawned binaries for media. |
|
||||||
|
| No native graphics path | vifm, nnn, mc | Preview zoo of mutually incompatible scripts. Build the adapter layer early. |
|
||||||
|
| Bespoke config DSL | lf | A whole parser users must learn; standard TOML + scripting runtime beats it. |
|
||||||
|
| Doctrinal non-features | lf (no tabs) | Minimal defaults and power features are not contradictory (yazi proves it). |
|
||||||
|
| Monolith | mc | Thin core + clean contracts. |
|
||||||
|
| API churn without pinning | yazi (mild) | Plugin version pinning story from day one. |
|
||||||
|
| Silence without governance | felix, hunter | Release cadence = perceived life. |
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
Shaped ADR-001 (ratatui), ADR-002 (extension phases), ADR-004 (protocol chain), and the
|
||||||
|
roadmap order (core narrow first, previews second, plugins third, Lua last).
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
- yazi: github.com/sxyazi/yazi, https://yazi-rs.github.io/blog/why-is-yazi-fast/, releases v25–v26
|
||||||
|
- nnn: github.com/jarun/nnn + plugins/README.md
|
||||||
|
- lf: github.com/gokcehan/lf + doc.md
|
||||||
|
- joshuto, felix, hunter, vifm, broot, mc: repos + release APIs (dates verified 2026-10-17)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
type: research
|
||||||
|
status: analyzed
|
||||||
|
confidence: high
|
||||||
|
created: 2026-10-17
|
||||||
|
question: How should viexp's file-type modules and plugins be designed so Linux users can disable, replace and extend them easily?
|
||||||
|
tags: [research, plugins, architecture, lua, wasm]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Extension architecture research
|
||||||
|
|
||||||
|
## Mechanism comparison
|
||||||
|
|
||||||
|
| Mechanism | Safety | Ease for users | Host complexity | Hot reload | Proof |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| External process contract (argv/env/stdout) | low (= shell) | **highest** — any language | very low | free | nnn plugins, lf previewer, ranger scope.sh |
|
||||||
|
| Declarative rules (TOML / rifle / mailcap) | high (data, not code) | high — one-liners | low | free | rifle.conf, yazi `[plugin]` tables |
|
||||||
|
| In-process Lua (mlua) | medium — sandboxes leak (2025 CVE); Luau is the hardened dialect | medium | medium-high | yes | yazi (mlua 0.12, vendored Lua 5.5), wezterm, neovim |
|
||||||
|
| Rhai | high | low (niche language) | low-medium | yes | niche |
|
||||||
|
| JS (deno_core/V8) | medium-high | medium | high (~30 MB) | yes | Deno |
|
||||||
|
| WASM (wasmtime/extism) | **highest** | lowest (compile a component) | high (WIT churn) | yes | zellij |
|
||||||
|
| Host-language plugins (Python) | lowest (monkeypatching) | high for pythonistas | medium, fragile | poor | ranger |
|
||||||
|
|
||||||
|
## Case-study lessons
|
||||||
|
|
||||||
|
- **ranger**: the script pipeline (scope.sh + rifle) carried ~90% of real customization; the
|
||||||
|
Python layer exists because previews/commands need *state*. Three disjoint mechanisms = confusion.
|
||||||
|
- **nnn**: cleanest "process as API": `$1`=file, `$2`=cwd, `$3`=picker; context in env
|
||||||
|
(`NNN_FIFO` hover stream); control back via `NNN_PIPE` with tiny opcodes (`1c/etc` = cd ctx 1).
|
||||||
|
- **lf**: minimum viable preview contract = `(path, w, h, x, y)` + `cleaner` hook + exit-code
|
||||||
|
caching semantics. Document SIGPIPE/exit codes or users suffer.
|
||||||
|
- **yazi**: built-ins are preset plugins routed through the same `[plugin]` TOML tables users
|
||||||
|
edit (`prepend_*`/`append_*` merge) — one dispatch model for built-in and user code. mlua
|
||||||
|
with vendored Lua so users install nothing. Still BETA → in-process scripting is the most
|
||||||
|
expensive layer to stabilize.
|
||||||
|
- **neovim**: Lua won because the host API is rich, stable and uniform — half-exposed APIs are
|
||||||
|
worse than external scripts.
|
||||||
|
- **zellij**: WASM gives real sandboxing but locks architecture (single-threaded synchronous
|
||||||
|
plugins; WASIp2 migration "a significant undertaking"). Not worth it for v1.
|
||||||
|
- **kakoune**: `disabled_hooks` regex = cheap runtime kill-switch worth copying.
|
||||||
|
|
||||||
|
## Cargo feature flags = compile-time layer, not the primary switch
|
||||||
|
|
||||||
|
Features (`pdf`, `video`, … default-on) trim binary size and C deps, but feature unification
|
||||||
|
breaks packagers, combinations explode, and removing a default feature is a breaking change.
|
||||||
|
**Runtime `enabled = false` in modules config must remain the real disable mechanism**; every
|
||||||
|
`#[cfg]`-gated module should degrade to a TOML rule pointing at an external tool.
|
||||||
|
|
||||||
|
## Recommended phasing (adopted)
|
||||||
|
|
||||||
|
- **v0.1 — declarative core**: every file-type module = rule in a TOML registry mapping
|
||||||
|
mime/glob → previewer/opener, with per-module `enabled` switch and `prepend_/append_` merge.
|
||||||
|
Built-ins already dispatch through this table → replacing/reordering needs no recompile.
|
||||||
|
- **v0.2 — external process protocol**: freeze the script contract —
|
||||||
|
`argv = (path, width, height, x, y)`, env context, FIFO/socket write-back, `cleaner` hook.
|
||||||
|
Ship bash + Python reference plugins. This is where 90% of Linux-user extensibility lives.
|
||||||
|
- **v1.0+ — embedded Lua** (mlua, vendored): only for what scripts cannot do — stateful,
|
||||||
|
in-process previews/fetchers — registered through the same tables. Evaluate Luau if
|
||||||
|
sandboxing becomes a requirement. WASM explicitly rejected for v1.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
Adopted as [[decisions/ADR-002-extension-architecture|ADR-002]] and [[decisions/ADR-003-file-type-modules|ADR-003]].
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
- yazi docs (plugins overview, configuration), yazi Cargo.tomls (mlua 0.12 lua55 vendored)
|
||||||
|
- nnn plugins README; lf doc.md; rifle(1); RFC 1524 (mailcap)
|
||||||
|
- zellij plugin docs + discussion #4139; wezterm config docs; neovim lua.txt; kakoune FAQ
|
||||||
|
- mlua/rhai/gluon repos; deno blog; Lua sandbox-bypass CVE coverage (2025)
|
||||||
|
- Cargo book: features.md
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
type: research
|
||||||
|
status: analyzed
|
||||||
|
confidence: high
|
||||||
|
created: 2026-10-17
|
||||||
|
question: What exactly makes ranger great, what makes it slow and fragile, and what must a Rust successor keep or fix?
|
||||||
|
tags: [research, ranger, file-manager]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Ranger deep dive
|
||||||
|
|
||||||
|
## Research question
|
||||||
|
Ranger is the most feature-complete terminal file manager but famously slow in specific
|
||||||
|
scenarios. We need a precise keep/fix list before writing a single line of viexp.
|
||||||
|
|
||||||
|
## Findings
|
||||||
|
|
||||||
|
### Project state (Oct 2026)
|
||||||
|
Python + curses, Miller-columns UI. 17.4k stars, ~900 open issues, last tag v1.9.4 (Nov 2024),
|
||||||
|
commits still trickling. Maintainers openly state they lack manpower (issue #1717).
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- Config = 4 files with **different merge semantics**: `rc.conf` merges, `rifle.conf` replaces,
|
||||||
|
`scope.sh` replaces (~700-line bash), `commands.py` merges (Python subclassing).
|
||||||
|
- Preview pipeline = external `scope.sh`; communicates via exit codes:
|
||||||
|
`0` = stdout preview, `1` = none, `2` = fixed-height text, `5` = image written to cache dir.
|
||||||
|
- Plugins = Python monkeypatching with full interpreter access ("Plugins have full access to
|
||||||
|
your computer!"). Ecosystem: ranger-devicons, ranger-zoxide, ranger-fzf-filter, etc.
|
||||||
|
- Image protocols: w3m, ueberzug, urxvt, kitty, sixel, iterm2, terminology.
|
||||||
|
|
||||||
|
### Why it is slow (root causes)
|
||||||
|
| Cause | Evidence |
|
||||||
|
|-------|----------|
|
||||||
|
| Synchronous UI loop — directory listing + `stat` of every file run inline | issue #1173: 100k files = multi-second uninterruptible hang (open since 2018) |
|
||||||
|
| No async I/O anywhere; threading refactor stalled | issue #1025 literally labeled `wants-async` |
|
||||||
|
| `statvfs` on **every status-bar refresh** → 4–10 s j/k lag on sshfs/NFS/CryFS/s3fs | issue #1025; vifm stays fast in identical setups |
|
||||||
|
| Python interpreter startup ~1 s cold | issue #1717 |
|
||||||
|
| Every preview spawns scope.sh + a chain of external binaries | architecture |
|
||||||
|
|
||||||
|
### Pain points (user-verified)
|
||||||
|
1. ueberzug unmaintained and X11-only; ueberzugpp fixes exist but are bolted on
|
||||||
|
(ranger #3188, #3158, #2995; ueberzugpp #122 fractional-scaling bug on Sway/Hyprland).
|
||||||
|
2. Network mounts unusable without manual `setlocal` hacks or `F` (freeze_files).
|
||||||
|
3. `:flat` broken past ~30k files (#379).
|
||||||
|
4. No built-in trash — ArchWiki documents `map DD shell gio trash` as the workaround.
|
||||||
|
5. Config fragmentation: four files, four formats, four merge behaviors.
|
||||||
|
|
||||||
|
### What users love (must keep)
|
||||||
|
Vim keys, Miller columns, `:bulkrename` via `$EDITOR` (the killer feature), rifle-style
|
||||||
|
ordered opener menu (`r`), preview breadth (20+ types), tabs/bookmarks/marks, `:filter`,
|
||||||
|
`--choosefile/--choosedir` + cd-on-exit, per-path options (`setlocal`).
|
||||||
|
|
||||||
|
## Keep / Fix table
|
||||||
|
|
||||||
|
| Keep | Fix |
|
||||||
|
|------|-----|
|
||||||
|
| Miller 3-pane layout, vim key vocabulary | Native binary startup (no interpreter) |
|
||||||
|
| `:bulkrename` via `$EDITOR` | Async dir loading / stat / preview — UI never blocks |
|
||||||
|
| Rifle-style opener rules + chooser menu | No `statvfs` per frame; poll with timeout+backoff |
|
||||||
|
| Exit-code preview contract (scope.sh idea) | First-class trash (`gio trash`-compatible) + undo |
|
||||||
|
| Preview breadth with caching | Native kitty/sixel/ueberzug++ graphics, Wayland-safe |
|
||||||
|
| `setlocal` per-path options | One config format (TOML) with documented merge semantics |
|
||||||
|
| `--choosefile/--choosedir`, cd-on-exit | Virtual scrolling for 100k+ entry dirs |
|
||||||
|
| `F` freeze escape hatch | Automatic freeze on network/fuse mounts |
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
Direct input to [[decisions/ADR-002-extension-architecture|ADR-002]] (extension model),
|
||||||
|
ADR-004 (preview protocols) and the roadmap's async requirements (Sprint 8).
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
- https://github.com/ranger/ranger — README, wiki, examples/plugins
|
||||||
|
- https://wiki.archlinux.org/title/Ranger
|
||||||
|
- Issues: #1717 (startup), #1173 (100k files), #1025 (network lag), #379 (`:flat`),
|
||||||
|
#3188 / #3158 / #2995 (ueberzug family)
|
||||||
|
- https://github.com/seebye/ueberzug (unmaintained notice), https://github.com/jstkdng/ueberzugpp
|
||||||
|
- Community: r/commandline "Is yazi overhyped?", r/linux FM threads, x-cmd six-FM comparison,
|
||||||
|
https://mqdir.com/blog/file-management/nnn-vs-yazi
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
type: research
|
||||||
|
status: analyzed
|
||||||
|
confidence: high
|
||||||
|
created: 2026-10-17
|
||||||
|
question: Which Rust crates should render previews (images, PDF, DJVU, EPUB, markdown, media) in a terminal, with what licenses and fallbacks?
|
||||||
|
tags: [research, rust, crates, preview, ratatui]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Rust preview stack (October 2026)
|
||||||
|
|
||||||
|
Versions verified against crates.io metadata on 2026-10-17.
|
||||||
|
|
||||||
|
## Recommended stack
|
||||||
|
|
||||||
|
| Concern | Crate | Why | Notes / license |
|
||||||
|
|---|---|---|---|
|
||||||
|
| TUI | **ratatui 0.30** + **crossterm 0.29** | De-facto standard; 0.30 split into `ratatui-core`/`-widgets` facade; yazi & felix use it | MIT; MSRV 1.88 |
|
||||||
|
| Images in TUI | **ratatui-image 11.1** | kitty, sixel, iTerm2, halfblocks + `Picker` protocol/font detection; lives in the ratatui org | MIT |
|
||||||
|
| Image decode | **image 0.25** | yazi's choice; jpeg/png/gif/webp/bmp/tiff (+avif feature) | MIT/Apache |
|
||||||
|
| Resize | **fast_image_resize 6.1** (SIMD) or `image::thumbnail` (triangle filter, yazi-style) | hot loop | MIT/Apache |
|
||||||
|
| Sixel encode | **quantette 0.6** (+ moxcms, palette) | yazi's actual sixel pipeline | MIT |
|
||||||
|
| PDF | **pdfium-render 0.9** + pdfium binary | only mature page→image in-process option | pdfium is **Apache-2.0** (b redistribute, ~5 MB); spawn `pdftoppm` as fallback |
|
||||||
|
| DJVU | **djvu-rs 0.43** (pure Rust, render→image) | first usable pure-Rust DjVu decoder | **6 months old** — keep `ddjvu` fallback |
|
||||||
|
| EPUB | hand-rolled: **zip** + quick-xml/html-to-text | the `epub` crate is **GPL-3.0 — viral** if statically linked | avoid the trap |
|
||||||
|
| Markdown | **comrak 0.56** → styled ratatui lines, or **termimad** (ready-made renderer) | termimad = fastest integration (broot uses it) | MIT |
|
||||||
|
| Syntax highlight | **syntect 5.3** | yazi's choice; regex Sublime syntaxes, no C deps | tree-sitter = heavier, per-language grammars |
|
||||||
|
| Media metadata | **spawn `ffprobe -print_format json`** | no linking pain; what yazi does | ffmpeg-next ties you to system FFmpeg — avoid |
|
||||||
|
| Video thumbnail | **spawn `ffmpeg -ss … -vframes 1 -vf scale…`** (+ `attached_pic` cover-art fallback) | yazi's proven recipe | — |
|
||||||
|
| Audio (optional in-process) | **symphonia 0.6** | pure-Rust metadata/album art | MPL-2.0 |
|
||||||
|
| MIME | **infer 0.22** (magic bytes) + **xdg-mime 0.4** (extension DB) | fast sniffing + correct `text/*` | `new_mime_guess` unmaintained — avoid |
|
||||||
|
| FS events | **notify 8.2** | yazi's watcher | CC0 |
|
||||||
|
| Fuzzy | **nucleo 0.5** | Helix's matcher, library-first | MPL-2.0 |
|
||||||
|
| Config | **serde + toml 1.1** | yazi's set | solid |
|
||||||
|
| Errors/logs | **anyhow + thiserror 2.x + tracing** | boring and correct | — |
|
||||||
|
| Async | **tokio 1.x** with results marshalled to a single-threaded UI actor | yazi's exact architecture; ecosystem is tokio-first | start threads-only, adopt tokio in Sprint 8 |
|
||||||
|
|
||||||
|
## Image protocol fallback chain (first match wins)
|
||||||
|
|
||||||
|
1. **kitty graphics** (unicode-placeholder mode) — kitty ≥0.28, Ghostty ← *our primary target*
|
||||||
|
2. **iTerm2 inline** — WezTerm, iTerm2, Warp, VSCode terminal
|
||||||
|
3. **sixel** — foot, Konsole 22.04+, Windows Terminal ≥1.22, WezTerm, rio
|
||||||
|
4. **ueberzug++ subprocess** — X11 + Wayland (wlroots compositors: sway/hyprland/niri/wayfire);
|
||||||
|
the only option for alacritty; not GNOME/KDE compositors
|
||||||
|
5. **chafa / halfblocks** — works everywhere incl. SSH and tmux
|
||||||
|
6. text: filename + size + mime
|
||||||
|
|
||||||
|
Terminal facts (Oct 2026): kitty has **no sixel by design**; alacritty rejected both protocols;
|
||||||
|
tmux needs `allow-passthrough on` and never passes kitty-old. The daily terminal here is
|
||||||
|
**kitty without a multiplexer** → tier 1 covers the primary user; tiers 3–5 cover everyone else.
|
||||||
|
|
||||||
|
## Thumbnail caching (yazi-proven)
|
||||||
|
|
||||||
|
Decode once at preview size (max ~600×900, triangle filter) → persist rendered pages/frames to
|
||||||
|
a disk cache dir → keep decoded previews in an in-process LRU → preload neighbors in background.
|
||||||
|
Re-encoding for a protocol is cheap; re-decoding is not.
|
||||||
|
|
||||||
|
## Risk notes
|
||||||
|
|
||||||
|
- GPL traps: `epub` crate (static link), ueberzug++ (fine — spawned), pdftoppm (fine — spawned).
|
||||||
|
- pdfium must be shipped as a binary (~5 MB/platform) or static-linked via pdfium-render `static`.
|
||||||
|
- djvu-rs is young; config-selectable `ddjvu` fallback required.
|
||||||
|
- ratatui 0.30 subcrate split may cause ecosystem churn in 2026 — pin carefully.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
Feeds ADR-001, ADR-004 and Sprints 5–8 crate choices.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
- https://github.com/sxyazi/yazi (adapter/Cargo.tomls, preset plugins pdf.lua/video.lua)
|
||||||
|
- https://yazi-rs.github.io/docs/image-preview, /blog/why-is-yazi-fast/
|
||||||
|
- crates.io version metadata (ratatui, ratatui-image, image, fast_image_resize, quantette,
|
||||||
|
pdfium-render, djvu-rs, epub, comrak, termimad, syntect, symphonia, notify, nucleo, infer, xdg-mime)
|
||||||
|
- https://github.com/ajrcarey/pdfium-render; pdfium LICENSE (Apache-2.0)
|
||||||
|
- https://www.arewesixelyet.com; https://sw.kovidgoyal.net/kitty/graphics-protocol
|
||||||
|
- https://github.com/jstkdng/ueberzugpp; https://github.com/matyushkin/djvu-rs
|
||||||
+196
@@ -0,0 +1,196 @@
|
|||||||
|
# viexp Roadmap — Rust + English in 20-hour sprints
|
||||||
|
|
||||||
|
Method: Josh Kaufman, **"The First 20 Hours"** — rapid skill acquisition. The project is split
|
||||||
|
into isolated 20-hour sprints (~45 min/day for a month each). Every sprint: one Rust sub-skill,
|
||||||
|
one working app increment, one English artifact. The goal of each sprint is to cross the
|
||||||
|
frustration barrier and ship something that runs — not perfect code.
|
||||||
|
|
||||||
|
**Kaufman's rules, adapted**
|
||||||
|
|
||||||
|
| Kaufman principle | How this roadmap applies it |
|
||||||
|
|---|---|
|
||||||
|
| Deconstruct the skill | The table below: each sprint = one sub-skill + one app increment |
|
||||||
|
| Learn just enough to self-correct | Per-sprint "Rust focus — the vital 20%": the minimum theory to *read your own compiler errors*. Every error goes to the ErrorJournal with a self-correction cue |
|
||||||
|
| Remove barriers to practice | Sprint 0 sets up the whole environment: toolchain, linters, fast feedback (`bacon`), Gitea board, CI. Never debug your setup during practice time |
|
||||||
|
| Fast feedback loops | `cargo check`/bacon on save, unit tests per session, immediate AI code review of each session's diff (commit → review → fix) |
|
||||||
|
| Quantity over quality | Many small commits, many small drills. Working ugly code beats beautiful unfinished code. No over-engineering before v0.1 |
|
||||||
|
|
||||||
|
## Skill deconstruction
|
||||||
|
|
||||||
|
**Rust sub-skills** (learned in order): cargo & project layout → types & control flow →
|
||||||
|
ownership/borrowing → structs, enums, pattern matching → `Option`/`Result` & `?` →
|
||||||
|
modules & crates → traits & generics → iterators → `Path`/`fs` & error handling (`thiserror`/`anyhow`) →
|
||||||
|
serde/TOML → lifetimes → trait objects & dynamic dispatch → threads & channels →
|
||||||
|
async/await & tokio → process spawning → caching & performance.
|
||||||
|
|
||||||
|
**App increments** (each demoable): `vls` CLI → 3-pane TUI → navigation & file ops →
|
||||||
|
config & openers → text previews → image previews → document previews → media & async →
|
||||||
|
script plugins → power features → performance → release.
|
||||||
|
|
||||||
|
**English track**: every commit message, every issue, all docs — English only. One written
|
||||||
|
artifact per sprint. Daily DevLog (3–5 sentences). See [[workflows/english-workflow|English workflow]].
|
||||||
|
|
||||||
|
## Session rhythm (45 min, every day)
|
||||||
|
|
||||||
|
1. **5 min** — read yesterday's DevLog + ErrorJournal; `git pull`, `bacon` starts.
|
||||||
|
2. **30 min** — today's drill or feature slice (one drill = one commit).
|
||||||
|
3. **10 min** — get the diff reviewed, fix what the reviewer flags, write the DevLog entry.
|
||||||
|
|
||||||
|
**Frustration rules**: stuck >20 min on one error → ask for a review instead of grinding;
|
||||||
|
never end a session with broken code (revert to green); a slow day is a reading day
|
||||||
|
(Rust Book chapter in English) — log it, no guilt.
|
||||||
|
|
||||||
|
## Sprints
|
||||||
|
|
||||||
|
Legend: 🎯 milestone. Each sprint gets a `docs/sprints/sprint-NN-*.md` note (Sprint template)
|
||||||
|
and a Gitea milestone with issues created from its Definition of Done.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Sprint 0 — Setup & deconstruction (≈6 h, short)
|
||||||
|
- **App deliverable**: empty `viexp` cargo project that compiles, `cargo test` runs one trivial
|
||||||
|
test, CI (Gitea Actions) is green, `bacon` + `clippy` + `rustfmt` wired, Gitea board ready.
|
||||||
|
- **Rust focus**: toolchain mental model — what cargo, rustc, crates.io, `Cargo.toml` are;
|
||||||
|
read your first compiler error on purpose (break a program, fix it).
|
||||||
|
- **English**: README "Status" section + the vault (this roadmap counts).
|
||||||
|
- **DoD**: `cargo bacon` shows check+test on save; first commit pushed; Sprint-1 issues exist.
|
||||||
|
|
||||||
|
### Sprint 1 — Rust core via `vls` (20 h)
|
||||||
|
- **App deliverable**: `viexp ls` subcommand — a better `ls`: colored names by type,
|
||||||
|
human-readable sizes, `--sort name|size|mtime`, `--hidden`, `--long`; ≥10 unit tests.
|
||||||
|
- **Rust focus (vital 20%)**: variables & types, functions, `if`/`match`, loops, `Vec`/`String`
|
||||||
|
vs `&str`, structs & enums, `Option`/`Result` + `?`, modules, `#[cfg(test)]`.
|
||||||
|
Rust Book ch. 1–9 (skim 7/8), rustlings `intro→enums`.
|
||||||
|
- **English**: README usage section; article "Why build a file manager in Rust" (draft).
|
||||||
|
- **DoD**: `vls ~/` output beats `ls` for your eyes; clippy clean; tests green.
|
||||||
|
|
||||||
|
### Sprint 2 — First TUI (20 h) — the borrow-checker sprint
|
||||||
|
- **App deliverable**: three-pane Miller layout (parent / current / placeholder preview),
|
||||||
|
`j/k/gg/G` cursor movement, `:q` quits, resize-safe redraw, no flicker.
|
||||||
|
- **Rust focus**: **ownership & borrowing in anger** (the state struct owns everything),
|
||||||
|
references vs clones, lifetimes as they appear in ratatui calls, the event-loop pattern
|
||||||
|
(poll event → mutate state → redraw). Rust Book ch. 4, 10.3 (skim), ratatui official docs.
|
||||||
|
- **English**: keymap reference table in docs (precise imperative phrasing).
|
||||||
|
- **DoD**: navigate a 10k-file directory smoothly; panic-free for 15 min of use.
|
||||||
|
|
||||||
|
### Sprint 3 — Navigation & file ops (20 h) 🎯 **M1: personal daily driver**
|
||||||
|
- **App deliverable**: enter/leave directories, hidden toggle, sort cycling, `Space`/`V`
|
||||||
|
selection, `yy`/`dd`/`pp` copy/cut/paste, delete → **first-class trash** (freedescompliant)
|
||||||
|
with undo, `:mkdir :touch :rename`, status bar (path, size, free space — **not** per-frame
|
||||||
|
`statvfs`; 1 Hz poll).
|
||||||
|
- **Rust focus**: `Path`/`PathBuf`, `fs::read_dir`/`metadata`, real error handling
|
||||||
|
(`thiserror` for lib-style internals, `anyhow` at main), iterators (`map/filter/sort_by`
|
||||||
|
chains), `Display` impls.
|
||||||
|
- **English**: article "Week one of dogfooding my own file manager".
|
||||||
|
- **DoD**: use viexp instead of ranger for real work for 3 days; log every pain point as an issue.
|
||||||
|
|
||||||
|
### Sprint 4 — Config & openers (20 h)
|
||||||
|
- **App deliverable**: `viexp.toml` (defaults, theme colors, sort) + `modules.toml` registry
|
||||||
|
v0 (ADR-003): mime/glob → opener, per-module `enabled`, user rules prepend/append;
|
||||||
|
opener engine (infer + xdg-mime → rule table → `xdg-open` fallback), `r` "open with" menu,
|
||||||
|
`:open_with`.
|
||||||
|
- **Rust focus**: serde derive on enums/structs, `Default` + merge semantics, `HashMap`/
|
||||||
|
`BTreeMap`, first **trait design** (`Opener`), builder-ish config loading with helpful errors.
|
||||||
|
- **English**: configuration guide draft (reference-style writing).
|
||||||
|
- **DoD**: any file opens with your preferred app from config alone; broken TOML produces a
|
||||||
|
friendly error, not a panic.
|
||||||
|
|
||||||
|
### Sprint 5 — Text previews & bulk rename (20 h)
|
||||||
|
- **App deliverable**: preview pane v1 — syntax-highlighted text (syntect) with line numbers
|
||||||
|
and scrolling, directory preview, file metadata panel, zip/tar listing, `:bulkrename` via
|
||||||
|
`$EDITOR` (ranger's killer feature), `n/N` plain search, `:` command history.
|
||||||
|
- **Rust focus**: lifetimes in real signatures, **trait objects** (`Box<dyn Previewer>` registry),
|
||||||
|
advanced iterators, caching keyed by `(path, mtime)`.
|
||||||
|
- **English**: article "How a TUI paints text" (explaining rendering → strongest writing practice).
|
||||||
|
- **DoD**: preview parity with ranger for text/code/archive files.
|
||||||
|
|
||||||
|
### Sprint 6 — Image previews (20 h) 🎯 **M2a**
|
||||||
|
- **App deliverable**: image module through the registry; **kitty graphics adapter first**
|
||||||
|
(owner's terminal), chafa/halfblocks floor; thumbnail disk cache + LRU; runtime
|
||||||
|
disable/replace via `modules.toml` proven on images.
|
||||||
|
- **Rust focus**: the `image` crate, decode/resize pipeline, `#[cfg(feature)]` modules,
|
||||||
|
pixel data, spawning helper processes safely.
|
||||||
|
- **English**: article "How terminals display images" (kitty protocol, sixel, ueberzug++ —
|
||||||
|
your best future blog post; research notes already hold the material).
|
||||||
|
- **DoD**: PNG/JPEG/WebP/GIF preview instantly in kitty; `enabled=false` kills it in one line.
|
||||||
|
|
||||||
|
### Sprint 7 — Document previews (20 h) 🎯 **M2b: full ranger replacement**
|
||||||
|
- **App deliverable**: PDF (pdfium-render, Apache-2.0), EPUB (hand-rolled zip+XML — **GPL
|
||||||
|
trap avoided**), Markdown (comrak/termimad), DJVU (djvu-rs + `ddjvu` fallback config).
|
||||||
|
All as registry modules, all runtime-disableable.
|
||||||
|
- **Rust focus**: binary data & byte streams, feature-gated crates, error taxonomy design,
|
||||||
|
more trait objects; reading crate docs as a skill.
|
||||||
|
- **English**: module documentation pages (PDF/EPUB/DJVU) in user-guide style.
|
||||||
|
- **DoD**: your entire document library previews; ranger uninstalled.
|
||||||
|
|
||||||
|
### Sprint 8 — Media & async (20 h) 🎯 **M2c**
|
||||||
|
- **App deliverable**: video thumbnail (spawn `ffmpeg -ss … -vframes 1`), audio metadata +
|
||||||
|
cover art (`ffprobe` JSON), **background preview pipeline**: std threads + mpsc now,
|
||||||
|
generation-counter cancellation, large-directory pagination; adopt **tokio** for the
|
||||||
|
pipeline by sprint end (ADR-005).
|
||||||
|
- **Rust focus**: `std::thread`, `mpsc`, `Arc`/`Mutex`, then async/await, `tokio::spawn`,
|
||||||
|
`select!`, cancellation patterns. Rust Book ch. 16 + async book chapters (English only!).
|
||||||
|
- **English**: article "Async Rust for a skeptic" — explain threads-vs-tokio in your own words.
|
||||||
|
- **DoD**: 4K-video directory scrolls at full speed; huge directory never blocks input.
|
||||||
|
|
||||||
|
### Sprint 9 — Script plugin protocol (20 h) 🎯 **M3a: extensible**
|
||||||
|
- **App deliverable**: freeze the **external plugin contract** (ADR-002 phase 2): previewer
|
||||||
|
argv `(path, w, h, x, y)` + env context + FIFO write-back + `cleaner` hook; documented
|
||||||
|
exit codes & SIGPIPE; opener scripts; `plugins/` dir discovery; two reference plugins
|
||||||
|
(bash: torrent info; python: git repo summary).
|
||||||
|
- **Rust focus**: process spawning & pipes, timeouts, safe command construction (no shell
|
||||||
|
injection), protocol design & semver, integration tests with fixture scripts.
|
||||||
|
- **English**: **plugin author guide** — the sprint's main artifact; write it as if for strangers.
|
||||||
|
- **DoD**: a script written only from the guide (no source peeking) works.
|
||||||
|
|
||||||
|
### Sprint 10 — Power features (20 h) 🎯 **M3b: feature parity with your ranger config**
|
||||||
|
- **App deliverable**: tabs, bookmarks + marks, fuzzy finder (nucleo) with **interruptible**
|
||||||
|
search (broot lesson), `:commands` set (`:find :delete :copy…`), `push` key-simulation,
|
||||||
|
shell integration `--choosefile/--choosedir/--selectfile`, cd-on-exit wrapper script,
|
||||||
|
session restore.
|
||||||
|
- **Rust focus**: state machines (modes), Rc-free architecture review, `macro_rules!` for
|
||||||
|
keymap tables, API refactoring under tests.
|
||||||
|
- **English**: article "Vim-style UX patterns in file managers".
|
||||||
|
- **DoD**: your shell rc sources the viexp wrapper; fuzzy find feels instant.
|
||||||
|
|
||||||
|
### Sprint 11 — Performance & robustness (20 h)
|
||||||
|
- **App deliverable**: `notify`-based live directory updates, virtual scrolling, startup
|
||||||
|
<50 ms, one-week dogfood with zero panics, `tracing` logs to file, panic hook restores
|
||||||
|
the terminal, criterion benchmarks committed, crash-fix buffer.
|
||||||
|
- **Rust focus**: profiling (`cargo flamegraph`, `perf`), benchmarking (criterion), reading
|
||||||
|
someone else's code (yazi's) for patterns, hardening.
|
||||||
|
- **English**: article "Profiling a TUI app: making viexp start in 40 ms".
|
||||||
|
- **DoD**: benchmark numbers in README; a week of daily use logged.
|
||||||
|
|
||||||
|
### Sprint 12 — Release & docs (20 h) 🎯 **M4: v0.1.0 public**
|
||||||
|
- **App deliverable**: `v0.1.0` — cargo-dist release builds, AUR PKGBUILD, Gitea release +
|
||||||
|
changelog, **user guide** (mdBook in-repo), plugin examples gallery, install docs.
|
||||||
|
- **Rust focus**: release engineering, MSRV policy, feature matrix testing, rustdoc comments
|
||||||
|
on the public plugin API.
|
||||||
|
- **English**: the user guide is the largest English artifact of the project; polish it hard.
|
||||||
|
- **DoD**: a stranger installs from AUR and writes a plugin using only the docs.
|
||||||
|
|
||||||
|
### Backlog (post-1.0, priority order)
|
||||||
|
Embedded Lua plugins (mlua, ADR-002 phase 3) · sixel/iTerm2/ueberzug++ adapters · DDS-style
|
||||||
|
inter-instance pub/sub · `NNN_FIFO`-style hover event stream · themes gallery · zoxide jump ·
|
||||||
|
archive mount/browsing depth.
|
||||||
|
|
||||||
|
## Milestones
|
||||||
|
|
||||||
|
| Milestone | After sprint | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| M1 | 3 | daily driver for basic navigation/file ops |
|
||||||
|
| M2a–c | 6–8 | full ranger replacement (images, docs, media) |
|
||||||
|
| M3a–b | 9–10 | extensible by scripts, feature parity |
|
||||||
|
| M4 | 12 | v0.1.0 released publicly |
|
||||||
|
|
||||||
|
At 45 min/day this is roughly a 12–13 month journey to v0.1.0 — the point is a working
|
||||||
|
product **and** durable skills, in that order, every single sprint.
|
||||||
|
|
||||||
|
## Cadence & safety nets
|
||||||
|
|
||||||
|
- Sprint notes live in `docs/sprints/`; each sprint's DoD becomes Gitea issues (milestone per
|
||||||
|
sprint) — see [[workflows/gitea-workflow|Gitea workflow]].
|
||||||
|
- If a sprint overruns by >30%, cut scope from the bottom of its DoD — never silently skip tests.
|
||||||
|
- One "buffer week" is allowed between sprints; more than one means the sprint was too big —
|
||||||
|
re-decompose in the retro.
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
type: sprint
|
||||||
|
number: 0
|
||||||
|
status: active
|
||||||
|
started: 2026-10-17
|
||||||
|
finished:
|
||||||
|
hours-planned: 6
|
||||||
|
hours-logged: 2
|
||||||
|
tags: [sprint, setup]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sprint 0 — Setup & deconstruction
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
Remove every barrier to practice: toolchain, fast feedback, project board, CI. After this
|
||||||
|
sprint, a practice session starts in 60 seconds and never touches configuration.
|
||||||
|
|
||||||
|
## App deliverable
|
||||||
|
`viexp` cargo project skeleton that compiles, one trivial test green, Gitea Actions CI green,
|
||||||
|
`bacon` watching check+clippy+test on save, Gitea board (project + labels + milestones) ready.
|
||||||
|
|
||||||
|
## Rust focus — the vital 20%
|
||||||
|
- What `rustc` / `cargo` / `crates.io` / `Cargo.toml` / `Cargo.lock` are and who owns which file.
|
||||||
|
- The compile cycle: `cargo check` (fast) vs `build` vs `run`; `cargo fmt`, `cargo clippy`.
|
||||||
|
- Reading a compiler error: error code (e.g. E0382), span, help hint — where to look first.
|
||||||
|
|
||||||
|
## Practice drills (20–30 min each)
|
||||||
|
- [ ] D1 — Install/verify: `rustup default stable`, `cargo --version`; run `rustup component
|
||||||
|
add clippy rustfmt`; install `bacon` (`cargo install --locked bacon`).
|
||||||
|
- [ ] D2 — `cargo new viexp --bin`, add a `lib.rs` skeleton (src split: `main.rs` calls
|
||||||
|
`viexp::run()`), one `#[test] fn sanity()` — make it fail, read the output, make it pass.
|
||||||
|
- [ ] D3 — Break it on purpose: introduce E0382 (move a `String`, then use it), E0308 (wrong
|
||||||
|
type), E0384 (reassign immutable). Paste each into the ErrorJournal **with a self-correction cue**.
|
||||||
|
- [ ] D4 — Gitea CI: `.gitea/workflows/ci.yml` running `fmt --check`, `clippy -D warnings`,
|
||||||
|
`test`. Push and watch it go green.
|
||||||
|
- [x] D5 — Gitea board: labels, milestone "Sprint 1", issues imported from Sprint-1 DoD.
|
||||||
|
(Done 2026-10-17: 11 labels, 13 sprint milestones, issues #1–#12; kanban board still needs
|
||||||
|
one-time manual creation in the web UI — Gitea 1.26.2 has no projects API.)
|
||||||
|
|
||||||
|
## English focus
|
||||||
|
README "Status" section (done), DevLog habit starts today, commit messages from now on:
|
||||||
|
Conventional Commits, imperative mood.
|
||||||
|
|
||||||
|
## Session rhythm (45 min)
|
||||||
|
5 min review → 30 min drill → 10 min DevLog + ErrorJournal.
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
- [ ] `cargo bacon` shows check+test passing on save
|
||||||
|
- [ ] CI green on `main`
|
||||||
|
- [ ] ErrorJournal has ≥3 entries with cues
|
||||||
|
- [ ] Sprint-1 milestone and issues exist on Gitea
|
||||||
|
- [ ] Retro filled
|
||||||
|
|
||||||
|
## Retrospective
|
||||||
|
### Shipped
|
||||||
|
### Frustrations
|
||||||
|
### Hours logged / English notes
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
type: sprint
|
||||||
|
number: 1
|
||||||
|
status: planned
|
||||||
|
started:
|
||||||
|
finished:
|
||||||
|
hours-planned: 20
|
||||||
|
hours-logged: 0
|
||||||
|
tags: [sprint, rust-basics]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Sprint 1 — Rust core via `vls`
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
Learn 80% of everyday Rust syntax by writing a real tool: `viexp ls`, a better `ls`.
|
||||||
|
|
||||||
|
## App deliverable
|
||||||
|
`viexp ls [PATH]` with: colored entries by type (dir/symlink/exec/image/archive), human sizes
|
||||||
|
(`1.4 K`, `2.1 M`), `--sort name|size|mtime`, `--hidden` toggle, `--long` (perms, dates),
|
||||||
|
`--json` (for tests), ≥10 unit tests, clippy clean.
|
||||||
|
|
||||||
|
## Rust focus — the vital 20%
|
||||||
|
Variables & shadowing, scalar types, functions, `if let`/`match`, loops & labels, `Vec`,
|
||||||
|
`String` vs `&str`, structs + methods, enums + `match` exhaustiveness, `Option`/`Result`,
|
||||||
|
the `?` operator, modules & `use`, `#[cfg(test)]` + `#[test]`.
|
||||||
|
Resources: Rust Book ch. 1–6, 7 (modules), 9 (errors) — **in English only**; rustlings
|
||||||
|
exercises `intro…functions…if…quiz1…enums…options…errors1-3`.
|
||||||
|
|
||||||
|
## Practice drills (20–30 min each — target ≈2 per day in week 1, features in week 2+)
|
||||||
|
- [ ] D1 — `read_dir` → print bare names; handle the `Result` with `?` and `anyhow` at `main`.
|
||||||
|
- [ ] D2 — `FileEntry` struct: name, is_dir, is_symlink, size, mtime; build with `metadata()`.
|
||||||
|
- [ ] D3 — Human size formatting fn + unit tests (B→K→M→G boundary values).
|
||||||
|
- [ ] D4 — Sorting: implement 3 comparators, `--sort` flag parsing (no clap yet — `std::env`).
|
||||||
|
- [ ] D5 — Colors: ANSI escape codes by hand (no crate) — learn what crates hide from you.
|
||||||
|
- [ ] D6 — `--hidden` filter (dotfiles) + tests.
|
||||||
|
- [ ] D7 — `--long` view: permissions string (`rwxr-x---`), date formatting.
|
||||||
|
- [ ] D8 — Refactor into `src/entry.rs`, `src/render.rs`, `src/cli.rs` — modules & `pub`.
|
||||||
|
- [ ] D9 — `--json` output via serde; golden-file test.
|
||||||
|
- [ ] D10 — Adopt `clap` for flags (derive API) — your first "big" crate.
|
||||||
|
- [ ] D11 — rustlings: enums + options + errors tracks.
|
||||||
|
- [ ] D12 — Review week: read your own code, note 3 things clippy taught you.
|
||||||
|
|
||||||
|
## English focus
|
||||||
|
README usage section; draft article "Why build a file manager in Rust" (150–200 words is enough).
|
||||||
|
|
||||||
|
## Session rhythm (45 min)
|
||||||
|
5 min review → 30 min drill → 10 min DevLog + ErrorJournal.
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
- [ ] `viexp ls ~/` is nicer than `ls` for your own eyes
|
||||||
|
- [ ] ≥10 tests green, clippy clean, CI green
|
||||||
|
- [ ] Article draft published in `docs/articles/`
|
||||||
|
- [ ] Retro filled; Sprint-2 milestone issues created
|
||||||
|
|
||||||
|
## Retrospective
|
||||||
|
### Shipped
|
||||||
|
### Frustrations
|
||||||
|
### Hours logged / English notes
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
type: task
|
||||||
|
status: todo
|
||||||
|
priority: P0
|
||||||
|
sprint: 0
|
||||||
|
gitea-issue: "#1"
|
||||||
|
created: 2026-10-17
|
||||||
|
due:
|
||||||
|
tags: [task, ci]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bootstrap cargo project and CI
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
A compiling `viexp` crate with lib/bin split, one passing test, and green Gitea Actions CI.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- [ ] `cargo new`-style skeleton: `src/main.rs` + `src/lib.rs` (`main` calls `viexp::run()`)
|
||||||
|
- [ ] `cargo test` green with one sanity test
|
||||||
|
- [ ] `.gitea/workflows/ci.yml`: `fmt --check` + `clippy -D warnings` + `test` on push
|
||||||
|
- [ ] First Conventional Commit pushed to `main`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
Rust Book ch. 1–2; see [[sprints/sprint-00-setup|Sprint 0]] drill D2/D4.
|
||||||
|
|
||||||
|
## Log
|
||||||
|
- 2026-10-17 — task created.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
type: task
|
||||||
|
status: todo
|
||||||
|
priority: P0
|
||||||
|
sprint: 0
|
||||||
|
gitea-issue: "#3"
|
||||||
|
created: 2026-10-17
|
||||||
|
due:
|
||||||
|
tags: [task, tooling]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Verify Rust toolchain and fast-feedback loop
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
`bacon` runs check + clippy + test on every save, so no practice minute is wasted on manual rebuilds.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- [ ] `rustup default stable`; `rustc --version` recorded in DevLog
|
||||||
|
- [ ] `clippy` and `rustfmt` components installed
|
||||||
|
- [ ] `bacon` installed and configured (check + test jobs)
|
||||||
|
- [ ] Editor shows inline errors (rust-analyzer) — verify once
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
Command reference: `rustup component add clippy rustfmt`; `cargo install --locked bacon`.
|
||||||
|
|
||||||
|
## Log
|
||||||
|
- 2026-10-17 — task created.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# English workflow
|
||||||
|
|
||||||
|
The project documentation is 100% English on purpose: the repo is an immersion environment.
|
||||||
|
Rust is learned through English, English through Rust.
|
||||||
|
|
||||||
|
## Daily habits (zero extra time)
|
||||||
|
|
||||||
|
- **Commit messages in English** — Conventional Commits format forces short, structured sentences.
|
||||||
|
- **DevLog entry** (10 min, last step of every session): 3–5 sentences about what you did,
|
||||||
|
in English. Don't translate from Russian in your head — describe the code, not the feelings.
|
||||||
|
- **Read error messages and docs in English only.** Never switch the compiler or Rust Book to Russian.
|
||||||
|
|
||||||
|
## Weekly habits
|
||||||
|
|
||||||
|
- **One article per sprint** in `articles/` (comparison, tutorial, retro) — reviewed for
|
||||||
|
language quality, not only content. Keep sentences short; prefer active voice.
|
||||||
|
- **Glossary growth**: every new term goes to `learning/glossary.md` with your own example
|
||||||
|
sentence. Re-using a term in your own sentence is what makes it stick.
|
||||||
|
- Weekly self-check: read Monday's DevLog aloud — if you can't, the sentence was too complex.
|
||||||
|
|
||||||
|
## What gets reviewed
|
||||||
|
|
||||||
|
| Artifact | Language focus |
|
||||||
|
|----------|----------------|
|
||||||
|
| Commit messages | imperative mood, conciseness |
|
||||||
|
| DevLog | past simple, describing actions |
|
||||||
|
| Issues / task notes | acceptance criteria phrasing ("must exist when done") |
|
||||||
|
| Articles | structure, connectors, technical vocabulary |
|
||||||
|
| Code comments & rustdoc | explaining "why", not "what" |
|
||||||
|
|
||||||
|
## Rules of thumb
|
||||||
|
|
||||||
|
1. Simple sentences first; compound later. Clarity beats sophistication.
|
||||||
|
2. If you needed a dictionary twice for one sentence — rewrite the sentence, not the vocab.
|
||||||
|
3. Write first, polish after; never let language anxiety stop the log entry.
|
||||||
|
4. Track words you *meet* vs words you *use* — the second list is the real progress.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Gitea workflow
|
||||||
|
|
||||||
|
The project is tracked at **https://git.v-port.ru/vaskes/viexp** (Gitea 1.26.2).
|
||||||
|
CLI access: `tea` (logged in as `vaskes`); API access with the saved token.
|
||||||
|
|
||||||
|
## Verified capabilities (2026-10-17)
|
||||||
|
|
||||||
|
| Feature | Via API? | Notes |
|
||||||
|
|---------|----------|-------|
|
||||||
|
| Labels | ✅ | 11 labels created (`type/*`, `priority/*`, `lang/english`) |
|
||||||
|
| Milestones | ✅ | 13 sprint milestones with due dates |
|
||||||
|
| Issues | ✅ | labels by **ID**, not by name (`POST /repos/vaskes/viexp/issues`) |
|
||||||
|
| Project board (kanban) | ❌ | endpoint missing in Gitea 1.26.2 — create once via web UI |
|
||||||
|
| Actions CI | ✅ | `.gitea/workflows/ci.yml` (repo has `has_actions: true`) |
|
||||||
|
|
||||||
|
## Organization
|
||||||
|
|
||||||
|
- **Project board** ("viexp development", created manually in the web UI):
|
||||||
|
columns `Backlog → In Progress → Review → Done`; sprints are tracked by milestones.
|
||||||
|
- **Milestones** = sprints: `Sprint 0 — Setup & deconstruction` … `Sprint 12 — Release & docs`
|
||||||
|
(one 20-hour sprint ≈ one month at ~45 min/day). Due dates are targets, not deadlines.
|
||||||
|
- **Labels**: `type/bug|feature|docs|research|chore|spike`, `priority/P0…P3`, `lang/english`.
|
||||||
|
- **Issues** are created from task notes (`docs/tasks/`) and sprint definitions-of-done;
|
||||||
|
the task note's `gitea-issue` property stores the issue number.
|
||||||
|
- Bootstrap/repeat script: `scripts/gitea-bootstrap.sh` (labels + milestones + seed issues).
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. One issue = one demoable outcome; acceptance criteria copied from the task note.
|
||||||
|
2. Branch per issue: `sprint-N/slug` or `feat/slug`; squash-merge into `main`.
|
||||||
|
3. Commit messages: Conventional Commits **in English** (`feat(preview): add kitty graphics support`)
|
||||||
|
— every commit is a micro English exercise.
|
||||||
|
4. An issue is closed only when its acceptance criteria are demoable.
|
||||||
|
5. Sprint retro (from the sprint note) is posted as a comment on the milestone's closing issue.
|
||||||
|
|
||||||
|
## Automation notes
|
||||||
|
|
||||||
|
- Gitea Actions can run `cargo fmt --check && cargo clippy -- -D warnings && cargo test` on push.
|
||||||
|
- Everything set up via API/`tea` is documented in `docs/workflows/` so it can be re-created
|
||||||
|
from scratch (the setup commands are in the sprint-0 task note).
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Research workflow
|
||||||
|
|
||||||
|
How research artifacts are collected, analyzed and turned into decisions — the pipeline that
|
||||||
|
feeds every architectural choice in viexp.
|
||||||
|
|
||||||
|
## Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
question → TMP/Research.md note (status: inbox)
|
||||||
|
→ sources collected (table filled)
|
||||||
|
→ findings summarized (status: in-progress)
|
||||||
|
→ synthesis article in articles/ (status: analyzed)
|
||||||
|
→ decision in decisions/ (ADR) + tasks/issues
|
||||||
|
→ archived (status: archived) once superseded
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Capture.** Any open question gets a `Research` note immediately (title = the question
|
||||||
|
domain, e.g. `djvu-preview-options`). One question per note — small notes beat monoliths.
|
||||||
|
2. **Collect.** Every source goes into the sources table with a one-line takeaway.
|
||||||
|
Screenshots and PDFs go to `attachments/`; paste links, not walls of copied text.
|
||||||
|
3. **Analyze.** Distill findings into bullets; mark `confidence: low|medium|high`.
|
||||||
|
Open questions move to follow-up notes, not into the article.
|
||||||
|
4. **Synthesize.** Cross-note comparisons become `Article` notes (e.g. the file-manager
|
||||||
|
comparison). The article states recommendations, not just facts.
|
||||||
|
5. **Decide.** Every recommendation that changes the architecture becomes an ADR
|
||||||
|
(`Decision` template) — short, dated, linked back to the research notes.
|
||||||
|
6. **Act.** ADRs spawn `Task` notes → Gitea issues (see [[workflows/gitea-workflow|Gitea workflow]]).
|
||||||
|
|
||||||
|
## Weekly review (Friday, 15 min)
|
||||||
|
|
||||||
|
- Process all `status: inbox` notes: promote, merge or archive.
|
||||||
|
- Update ADRs whose status changed during the week.
|
||||||
|
- Check that every active sprint task has a linked issue and vice versa.
|
||||||
|
|
||||||
|
## Obsidian mechanics used
|
||||||
|
|
||||||
|
- **Templates** (`TMP/`) keep structure uniform; headings are predictable, so folding works.
|
||||||
|
- **Properties** (`type`, `status`, `tags`, `sprint`) power the tag pane and search, e.g.
|
||||||
|
`["type": "research", "status": "inbox"]`.
|
||||||
|
- **Backlinks / graph** show which decisions depend on which research.
|
||||||
|
- **Attachments** are centralized in `docs/attachments/` (set in Obsidian settings).
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# viexp Gitea bootstrap — create labels, sprint milestones and seed issues.
|
||||||
|
# Re-runnable: existing items are detected and reused.
|
||||||
|
# Auth: reads the token saved by `tea login add` from ~/.config/tea/config.yml.
|
||||||
|
# NOTE (Gitea 1.26.2): the repo *project board* has no API on this instance —
|
||||||
|
# create "viexp development" once via the web UI (Issues → Projects).
|
||||||
|
set -u
|
||||||
|
TOKEN=$(grep -E '^\s*token:' ~/.config/tea/config.yml | head -1 | sed -E 's/^\s*token:\s*//; s/"//g')
|
||||||
|
API=https://git.v-port.ru/api/v1
|
||||||
|
REPO=vaskes/viexp
|
||||||
|
TZOFF=$(date +%:z)
|
||||||
|
|
||||||
|
req() { local m=$1 p=$2 d=${3:-}
|
||||||
|
if [ -n "$d" ]; then curl -s -X "$m" "$API$p" -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' -d "$d"
|
||||||
|
else curl -s -X "$m" "$API$p" -H "Authorization: token $TOKEN"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== labels =="
|
||||||
|
declare -a LABELS=(
|
||||||
|
'type/bug|ee0701|Something broken in shipped code'
|
||||||
|
'type/feature|84b6eb|New app capability'
|
||||||
|
'type/docs|d4c5f9|Documentation and vault work'
|
||||||
|
'type/research|fad8c7|Investigation / spike output expected'
|
||||||
|
'type/chore|ededed|Tooling, CI, maintenance'
|
||||||
|
'type/spike|fef2c0|Time-boxed experiment'
|
||||||
|
'priority/P0|b60205|Blocks the sprint'
|
||||||
|
'priority/P1|d93f0b|Important this sprint'
|
||||||
|
'priority/P2|fbca04|Normal'
|
||||||
|
'priority/P3|0e8a16|Nice to have'
|
||||||
|
'lang/english|5319e7|English-practice artifact'
|
||||||
|
)
|
||||||
|
for L in "${LABELS[@]}"; do
|
||||||
|
IFS='|' read -r name color desc <<< "$L"
|
||||||
|
req POST "/repos/$REPO/labels" "{\"name\":\"$name\",\"color\":\"$color\",\"description\":\"$desc\"}" >/dev/null
|
||||||
|
done
|
||||||
|
req GET "/repos/$REPO/labels" | jq -r '.[].name' | tr '\n' ' '; echo
|
||||||
|
|
||||||
|
echo "== milestones (sprints; see docs/roadmap.md) =="
|
||||||
|
create_ms() { # title description due
|
||||||
|
local existing
|
||||||
|
existing=$(req GET "/repos/$REPO/milestones?state=all" | jq -r --arg t "$1" '.[] | select(.title==$t) | .id')
|
||||||
|
if [ -n "$existing" ] && [ "$existing" != "null" ]; then echo "$existing"; return; fi
|
||||||
|
req POST "/repos/$REPO/milestones" "{\"title\":\"$1\",\"description\":\"$2\",\"due_on\":\"$3\"}" | jq -r '.id // empty'
|
||||||
|
}
|
||||||
|
S0="Sprint 0 — Setup & deconstruction"
|
||||||
|
S1="Sprint 1 — Rust core via vls"
|
||||||
|
create_ms "$S0" "Toolchain, CI, board, vault. ~6h." "2026-10-31T23:59:59$TZOFF" >/dev/null
|
||||||
|
create_ms "$S1" "viexp ls subcommand: colors, sizes, sorting, tests. 20h." "2026-11-30T23:59:59$TZOFF" >/dev/null
|
||||||
|
req GET "/repos/$REPO/milestones?state=all" | jq -r '.[] | "\(.id) \(.title)"'
|
||||||
|
|
||||||
|
echo "== issues =="
|
||||||
|
bash "$(dirname "$0")/gitea-issues.sh"
|
||||||
|
|
||||||
|
echo "== project board =="
|
||||||
|
echo "No API on Gitea 1.26.2 — create 'viexp development' board in the web UI (Issues → Projects), columns:"
|
||||||
|
echo " Backlog → In Progress → Review → Done (sprints tracked by milestones)"
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Fix-up: create issues with label IDs; diagnose projects API.
|
||||||
|
set -u
|
||||||
|
TOKEN=$(grep -E '^\s*token:' ~/.config/tea/config.yml | head -1 | sed -E 's/^\s*token:\s*//; s/"//g')
|
||||||
|
API=https://git.v-port.ru/api/v1
|
||||||
|
REPO=vaskes/viexp
|
||||||
|
|
||||||
|
req() { local m=$1 p=$2 d=${3:-}
|
||||||
|
if [ -n "$d" ]; then curl -s -X "$m" "$API$p" -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' -d "$d"
|
||||||
|
else curl -s -X "$m" "$API$p" -H "Authorization: token $TOKEN"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "== raw projects GET (first 200 chars) =="
|
||||||
|
req GET "/repos/$REPO/projects" | head -c 200; echo; echo
|
||||||
|
|
||||||
|
declare -A LMAP MMAP
|
||||||
|
while IFS=$'\t' read -r id name; do LMAP[$name]=$id; done < <(req GET "/repos/$REPO/labels" | jq -r '.[] | "\(.id)\t\(.name)"')
|
||||||
|
while IFS=$'\t' read -r id title; do MMAP[$title]=$id; done < <(req GET "/repos/$REPO/milestones?state=all" | jq -r '.[] | "\(.id)\t\(.title)"')
|
||||||
|
|
||||||
|
EXISTING=$(req GET "/repos/$REPO/issues?state=all&type=issues")
|
||||||
|
|
||||||
|
create_issue() { # title body milestone_title labels_csv -> $1 $2 $3 $4
|
||||||
|
local exist
|
||||||
|
exist=$(echo "$EXISTING" | jq -r --arg t "$1" '.[]? | select(.title==$t) | .number')
|
||||||
|
if [ -n "$exist" ] && [ "$exist" != "null" ]; then echo "#$exist (existing) $1"; return; fi
|
||||||
|
local ids=() n
|
||||||
|
IFS=',' read -ra names <<< "$4"
|
||||||
|
for n in "${names[@]}"; do [ -n "$n" ] && ids+=("${LMAP[$n]}"); done
|
||||||
|
local ids_json
|
||||||
|
ids_json=$(printf '%s\n' "${ids[@]}" | jq -R -s 'split("\n") | map(select(length>0) | tonumber) | map(select(. != null))')
|
||||||
|
local ms=${MMAP[$3]:-}
|
||||||
|
local body
|
||||||
|
if [ -n "$ms" ]; then
|
||||||
|
body=$(jq -n --arg t "$1" --arg b "$2" --argjson ms "$ms" --argjson ids "$ids_json" '{title:$t, body:$b, milestone:$ms, labels:$ids}')
|
||||||
|
else
|
||||||
|
body=$(jq -n --arg t "$1" --arg b "$2" --argjson ids "$ids_json" '{title:$t, body:$b, labels:$ids}')
|
||||||
|
fi
|
||||||
|
local num
|
||||||
|
num=$(req POST "/repos/$REPO/issues" "$body" | jq -r '.number // "FAIL \(.message)"')
|
||||||
|
echo "#$num $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
MS0="Sprint 0 — Setup & deconstruction"
|
||||||
|
MS1="Sprint 1 — Rust core via vls"
|
||||||
|
|
||||||
|
create_issue "Bootstrap cargo project skeleton (lib/bin split + sanity test)" "From docs/tasks/0001. Acceptance: src/main.rs + src/lib.rs (main calls viexp::run()), cargo test green, first conventional commit pushed." "$MS0" "type/chore,priority/P0"
|
||||||
|
create_issue "Add Gitea Actions CI: fmt --check + clippy -D warnings + test" ".gitea/workflows/ci.yml on push to main; green run required." "$MS0" "type/chore,priority/P0"
|
||||||
|
create_issue "Install bacon and verify fast-feedback loop" "cargo install --locked bacon; bacon runs check+test on save; rust-analyzer inline errors verified once." "$MS0" "type/chore,priority/P0"
|
||||||
|
create_issue "ErrorJournal: 3 deliberate compiler errors with self-correction cues" "Break code on purpose (E0382, E0308, E0384), record in docs/learning with cues. Learning artifact." "$MS0" "type/docs,priority/P1,lang/english"
|
||||||
|
create_issue "vls: read directory and print entries" "fs::read_dir + anyhow at main boundary. Sprint-1 drill D1." "$MS1" "type/feature,priority/P0"
|
||||||
|
create_issue "vls: FileEntry struct with metadata" "name, is_dir, is_symlink, size, mtime. Drill D2." "$MS1" "type/feature,priority/P0"
|
||||||
|
create_issue "vls: human-readable sizes + unit tests" "B/K/M/G boundary tests. Drill D3." "$MS1" "type/feature,priority/P1"
|
||||||
|
create_issue "vls: sorting modes (name|size|mtime) via flags" "std::env parsing first, clap later. Drill D4." "$MS1" "type/feature,priority/P1"
|
||||||
|
create_issue "vls: ANSI colors by file type (no crate)" "Hand-rolled escape codes. Drill D5." "$MS1" "type/feature,priority/P2"
|
||||||
|
create_issue "vls: --hidden toggle and --long view" "Dotfile filter; rwx perms string; dates. Drills D6-D7." "$MS1" "type/feature,priority/P2"
|
||||||
|
create_issue "vls: modularize + --json + clap derive" "entry/render/cli modules, serde golden-file test. Drills D8-D10." "$MS1" "type/chore,priority/P1"
|
||||||
|
create_issue "Article: Why build a file manager in Rust (draft)" "150-200 words, English, docs/articles/. Sprint-1 English artifact." "$MS1" "type/docs,priority/P2,lang/english"
|
||||||
|
echo "== done =="
|
||||||
Reference in New Issue
Block a user