DEV Community

Cover image for 463 GitHub Stars Didn't Validate My Windows App. Other People's PCs Did.
Zihang Dong 董子航
Zihang Dong 董子航

Posted on

463 GitHub Stars Didn't Validate My Windows App. Other People's PCs Did.

Eighteen days after I open-sourced ToolKnit Desktop, the repository had reached 463 GitHub stars and 56 forks. GitHub's release asset counter had also passed 3,000 installer downloads across four public releases.

Those figures are a snapshot from August 17, 2026, and asset downloads are not the same as unique users. Still, they told me that people were curious.

They did not tell me that the app was reliable.

The useful feedback arrived when ToolKnit left my machine.

A hardware inspector that worked on my Windows 10 machine returned nothing on another user's Windows 11 system. A window that maximized perfectly most of the time occasionally left a thin gap along the top and left edges. PPT conversion worked, but on some machines it was slow enough to feel broken and could trigger a Windows printer dialog. An audio tool kept resources from the previously loaded file. A PDF text-editing experiment produced ghosted text because the original page content and the replacement layer were both visible.

None of those problems appeared in the clean version of the product that existed on my own computer.

The first 463 stars did not validate my code. They expanded the number of environments in which it could be wrong.

That has been the most valuable part of releasing it.

It started with one file problem

ToolKnit began with an ordinary PDF task.

I did not want another subscription, another oversized application, or another website asking me to upload a private document. I needed a small operation to be simple, predictable, and close to the file itself.

One PDF tool became several. The same pattern appeared with images, audio, video, text, and presentations. Many everyday file operations were individually small, but the available solutions were often fragmented across unrelated applications and websites.

Eventually I decided that some of these workflows needed a real desktop environment.

Browsers are excellent for quick access, but local files introduce requirements that are easier to handle in a desktop application:

  • larger files and longer-running jobs
  • repeatable output directories
  • native drag and drop
  • local command-line automation
  • hardware and system inspection
  • optional processing runtimes
  • explicit control over when data leaves the machine

That became ToolKnit Desktop.

I build and primarily maintain it alone, usually after my day job. That makes scope discipline and reusable patterns less of an aesthetic preference and more of a survival constraint.

Version 2.0, released on August 16, contains 49 desktop tools across 11 categories, with 46 selected capabilities exposed through MCP. They cover PDF, PPT, image, audio, video, text, calculators, hardware inspection, cleanup, creative utilities, and AI-assisted workflows.

The application is Windows-first and built with Tauri 2, Rust, and JavaScript. It also uses focused engines where appropriate, including PDF.js, pdf-lib, qpdf, FFmpeg, Whisper, and LibreOffice.

The technology list is less important than the boundary around each component: one engine should solve one clear class of problems, and the application should make its inputs, outputs, dependencies, and failure modes visible.

ToolKnit Desktop 2.0 home screen

Local-first requires more than avoiding an upload button

ToolKnit is local-first by default.

Its ordinary PDF, PPT, image, audio, video, text, hardware, and cleanup operations run on the user's computer. ToolKnit does not upload those source files to its own server.

That statement needs qualification because the application also includes AI features.

When a user explicitly invokes AI polishing, translation, document generation, table generation, or another model-backed workflow, the relevant content is sent to the model service that user configured. The interface must make that transition clear.

This distinction matters. "Local-first" should describe an observable data boundary, not act as a vague privacy label.

I started treating every feature as a processing contract:

preflight
  -> validate input
  -> declare required dependency
  -> execute locally or request explicit remote authorization
  -> report progress
  -> return a structured result
  -> reveal the exact output location
  -> support retry, cancellation, or recovery
Enter fullscreen mode Exit fullscreen mode

That model improved more than privacy messaging. It also made the application easier to test and helped connect the desktop interface to the CLI and MCP layers.

One capability, three different surfaces

ToolKnit 2.0 supports three ways of working with suitable capabilities:

  1. The desktop application provides visual preview, drag and drop, editing, and interactive configuration.
  2. The CLI supports deterministic batch operations and scripts.
  3. MCP exposes selected local capabilities to compatible IDE agents through explicit tool contracts.

These surfaces should share behavior, but they should not blindly expose every feature.

