Skip to content

Deepen the build, index, config and watch modules - #92

Merged
LDGerrits merged 8 commits into
refactorfrom
architecture-deepening
Oct 4, 2026
Merged

LDGerrits merged 8 commits into
refactorfrom
architecture-deepening

Conversation

@LDGerrits

Copy link
Copy Markdown
Owner

Six changes from an architecture review, each in its own commit.

Bugs fixed

  • Excluded files inside an init folder. where reported a file excluded inside an init folder as placed, and the build let Rojo sync one excluded in a subfolder of an init folder. The root scan now decides an init folder's members once, applying exclude at every level. A new e2e case (structure/exclude-inside-init-folder) checks it with real Rojo.
  • Links added during a watch. A link to its own parent, or to nothing, added during a watch was indexed as a directory with no warning, so the watch built a different tree from a fresh build. An index update now reads each added entry's type from disk and applies the scan's link rule. DiskWatcher reports a link it won't follow, and its removal.

Refactors

  • Build: Placement records the files the template displaced and no longer carries the index. ConfigBuilder builds one config and is what run and locate share; its BuiltProject carries the placement. OutputWriter stages the write. CoreBuildService keeps only its interface.
  • Config: ConfigService.initialize returns a ConfigSelection (entries, unselected files, requireValid(), brokenError), and requireBuildable takes it. getBrokenError, listConfigFiles and listUnselectedConfigFiles leave the interface.
  • Watch: RebuildReport is a ConfigBuild with its entry and whether the round checked the sync dir. BuildLog.outcome is the one place that turns an outcome into lines, and build and watch both call it.
  • Watch tests: the session's behavior is tested in the session's tests. The command test no longer patches watchService.watch.

Visible changes

  • A failed text build now prints <out file> · not written before its errors, as watch does. Two e2e transcripts change by that one line.
  • The summary's excluded count includes files excluded inside init folders.

Not changed on purpose

  • The no-routes check stays a build error. It is not a config error, because spec §4.1/§4.7 make building a config without routes the error, and a test pins that such a config still loads. As a result, rogen list still shows a routeless config as healthy.

Testing

  • npm test: 2070 passed, including the e2e cases against real Rojo.
  • npm run lint and tsc --noEmit are clean.

🤖 Generated with Claude Code

LDGerrits and others added 6 commits October 4, 2026 01:56
A link to its own parent, or to nothing, added during a watch was indexed as
a directory, or never reported, so the watch built a tree a fresh build
wouldn't and dropped the unresolved-link warning.

applyChanges now reads an added entry's type from the disk and classifies it
by the scan's rule, indexes an added directory whole, skips what is gone, and
queues behind a scan in progress. DiskWatcher reports a link it won't follow,
and its removal, instead of hiding it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The root scan now decides an init folder's members once, applying `exclude`
at every level inside it. A file excluded inside an init folder is excluded
in `where` (it was reported as placed) and in the build (a file excluded in
a subfolder of one reached Rojo). FileLocator reads the members instead of
walking the index itself.

Placement records the files the template displaced, so FolderMetaApplier
and the template-clash rule stop joining routed files with leftOut, and it
no longer carries the index.

ConfigBuilder builds one config in memory and is what run and locate share;
its BuiltProject carries the Placement, so a test places and builds once.
OutputWriter owns the staged write. CoreBuildService keeps only its
interface: the set checks, run and locate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… about

Every command that reads configs ran initialize, then its own validity
gate, then asked for the unselected config files in a second call. Now
initialize returns a ConfigSelection: its entries, the config files it
didn't pick, requireValid() and brokenError. requireBuildable takes the
selection, so build, watch, where and list all start the same way.

getBrokenError, listConfigFiles and listUnselectedConfigFiles leave the
ConfigService interface, and requireValidEntries becomes the selection's
method.

The no-routes check stays with the build: the spec makes building a config
without routes the error, not the config itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…resenter prints it

The watch session translated each ConfigBuild into a RebuildReport with its
own field names, threw on notWritten, and WatchLog translated it back into
BuildLog calls; text build made the same choice its own way and printed a
failed config's warnings with no line naming its out file.

RebuildReport is now a ConfigBuild with its entry and whether the round
checked the sync dir. BuildLog.outcome holds the only switch from outcome
to lines, and build and watch both call it, so text build now says
"<out file> · not written" for a config it didn't write, as watch does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ithout patching it

The watch command's tests overwrote watchService.watch to subscribe to the
session's updates, and most of them exercised the session: what it watches,
source changes, linked directories and hot reload. Those groups now live in
the session's tests, against the same in-memory watcher and file system.
The command's tests keep startup, output, shutdown and flags, and assert
only what the command shows or writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
BuildLog.outcome takes the config entry and the build, which already names
its config, instead of the config twice. The index joins paths with
joinPosix and names the flag that tolerates a replaced directory. A test
comment goes, and router.ts loses a formatting-only change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
rogen Ready Ready Preview Oct 4, 2026 12:30am UTC

LDGerrits and others added 2 commits October 4, 2026 02:27
…n WatchLog

failedBuild moves into the build contract, so CoreBuildService and the watch
session's blocked rebuild make a failed ConfigBuild the same way.

BuildLog.outcome takes an optional note for the end of the line instead of
a repeated flag. WatchLog, which keeps the history of what it printed, says
"same errors as before" itself; the output is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The warnings and sync warnings travel together, and two positional arrays
of one type could swap without a compile error. A BuiltProject already has
both, so the write-failure path passes it as is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@LDGerrits
LDGerrits merged commit 1ab8658 into refactor Oct 4, 2026
3 checks passed
@LDGerrits
LDGerrits deleted the architecture-deepening branch October 4, 2026 00:32

This branch was successfully deployed

1 active deployment
Preview — d2e98b5b Deployed Oct 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant