From 066700b5407ba4a5402f6e7427337f5250a2081c Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Sat, 8 Aug 2026 18:27:52 -0400 Subject: [PATCH] feat(ui): add spinner, progress output, and summary table --- .omo/evidence/task-25-tofu-core.log | 328 +++++++++++++++++++++++++++ .omo/notepads/tofu-core/learnings.md | 54 +++++ src/tofu/ui.d | 275 ++++++++++++++++++++++ 3 files changed, 657 insertions(+) create mode 100644 .omo/evidence/task-25-tofu-core.log create mode 100644 src/tofu/ui.d diff --git a/.omo/evidence/task-25-tofu-core.log b/.omo/evidence/task-25-tofu-core.log new file mode 100644 index 0000000..9a5d815 --- /dev/null +++ b/.omo/evidence/task-25-tofu-core.log @@ -0,0 +1,328 @@ + Warning + Warning ## Warning for package tofu ## + Warning + Warning The following compiler flags have been specified in the package description + Warning file. They are handled by DUB and direct use in packages is discouraged. + Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags + Warning to the compiler, or use one of the suggestions below: + Warning + Warning warningsAsErrors: Use "buildRequirements" to control the warning level + Warning + Generating test runner configuration 'tofu-test-application' for 'application' (executable). + Warning Excluding main source file src/main.d from test. + Starting Performing "unittest" build using /usr/bin/dmd for x86_64. + Up-to-date toml 1.0.0: target for configuration [library] is up to date. + Building tofu ~main: building configuration [tofu-test-application] +src/tofu/ui.d(98,43): Error: function `core.thread.osthread.Thread.join(bool rethrow = true)` is not callable using argument types `(Duration)` + () @trusted { _thread.join(dur!("msecs")(2000)); }(); + ^ +src/tofu/ui.d(98,43): cannot pass argument `dur(2000L)` of type `Duration` to parameter `bool rethrow = true` +Error /usr/bin/dmd failed with exit code 1. + Warning + Warning ## Warning for package tofu ## + Warning + Warning The following compiler flags have been specified in the package description + Warning file. They are handled by DUB and direct use in packages is discouraged. + Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags + Warning to the compiler, or use one of the suggestions below: + Warning + Warning warningsAsErrors: Use "buildRequirements" to control the warning level + Warning + Generating test runner configuration 'tofu-test-application' for 'application' (executable). + Warning Excluding main source file src/main.d from test. + Starting Performing "unittest" build using /usr/bin/dmd for x86_64. + Up-to-date toml 1.0.0: target for configuration [library] is up to date. + Building tofu ~main: building configuration [tofu-test-application] +src/tofu/commands/info.d(325,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("neovim", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(325,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/info.d(335,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("nonexistent", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(335,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/info.d(356,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("neovim", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(356,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/info.d(371,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("neovim", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(371,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/info.d(386,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("ripgrep", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(386,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/info.d(397,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("firefox", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(397,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/info.d(412,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("firefox", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(412,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/info.d(425,34): Error: function `infoCommandWithIndex` is not callable using argument types `(string, ParsedArgs, Config, PackageIndex[] function(Config _) @safe)` + int ec = infoCommandWithIndex("ripgrep", flags, cfg, &fakeIndex); + ^ +src/tofu/commands/info.d(425,34): cannot pass argument `& fakeIndex` of type `PackageIndex[] function(Config _) @safe` to parameter `scope PackageIndex[] delegate(Config) @safe fetchFn` +src/tofu/commands/info.d(179,19): `tofu.commands.info.infoCommandWithIndex(string pkgName, ParsedArgs flags, Config cfg, scope PackageIndex[] delegate(Config) @safe fetchFn)` declared here +package @safe int infoCommandWithIndex( + ^ +src/tofu/commands/install.d(112,27): Error: taking the address of local variable `entry` is not allowed in a `@safe` function + foundEntry = &entry; + ^ +src/tofu/commands/install.d(231,34): Error: `@safe` function `tofu.commands.install.installCommand` cannot call `@system` function `std.stdio.readln!string.readln` + response = readln().strip(); + ^ +/usr/include/dlang/dmd/std/stdio.d(5242,20): which calls `makeGlobal` +@property ref File makeGlobal(StdFileHandle _iob)() + ^ +/usr/include/dlang/dmd/std/stdio.d(5244,25): and using `__gshared` instead of `shared` makes it fail to infer `@safe` + __gshared File.Impl impl; + ^ +/usr/include/dlang/dmd/std/stdio.d(4499,3): `std.stdio.readln!string.readln` is declared here +S readln(S = string)(dchar terminator = '\n') + ^ +src/tofu/commands/install.d(363,10): Error: `write` matches conflicting symbols: + write(path, content); + ^ +/usr/include/dlang/dmd/std/file.d(741,6): function `std.file.write!string.write` +void write(R)(R name, const void[] buffer) + ^ +/usr/include/dlang/dmd/std/stdio.d(4091,6): function `std.stdio.write!(string, string).write` +void write(T...)(T args) + ^ +src/tofu/commands/install.d(371,12): Error: module `std.string` import `octal` not found + import std.string : toStringz, octal; + ^ +src/tofu/commands/install.d(390,10): Error: `write` matches conflicting symbols: + write(path, script); + ^ +/usr/include/dlang/dmd/std/file.d(741,6): function `std.file.write!string.write` +void write(R)(R name, const void[] buffer) + ^ +/usr/include/dlang/dmd/std/stdio.d(4091,6): function `std.stdio.write!(string, string).write` +void write(T...)(T args) + ^ +src/tofu/commands/install.d(401,12): Error: module `std.string` import `octal` not found + import std.string : toStringz, octal; + ^ +src/tofu/commands/install.d(405,10): Error: `write` matches conflicting symbols: + write(path, script); + ^ +/usr/include/dlang/dmd/std/file.d(741,6): function `std.file.write!string.write` +void write(R)(R name, const void[] buffer) + ^ +/usr/include/dlang/dmd/std/stdio.d(4091,6): function `std.stdio.write!(string, string).write` +void write(T...)(T args) + ^ +src/tofu/commands/remove.d(49,9): Error: `@safe` function `tofu.commands.remove.removeCommand` cannot call `@system` function `std.stdio.makeGlobal!"core.stdc.stdio.stdout".makeGlobal` + stdout.flush(); + ^ +/usr/include/dlang/dmd/std/stdio.d(5244,25): and using `__gshared` instead of `shared` makes it fail to infer `@safe` + __gshared File.Impl impl; + ^ +/usr/include/dlang/dmd/std/stdio.d(5242,20): `std.stdio.makeGlobal!"core.stdc.stdio.stdout".makeGlobal` is declared here +@property ref File makeGlobal(StdFileHandle _iob)() + ^ +Error /usr/bin/dmd failed with exit code 1. + Warning + Warning ## Warning for package tofu ## + Warning + Warning The following compiler flags have been specified in the package description + Warning file. They are handled by DUB and direct use in packages is discouraged. + Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags + Warning to the compiler, or use one of the suggestions below: + Warning + Warning warningsAsErrors: Use "buildRequirements" to control the warning level + Warning + Generating test runner configuration 'tofu-test-application' for 'application' (executable). + Warning Excluding main source file src/main.d from test. + Starting Performing "unittest" build using /usr/bin/dmd for x86_64. + Up-to-date toml 1.0.0: target for configuration [library] is up to date. + Building tofu ~main: building configuration [tofu-test-application] + Linking tofu-test-application + Finished To force a rebuild of up-to-date targets, run again with --force + Running tofu-test-application +zeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building hello...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasedonezeta-makepkg: building depA...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building depB...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building target...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phasezeta-makepkg: building depA...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phaseerror build failed for depA: build failed for depA: +zeta-makepkg: building depA... +zeta-makepkg: configure phase +zeta-makepkg: build phase +zeta-makepkg: install phase +zeta-makepkg: building pkg...zeta-makepkg: configure phasezeta-makepkg: build phasezeta-makepkg: install phaseerror build failed for noexist: recipe not found at /tmp/tofu-test-build-buildall-norecipe-232842/nonexistent.recipe +warn corrupted cache for pkg: Found 'h' when expecting 'r'. (Line 1:2) +Warning: malformed TOML config at /tmp/tofu-test-config-bad-232842.toml: Invalid table key declaration (2:0) +Warning: invalid TOFU_DEFAULT_JOBS 'not-a-number', using default 1 +warn removing stale lock (PID 99999999 not alive) +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 - +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/package.lua HTTP/1.1" 200 - +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/build.sh HTTP/1.1" 200 - +127.0.0.1 - - [08/Aug/2026 18:26:40] code 404, message File not found +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/hello.recipe HTTP/1.1" 404 - +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 - +127.0.0.1 - - [08/Aug/2026 18:26:40] code 404, message File not found +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/package.lua HTTP/1.1" 404 - +127.0.0.1 - - [08/Aug/2026 18:26:40] code 404, message File not found +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/build.sh HTTP/1.1" 404 - +127.0.0.1 - - [08/Aug/2026 18:26:40] "GET /recipes/hello/scripts/build.sh HTTP/1.1" 200 - +127.0.0.1 - - [08/Aug/2026 18:26:41] "GET /recipes/hello/hello.recipe HTTP/1.1" 200 - +127.0.0.1 - - [08/Aug/2026 18:26:41] code 404, message File not found +127.0.0.1 - - [08/Aug/2026 18:26:41] "GET /recipes/hello/package.lua HTTP/1.1" 404 - +127.0.0.1 - - [08/Aug/2026 18:26:41] code 404, message File not found +127.0.0.1 - - [08/Aug/2026 18:26:41] "GET /recipes/hello/build.sh HTTP/1.1" 404 - +warn skipping index entry with empty name +warn skipping index entry with empty name +warn skipping index entry 'badpool': invalid pool 'bad_pool_value' + . checking binary firefox ... + - binary firefox-2.1.0 satisfies firefox>=2.0 + . checking binary libfoo ... + - binary libfoo-1.9 does not satisfy libfoo>=2.0 + . checking binary noexist ... + . checking binary brokenpkg ... + . checking binary badpkg ... + . checking binary testpkg ... + - binary testpkg-2.1.0 satisfies testpkg +==> ──── building depA (1/3) ──── + ok depA +==> ──── building depB (2/3) ──── + ok depB +==> ──── building target (3/3) ──── + ok target +==> ──── building depA (1/3) ──── + - nothing to build +==> ──── building pkg (1/1) ──── + - pkg already built, skipping +==> ──── building pkg (1/1) ──── + ok pkg +==> fetching recipe hello + . downloading hello.recipe + . downloading package.lua + . downloading build.sh + ok recipe hello downloaded +==> fetching recipe hello + . downloading hello.recipe +==> fetching recipe hello + . downloading hello.recipe + . downloading package.lua + - no package.lua for hello (optional manifest) + . downloading build.sh + - no build.sh for hello (optional build script) + . custom build system: fetching scripts/build.sh + ok recipe hello downloaded +==> fetching recipe hello + . downloading hello.recipe + . downloading package.lua + - no package.lua for hello (optional manifest) + . downloading build.sh + - no build.sh for hello (optional build script) + ok recipe hello downloaded +==> fetching recipe hello + . downloading hello.recipe + . fetching index http://127.0.0.1:38591//index.lua + ok index loaded: 3 packages + . fetching index http://127.0.0.1:52363//index.lua + . fetching index http://127.0.0.1:42821//index.lua + . fetching index http://127.0.0.1:43871//index.lua + . fetching index http://127.0.0.1:44243//index.lua + ok index loaded: 0 packages + . fetching index http://127.0.0.1:41801//index.lua + ok index loaded: 1 packages + . fetching index http://127.0.0.1:48459//index.lua + ok index loaded: 2 packages + . fetching index http://127.0.0.1:57985//index.lua + . fetching index http://127.0.0.1:55199//index.lua + ok index loaded: 1 packages +installing package...error: build failedreason: missing dependencytestpkg-2.1 is already installed -- use -ReProvide to reinstall - already installed — skipping + ok installed A with 2 dependencies + - nothing to install +install failed: conflict detectedtestpkg-2.1 is already installed -- use -ReProvide to reinstallwarn corrupted installed state at /tmp/tofu-test-state-corrupt-232842/installed.json: Found 'h' when expecting 'r'. (Line 1:2) +warn corrupted installed state at /tmp/tofu-test-state-corrupt-232842/installed.json: Found 'h' when expecting 'r'. (Line 1:2) +19 modules passed unittests + - already installed — skipping + ok installed A with 0 dependencies + - binary libfoo-2.1 satisfies libfoo>=2.0 + - libfoo: binary 1.9 too old, building from recipe + - libfoo: binary 1.9 too old, building from recipe + - binary libbar-3.0 satisfies libbar (unconstrained) + - libbaz: no binary available, building from recipe + - binary libfoo-1.5 satisfies libfoo>=1.0 + - libfoo: binary 1.5 too old, building from recipe + - binary parentA-1.5 satisfies parentA (unconstrained) + - binary parentB-1.5 satisfies parentB (unconstrained) +==> generating build plan + - + C (recipe) + - + A (recipe) + ok build plan: 2 packages +==> generating build plan + - recipe libfoo not in cache, re-fetching + - + libfoo (recipe) + ok build plan: 1 packages +==> generating build plan +==> generating build plan + - nothing to build (all binary) +==> generating build plan + - + mypkg (recipe) + ok build plan: 1 packages +==> generating build plan + - + libfoo (recipe) + - + mypkg (recipe) + ok build plan: 2 packages + Warning + Warning ## Warning for package tofu ## + Warning + Warning The following compiler flags have been specified in the package description + Warning file. They are handled by DUB and direct use in packages is discouraged. + Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags + Warning to the compiler, or use one of the suggestions below: + Warning + Warning warningsAsErrors: Use "buildRequirements" to control the warning level + Warning + Starting Performing "debug" build using /usr/bin/dmd for x86_64. + Up-to-date toml 1.0.0: target for configuration [library] is up to date. + Building tofu ~main: building configuration [application] +src/tofu/commands/remove.d(11,8): Error: module `tofu.install` import `runRemove` not found +import tofu.install : runRemove, InstallException; + ^ +Error /usr/bin/dmd failed with exit code 1. + Warning + Warning ## Warning for package tofu ## + Warning + Warning The following compiler flags have been specified in the package description + Warning file. They are handled by DUB and direct use in packages is discouraged. + Warning Alternatively, you can set the DFLAGS environment variable to pass custom flags + Warning to the compiler, or use one of the suggestions below: + Warning + Warning warningsAsErrors: Use "buildRequirements" to control the warning level + Warning + Starting Performing "debug" build using /usr/bin/dmd for x86_64. + Up-to-date toml 1.0.0: target for configuration [library] is up to date. + Building tofu ~main: building configuration [application] +src/main.d(25,8): Error: unable to read module `info` +import tofu.commands.info : infoCommand; + ^ +src/main.d(25,8): Expected 'tofu/commands/info.d' or 'tofu/commands/info/package.d' in one of the following import paths: +import path[0] = src/ +import path[1] = ../../.dub/packages/toml/1.0.0/toml/src/ +import path[2] = /usr/include/dlang/dmd +Error /usr/bin/dmd failed with exit code 1. diff --git a/.omo/notepads/tofu-core/learnings.md b/.omo/notepads/tofu-core/learnings.md index acaceb9..227657e 100644 --- a/.omo/notepads/tofu-core/learnings.md +++ b/.omo/notepads/tofu-core/learnings.md @@ -1029,3 +1029,57 @@ The sandbox test (test 2) creates a sentinel file, serves an index containing `o - `dub test` passes — all 18 modules, including search.d's 7 unittests. - `dub run -- -Ss neovim` fails on lock file creation (`~/.cache/tofu` directory doesn't exist) — pre-existing issue, expected without initialized environment. - Evidence logged to `.omo/evidence/task-20-tofu-core.log`. + +--- + +## Task 25 — `tofu.ui` (spinner, progress output, summary table) + +### Architecture +- Module `tofu.ui` — file `src/tofu/ui.d`, standalone module with no external dependencies beyond `tofu.log` and `core.thread`. +- Three public APIs: `Spinner` class, `buildSeparator`, `summaryTable`. +- `startSpinner(string label)` factory returns `Spinner` handle — creates and starts animation. + +### Spinner — thread-based animation +- **TTY detection**: `isatty(1)` from `core.sys.posix.unistd` — when stdout is NOT a terminal, spinner prints `"