diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..639e1fa --- /dev/null +++ b/.gitignore @@ -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 +*~ diff --git a/README.md b/README.md index e69de29..50d9246 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/TMP/Article.md b/TMP/Article.md new file mode 100644 index 0000000..36f95b7 --- /dev/null +++ b/TMP/Article.md @@ -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.* diff --git a/TMP/Decision.md b/TMP/Decision.md new file mode 100644 index 0000000..ee5aecc --- /dev/null +++ b/TMP/Decision.md @@ -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: + + diff --git a/TMP/DevLog.md b/TMP/DevLog.md new file mode 100644 index 0000000..6f1b523 --- /dev/null +++ b/TMP/DevLog.md @@ -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 +- diff --git a/TMP/ErrorJournal.md b/TMP/ErrorJournal.md new file mode 100644 index 0000000..f08e9cc --- /dev/null +++ b/TMP/ErrorJournal.md @@ -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. diff --git a/TMP/Glossary.md b/TMP/Glossary.md new file mode 100644 index 0000000..f39470a --- /dev/null +++ b/TMP/Glossary.md @@ -0,0 +1,11 @@ +--- +type: glossary +created: {{date}} +tags: [glossary, english] +--- + +# {{title}} + +| Term / phrase | Translation | Where I met it | My example sentence | +| ------------- | ----------- | -------------- | ------------------- | +| | | | | diff --git a/TMP/Research.md b/TMP/Research.md new file mode 100644 index 0000000..f3822e1 --- /dev/null +++ b/TMP/Research.md @@ -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). + + diff --git a/TMP/Sprint.md b/TMP/Sprint.md new file mode 100644 index 0000000..b33ae1d --- /dev/null +++ b/TMP/Sprint.md @@ -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 diff --git a/TMP/Task.md b/TMP/Task.md new file mode 100644 index 0000000..0c84c90 --- /dev/null +++ b/TMP/Task.md @@ -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. + + diff --git a/docs/articles/file-manager-comparison.md b/docs/articles/file-manager-comparison.md new file mode 100644 index 0000000..76f5063 --- /dev/null +++ b/docs/articles/file-manager-comparison.md @@ -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. diff --git a/docs/attachments/.gitkeep b/docs/attachments/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/decisions/ADR-001-tui-framework.md b/docs/decisions/ADR-001-tui-framework.md new file mode 100644 index 0000000..83db593 --- /dev/null +++ b/docs/decisions/ADR-001-tui-framework.md @@ -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. diff --git a/docs/decisions/ADR-002-extension-architecture.md b/docs/decisions/ADR-002-extension-architecture.md new file mode 100644 index 0000000..fdb2438 --- /dev/null +++ b/docs/decisions/ADR-002-extension-architecture.md @@ -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. diff --git a/docs/decisions/ADR-003-file-type-modules.md b/docs/decisions/ADR-003-file-type-modules.md new file mode 100644 index 0000000..678647b --- /dev/null +++ b/docs/decisions/ADR-003-file-type-modules.md @@ -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. diff --git a/docs/decisions/ADR-004-preview-protocol-chain.md b/docs/decisions/ADR-004-preview-protocol-chain.md new file mode 100644 index 0000000..2e09971 --- /dev/null +++ b/docs/decisions/ADR-004-preview-protocol-chain.md @@ -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. diff --git a/docs/decisions/ADR-005-async-adoption.md b/docs/decisions/ADR-005-async-adoption.md new file mode 100644 index 0000000..2426a54 --- /dev/null +++ b/docs/decisions/ADR-005-async-adoption.md @@ -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. diff --git a/docs/devlog/2026-10-17.md b/docs/devlog/2026-10-17.md new file mode 100644 index 0000000..ffd3f8c --- /dev/null +++ b/docs/devlog/2026-10-17.md @@ -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. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..072bac8 --- /dev/null +++ b/docs/index.md @@ -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`). diff --git a/docs/learning/glossary.md b/docs/learning/glossary.md new file mode 100644 index 0000000..f176f51 --- /dev/null +++ b/docs/learning/glossary.md @@ -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. | diff --git a/docs/research/competitor-landscape.md b/docs/research/competitor-landscape.md new file mode 100644 index 0000000..ad99492 --- /dev/null +++ b/docs/research/competitor-landscape.md @@ -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) diff --git a/docs/research/plugin-architecture.md b/docs/research/plugin-architecture.md new file mode 100644 index 0000000..b480c26 --- /dev/null +++ b/docs/research/plugin-architecture.md @@ -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 diff --git a/docs/research/ranger-deep-dive.md b/docs/research/ranger-deep-dive.md new file mode 100644 index 0000000..8f0f3a4 --- /dev/null +++ b/docs/research/ranger-deep-dive.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 diff --git a/docs/research/rust-preview-stack.md b/docs/research/rust-preview-stack.md new file mode 100644 index 0000000..f118e79 --- /dev/null +++ b/docs/research/rust-preview-stack.md @@ -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 diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..1d66e1c --- /dev/null +++ b/docs/roadmap.md @@ -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` 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. diff --git a/docs/sprints/sprint-00-setup.md b/docs/sprints/sprint-00-setup.md new file mode 100644 index 0000000..01d150a --- /dev/null +++ b/docs/sprints/sprint-00-setup.md @@ -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 diff --git a/docs/sprints/sprint-01-rust-core.md b/docs/sprints/sprint-01-rust-core.md new file mode 100644 index 0000000..51bc441 --- /dev/null +++ b/docs/sprints/sprint-01-rust-core.md @@ -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 diff --git a/docs/tasks/0001-bootstrap-cargo-project-and-ci.md b/docs/tasks/0001-bootstrap-cargo-project-and-ci.md new file mode 100644 index 0000000..6cefa28 --- /dev/null +++ b/docs/tasks/0001-bootstrap-cargo-project-and-ci.md @@ -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. diff --git a/docs/tasks/0002-verify-rust-toolchain-and-fast-feedback.md b/docs/tasks/0002-verify-rust-toolchain-and-fast-feedback.md new file mode 100644 index 0000000..855fcb3 --- /dev/null +++ b/docs/tasks/0002-verify-rust-toolchain-and-fast-feedback.md @@ -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. diff --git a/docs/workflows/english-workflow.md b/docs/workflows/english-workflow.md new file mode 100644 index 0000000..0aebe1b --- /dev/null +++ b/docs/workflows/english-workflow.md @@ -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. diff --git a/docs/workflows/gitea-workflow.md b/docs/workflows/gitea-workflow.md new file mode 100644 index 0000000..b03060a --- /dev/null +++ b/docs/workflows/gitea-workflow.md @@ -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). diff --git a/docs/workflows/research-workflow.md b/docs/workflows/research-workflow.md new file mode 100644 index 0000000..a74f204 --- /dev/null +++ b/docs/workflows/research-workflow.md @@ -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). diff --git a/scripts/gitea-bootstrap.sh b/scripts/gitea-bootstrap.sh new file mode 100644 index 0000000..d2d0b11 --- /dev/null +++ b/scripts/gitea-bootstrap.sh @@ -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)" diff --git a/scripts/gitea-issues.sh b/scripts/gitea-issues.sh new file mode 100644 index 0000000..2ce1a85 --- /dev/null +++ b/scripts/gitea-issues.sh @@ -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 =="