One of the easiest ways to evaluate a framework is to run its initializer and look at the Git diff.
The first impression is often reassuring: routes, layouts, components, styles, configuration, build scripts, and examples are all present. Nothing is hidden. You can edit everything.
That reassurance has an expiration date.
After the first framework upgrade, the project has to answer a question it was never prepared for: which of these files express our product, and which are old copies of the framework?
TidyPress tries to leave a smaller footprint. Not because the engine is simple, but because engine complexity should remain owned and replaceable by the engine.
The repository shows what the project owns. The cache contains what can be regenerated.
Scaffolding transfers ownership
Copying a file into a new project is not a neutral act. The framework has handed that file to the user.
If the file contains a title and example paragraph, that ownership is sensible. If it contains the route runtime, responsive shell, search integration, and internal imports required by the framework, the user has become the maintainer of infrastructure they did not choose.
This creates a familiar upgrade pattern:
- the framework fixes accessibility or responsive behavior;
- existing projects still contain the copied old layout;
- migration instructions ask users to compare two large files;
- local customizations make an automatic replacement unsafe;
- each project quietly becomes its own fork.
The starter felt transparent because everything was visible. In practice it made the boundary impossible to see.
What belongs in a TidyPress project
A TidyPress publish root contains decisions the author can reasonably own:
site/
├── tidypress.config.ts # public structure and capabilities
├── src/content/ # authored Markdown and MDX
├── public/ # project assets
└── site/ # optional owned extensionsThe engine’s route runtime, default layouts, MDX components, Astro integration,
and baseline styles remain in the installed packages. tidypress init creates
content and configuration, not a private copy of the framework application.
When the project upgrades TidyPress, those engine files upgrade together. When the project needs to depart from the baseline, it does so through a named style, slot, component, layout, or presentation boundary.
The repository can remain small without being closed.
Astro still needs a project root
This ownership model creates a real implementation problem. Astro expects to build an application from a root containing its configuration, pages, content setup, integrations, and dependencies. The user’s authored files deliberately do not contain that complete application.
TidyPress reconciles the two by preparing an isolated Astro root under the platform’s user cache directory. The cache key includes the absolute publish root and the active engine version. Development, production build, and preview receive mode-appropriate generated roots.
Project content and declared presentation paths are mounted into that root. The engine supplies the route runtime and components. Astro sees a complete application; the user’s repository remains a publication rather than an Astro fork.
Mount only what the project declared
The easy implementation would copy the entire repository into the generated root. That risks pulling local dependencies, build output, unrelated monorepo packages, and secrets into a context where they do not belong.
TidyPress instead derives project paths from normalized configuration:
- styles and scripts;
- UI component overrides;
- named slots;
- Markdown plugins;
- layouts and docs forms;
- collection presentations and route views.
Those paths form a manifest. The manifest is both an import map and an ownership record: if a project file enters the renderer, configuration should explain why.
Public assets use a similarly explicit merge. Engine assets enter a cache-owned public directory first; project assets copy over them. The project can replace an icon or add media without editing the package.
The hard part appears under npx
A framework can look healthy inside its monorepo while failing immediately on a new machine. Workspace links and hoisted dependencies make imports resolve in ways a packed package cannot reproduce.
The problem becomes sharper under npx. The CLI may execute from a package-
manager cache while the project’s tidypress.config.ts imports defineConfig
from tidypress. The publish root may not have a local dependency at all.
TidyPress handles that case with a narrow runtime shim in the publish root’s
node_modules only when required. The shim re-exports configuration from the
active CLI package. It does not link the whole CLI tree and invite the bundler
to traverse private implementation.
This is not an exciting feature. It is the difference between “the starter works in our repository” and “the published package works for a stranger.”
Isolation is not a security sandbox
The cache root separates ownership and generated state. It does not make project extensions safe to run as untrusted code.
A configured Astro component, remark plugin, or project script intentionally participates in the build and runs with the user’s permissions. Calling the cache a sandbox would promise an isolation boundary it does not provide.
Precise language matters here:
- isolated root means framework machinery has a replaceable home outside the repository;
- sandbox would mean untrusted code cannot affect the surrounding machine.
TidyPress provides the first, not the second.
A smaller repository creates more framework work
Keeping internals out of the project does not remove complexity. It transfers responsibility back to the framework, where it belongs.
TidyPress has to manage aliases, virtual modules, dependency resolution, watched paths, cache invalidation, public-asset merging, cross-platform paths, and the difference between source-workspace and packed-package execution.
A copied starter can delegate these problems to each user. An installed framework cannot.
That trade is only worthwhile if it is tested. The project exercises development and production through the CLI, builds committed examples, packs public packages, and runs clean-install scenarios. The cache root is not an obscure optimization; every ordinary command depends on it.
The deletion test
Ownership boundaries become clearer when expressed as deletion tests:
- Delete the cache: the next command should reconstruct it.
- Delete
build/: the next production build should reconstruct it. - Delete
src/content/: authored work is gone. - Delete
tidypress.config.ts: the publication’s public model is gone. - Remove an owned extension: only the explicit customization should disappear.
If deleting generated state changes the meaning of the project, the framework hid source in the wrong place. If deleting source does not affect the build, the framework may be using stale state.
These tests are more useful than debating whether a directory “looks clean.”
Small is not empty
A tiny repository with all meaningful choices trapped in a remote dashboard is not ownership. A giant repository exposing every internal is not transparency.
The goal is correspondence: files in the repository should map to decisions the project understands and intends to maintain. Everything else should be generated from a versioned package or emitted as a disposable artifact.
That is the footprint TidyPress wants to leave. The framework can remain complex. The user should not have to display that complexity as permanent maintenance work.