A PDF merge operation has clear files, ordering, output, and error conditions. It fits a CLI or agent tool well.

Dragging a visual component inside a PDF editor is different. Its core export logic may be reusable, but the interaction itself belongs in the desktop application.

This led to a useful rule:

A feature should enter the CLI or MCP layer only when its inputs, side effects, progress, output, and failures can be explained without relying on hidden UI state.

Adding a command is easy. Maintaining a trustworthy automation contract is the real work.

Bug 1: maximizing a custom window was probabilistic

ToolKnit uses custom window chrome and optional rounded corners.

At normal size, the window could look correct. When maximized, the rounded corners needed to disappear so the content reached the screen edges. The first implementation appeared to work, but repeated testing exposed a small probability that the top or left side would not fully cover the available area.

This was not a simple CSS border-radius bug.

Several states were involved:

  • the native Windows window state
  • the Tauri maximize event
  • the transparent window surface
  • the custom frame and shadow
  • the frontend's maximized class
  • the timing of the final bounds update

If those states changed in the wrong order, the visual result could be one frame behind the native window.

The larger lesson was that system state needs a single synchronization path. Every page should not implement its own maximize logic, and frontend layout should not guess whether the native window is maximized.

The fix required treating maximize and restore as transitions, not isolated button clicks. The application needed one shared window-state controller, consistent state reconciliation, and repeated testing across maximize, restore, double-click, taskbar restoration, and keyboard-driven window changes.

A bug that happened only occasionally was still a release bug. Probability is not the same as harmlessness.

Bug 2: Windows 10 success said nothing about Windows 11

The hardware tools originally worked on the machine where I built them.

Then a Windows 11 Professional user opened every hardware page and received incomplete data. Running the app as administrator did not help. The useful clue appeared only after the application began preserving and displaying the underlying error:

CannotCreateTypeConstrainedLanguage
Enter fullscreen mode Exit fullscreen mode

A PowerShell execution environment was preventing an operation that my development machine allowed.

This changed how I thought about Windows compatibility. "Works on Windows" is not one test case. Different editions, policies, language modes, permissions, firmware implementations, drivers, and vendor-specific data can all change the result.

Hardware inspection now has to be approached as a layered query problem:

  • prefer stable system interfaces
  • avoid unnecessary dynamic type creation
  • preserve stderr instead of reducing every failure to "inspection failed"
  • provide fallback sources when one query path is unavailable
  • allow partial results rather than discarding the entire inspection
  • distinguish permissions from unsupported or missing data

The most important improvement was not another query. It was exposing the actual failure.

A specific error can be fixed. A generic toast cannot.

Bug 3: PPT conversion was really dependency management

Converting PPT files accurately is not just a matter of reading XML from a ZIP archive. Fonts, shapes, layout rules, transitions, and Office rendering behavior make fidelity difficult.

ToolKnit uses an optional LibreOffice runtime for PPT rendering. Bundling that runtime into the base installer would substantially increase the download for every user, including people who never open a presentation tool.

So the runtime is installed on demand.

That decision created a complete secondary product flow:

  • detect whether the dependency is available
  • prevent entry into a tool that cannot work yet
  • offer official and regional download sources
  • display download and installation stages separately
  • verify the extracted runtime
  • store it in the application's managed data location
  • reuse it without rescanning on every page render
  • run conversions headlessly with an isolated profile

The first progress indicator reached 100% while extraction and verification were still running. From the user's perspective, the installation froze for another minute. Technically, the download was complete. Product-wise, the progress report was misleading.

PPT conversion also exposed a printer-related Windows dialog and took too long on small presentations. Resolving the runtime path repeatedly and starting conversion with the wrong process assumptions made the workflow slower and noisier than necessary.

The general lesson is that optional dependencies are not a packaging footnote. Detection, installation, verification, startup cost, caching, and failure recovery are part of the feature.

Bug 4: the second audio file was more revealing than the first

The BPM detector became much more accurate after improving the analysis pipeline, but another problem remained.

When a user uploaded a different audio file, the previous playback and processing state was not completely destroyed. The new file entered a page that still contained resources associated with the old one.

This class of bug appears across media tools:

  • old object URLs remain alive
  • animation frames continue running
  • audio contexts or nodes survive
  • stale asynchronous results update the new session
  • waveform data is reused unintentionally
  • previous playback position leaks into the next file

The first upload is a happy-path test. The second upload is a lifecycle test.

I now consider replacement, cancellation, and page exit part of the core media workflow. Every resource should have a clear owner and a clear destruction point.

Bug 5: PDF text is not a word processor document

A user opened a normal, non-scanned PDF and expected to change a word directly.

That expectation is reasonable. The internal structure of a PDF makes it difficult.

Visible text may be stored as separate glyph runs with embedded fonts, transformation matrices, positioning commands, and no semantic concept of a paragraph. Replacing one word can change its width without providing any layout engine to reflow the surrounding content.

My lightweight editor used selectable hit regions and replacement layers. When the original content was not masked precisely, the replacement appeared on top of the old text and created a visible ghost.

That failure clarified the product boundary.

ToolKnit can support useful lightweight PDF edits, insertion, movement, resizing, rotation, masking, and export. It should not claim to provide the same native document model as a full professional PDF editor.

The engineering work is still valuable, but the interface and documentation must describe what the tool actually guarantees.

Responsive UI bugs usually appeared after real data arrived

Many pages looked correct in their initial empty state.

Then a result arrived.

Long filenames exceeded a dialog. Statistics overlapped controls. A right-side preview grew while the middle column stayed empty. A menu opened beneath a scrolling list because its stacking context was wrong. Controls that fit in a maximized window were clipped at the default 1400 x 900 size.

These bugs encouraged a better layout test matrix:

Dimension Cases
Window default, maximized, restored
Data empty, short, long, many items
Task idle, running, completed, failed
Lifecycle first file, replacement file, repeated run
Environment dependency present, absent, damaged
System Windows versions, scaling, permission levels

A screenshot of an empty page is not evidence of responsive behavior. A page needs to survive its largest meaningful state.

Open source widened the test surface

Before release, I had tested almost every feature myself. That was useful, but all of those tests still shared many assumptions:

  • the same hardware
  • the same Windows configuration
  • the same display
  • the same network
  • the same filesystem habits
  • the same understanding of how the interface was intended to work

External users removed those shared assumptions.

Their reports showed that a reproducible bug description can be more valuable than another feature request. The project also received its first outside pull requests, which forced me to think more clearly about contribution scope, regression risk, and how to review changes without losing product consistency.

Stars helped people discover the repository. Issues, screenshots, sample files, and environment details helped improve it.

Those are different forms of contribution, and both matter.

What I would recommend to another solo developer

The lessons I am carrying forward are simple, but not easy.

Define completion around repeated use

Test the second upload, second run, cancel path, restored window, and missing dependency.

Preserve real errors

Do not replace actionable stderr with a generic notification. Sanitize it, bound its length, and give the user enough context to report it.

Centralize operating-system behavior

Window controls, storage paths, permissions, dependency resolution, and external process execution should not be reimplemented independently by every page.

Make privacy boundaries concrete

State which operations are local, which invoke a configured external provider, and what data is sent.

Use on-demand dependencies deliberately

A smaller installer is useful only if dependency installation is understandable and recoverable.

Let automation earn its interface

Expose a capability to CLI or MCP when it has a deterministic, inspectable contract, not merely because automation sounds impressive.

Publish before your environment becomes your only reality

Private testing can verify logic. It cannot reproduce the diversity of real Windows machines.

What ToolKnit 2.0 is, and is not

ToolKnit Desktop 2.0 is a Windows-first local file workbench with 49 tools across 11 categories.

It is not a replacement for every professional creative or document application. Some advanced capabilities require optional runtimes. AI operations are not offline unless the configured provider itself is local. Lightweight PDF editing has format-level limitations.

I think stating those limits makes the project stronger.

The purpose of ToolKnit is not to promise that one application solves every possible file problem. It is to make a useful set of frequent operations clearer, more local, and easier to repeat.

Try it, inspect it, or break an assumption

ToolKnit Desktop is open source under the Apache License 2.0.

If you test it, a reproducible issue with the Windows version, operation, file type, and visible error is especially helpful.

For other solo developers, I am curious:

Which bug in your product appeared only after it left your own machine?

That is the part of open-source growth I now value most.

Top comments (0)