8000 2.x by bergie · Pull Request #15 · noflo/noflo-jekyll · GitHub
[go: up one dir, main page]

Skip to content

2.x - #15

Merged
bergie merged 48 commits into
masterfrom
2.x
Oct 17, 2026
Merged

2.x#15
bergie merged 48 commits into
masterfrom
2.x

Conversation

@bergie
@bergie bergie commented Oct 12, 2026
Copy link
Copy Markdown
Member

No description provided.

bergie added 30 commits July 1, 2026 10:25
- PathMetadata, BuildPostPath, and PostCollector as assembly relays
  (Component base, processMessage, msg envelope with error
  accumulation) — the 1.x pending-queue and disconnect-flush machinery
  is replaced by control ports and an explicit flush bang
- PostCollector initializes its config/posts/categories once: the
  control-port hasData stays true, so an unguarded re-init wiped the
  collected categories on every activation
- node:test suite for the three relays; package scaffold with the 2.x
  toolchain, CI and publish workflows; the 1.x Gruntfile and index.js
  entry removed
The setup-node cache: npm step requires a lock file; the library
convention is a committed package-lock.json with only node_modules
ignored.
- DocumentBuilder: template inheritance resolution, include and
  posts-availability checks, and pagination expansion — one message in,
  N page messages out; missing layouts/includes/references fail the
  message with accumulated errors instead of the 1.x retry queue
- The initialized flag was an antipattern: control-port reads are
  non-consuming, so the work branch reads the buffered config on every
  activation, and PostCollector detects a new config IP by reference
  comparison instead of a flag
Reference comparison on the non-consuming control read detects a new
config IP; no flag needed.
The copy broke the identity check; the buffered IP data object is not
mutated by this component, so keeping the reference itself is safe.
Leftover .coffee files in components/ are fatal to 2.x discovery
(uncompiled CoffeeScript crashes registry construction); the
BuildPostPath nodeunit spec's replacement is test/Relays.test.js.
GetConfig and Jekyll specs stay until their replacements land.
- LoadDocument reads a source file into a document message (path,
  source, raw body); read failures route to fail with the path
  message carrying the accumulated error
- FrontMatter splits Jekyll YAML front matter into message fields and
  trims the body; bodies without front matter pass through, invalid
  YAML fails the message
- node:test coverage for both, including the error paths
The 1.x disconnect-flush becomes the 2.x collect-until-bracket-close
pattern: the posts leg arrives as a bracketed stream (ReadDir's
bracketed directory listing forwarded through the pipeline), hasStream
signals the complete stream, and the whole collection is processed in
one activation. The component is now stateless — no accumulator, no
flush bang, no tearDown state — and a second stream produces a fresh
collection.
- graphs/ScanSource: the source tree scan as a subgraph — core/Split
  fans the source path into three filesystem/ReadDir legs (_posts,
  _layouts, _includes), each emitting a bracketed directory stream
- graphs/Build: the full document pipeline — the scan's posts leg is
  bracket-stripped (groups/RemoveGroups) for the per-message wrapped
  components (markdown/ParseMarkdown), runs the relay chain
  (LoadDocument, FrontMatter, PathMetadata, BuildPostPath), is
  re-bracketed (groups/Group) to gate the stateless PostCollector, and
  the flushed config expands (ExpandPosts) into per-post build messages
  for DocumentBuilder → liquid/Template → filesystem/WriteFile
- scripts/generate-graphs.js: the GraphModel-based generator, rerunnable

The 0.x-era 8-graph set is replaced by one coherent document flow; the
0.x graphs died with their constraints (groups-as-metadata, WirePattern
queues, disconnect flushing).
Same rule as the .coffee removal: the loader scans the package's graph
directory, and the 0.x-era graph JSONs (referencing 1.x components and
0.x node names like Filename→document) crash registry construction.
The Pipeline.test.js debug tool's hanging network (noflo/noflo#1048)
held the event loop for minutes; the EndToEnd.test.js now has a clean
assertion set: the static copy verified, the document pipeline's built
output verified, and the after hook force-exits for the streaming
network.
…pha.6

The assembly alpha.6 includes the wrapAssembly Assembly-suffix fix
(the self-reference recursion guard) — the end-to-end build now
completes: the fixture site's post is rendered through the full
pipeline (markdown → liquid → write) and the static files are copied.
…ent pipeline is the known open item (WD 35 update #7)

The streaming network doesn't self-end (noflo/noflo#1048); the test
resolves after a delay and checks the target dir. The static copy leg
is verified; the document pipeline requires the assembly bracket
correlation and the promise-chain bounding.
No ordering is assumed between the wires: documents, templates, and the
site config arrive in any order, only within a stream is there ordering.
A document whose referenced parts have not ar
8000
rived stays buffered on its
inport (the head message is peeked at non-consumingly and consumed only
when the config and template store can resolve it); config and template
arrivals re-fire the process and drain the buffer. Missing parts are
'not yet', never 'never', so the fail-on-missing behavior is gone and
the fail port is for unexpected errors only. The config port had to
become a firing port: a control IIP never fires the process, so a late
config could never trigger the retry.
With the CollectFields has/get-order fix and the order-tolerant
DocumentBuilder the build network drains and emits end on its own —
the noflo/noflo#1048-style 15s forced stop in the networkCallback is
obsolete. The e2e test drops its race-with-timeout shape and asserts
both the static copy and the rendered document directly
test/Jekyll.coffee and test/GetConfig.coffee were the only consumers of
the tango-icon-theme fixture trees (22MB, two full source copies used by
the 1.x CopyFile tests). They survived the earlier 1.x cleanup; the 2.x
e2e builds its fixture site in a temp directory instead
scripts/debug-e2e.js runs the real asPromise path with a Flowtrace and
dumps the activation walk — the tool that pinned the wrapped-subgraph
activation leak. Ignore trace dumps and fixture build output
Carries the CollectFields has/get-order fix: the collector has-checks
the message and every mapped field's stream completeness before any
get, so it no longer opens an activation on partial preconditions —
the root cause of the wrapped-subgraph activation leak that kept the
build network from shutting down
:title expands to the Jekyll slug (the source name without its
extension — a deliberate semantic change from the 1.x name-with-
extension quirk; the e2e expectation moves from hello.md to
hello.html), :month and :day zero-pad, and a trailing-slash permalink
writes index.html into the directory, matching the 2014
Jekyll-generated reference
Converted documents enrich the site collection as they pass: the
matching config.posts entry gains the rendered body as content, and
pages referencing posts or the paginator resolve once every collected
post converted — the 1.x two-pass shape without a second collect and
without timers. Layout-chain front matter merges into the page context
(a layout's class: index reaches page.class). The drain guard is
per-scope and the edge-delivered config port is scoped: false, ready
for per-message scope correlation once network liveness accounts for
buffered IPs (work documents #45 and #35 update #16)
SplitStatic routes site root entries: front-matter files enter the
document pipeline (FrontMatter → builder → shared render tail) so
index.html, the category listing pages, 404.html and index.txt render
through layouts and liquid instead of being copied verbatim; plain
files and directories copy verbatim. CopyStatic walks directory trees
recursively, skipping underscore and dot entries at every level and
dropping trees that contribute nothing (baz/ with only _posts).
FrontMatter's fence matcher accepts empty front matter (--- ---).
Pagination pages write directory-style (page2/index.html) with the
2014 reference's paginator links
bergie added 18 commits October 17, 2026 02:00
The fixture reference is regenerated with Jekyll 3.9 (the 2014-era one
retires to history): non-root HTML pages write directory-style under
the pretty permalink, the root index stays flat, index.txt keeps its
name, and the source config gains the jekyll-paginate plugin that
modern pagination requires. SplitStatic derives the page output paths
from that convention.

The parity test walks both trees: text files compare with blank lines
collapsed (the documented rdiscount-vs-commonmark formatting
difference), binary assets bytewise, and the whole comparison is strict
— the known gap (nested _posts discovery, with the five files whose
output depends on it) is a visible test todo, not a whitelist. The
build runs under a 30s watchdog so a stall fails fast with the
diagnostics dumped
The parity test records the whole build into a Flowtrace and exports it
as a binary trace (.flowtrace/parity.trace, via
@noflo/fbp-protocol) plus the JSON state before asserting, so a stalled
or deviating build always leaves the diagnosable artifact behind. CI
uploads .flowtrace/ on failure. The stall diagnosis then happens from
the trace's activation walk — the last ACTIVATION_BEGIN without a
matching ACTIVATION_END names the hung component — not by
instrumenting the network
The generator's corpus wiring (FrontMatter between the loads and the
builder's template store, the include corpus registering into the
liquid engine) was lost in an earlier revert; without it layouts
arrived with front matter unstripped and the footer include never
registered. The builder's template consumption cannot take forwarded
brackets (getData consumes the buffer head, brackets included), so the
corpus edges run through groups/RemoveGroups. CopyStatic's tree walk
copies binaries as buffers — utf8 round-trips corrupted the PNGs
The wrapped adapters' collector cross-pairing is fixed with unique
scopes: ExpandPosts stamps each post (its path), the builder stamps
each generated page. On the closedown build (noflo/noflo#1092) the
network stays live while a scoped message waits buffered, and the
per-scope pairing is immune to async inner completion order. CopyStatic
no longer removes directories: the empty-tree rm raced the document
pipeline's writes into the same path (baz/ holds both the ignored
_posts tree and the rendered baz page), deleting rendered output
The activation-contract fixes from the noflo/noflo#1092 review: the
scan, the static split, the copier, and the document loader returned
void and ran their asynchronous work fire-and-forget — the network
looked quiescent while packets were still parking in edges, and the
corpus emerged into the ended network. The async work now returns its
promise, holding the activation until the sends complete in-activation.

DocumentBuilder: documents consumed on arrival into a per-scope state
store — waiting in the port buffer stranded them behind their scope,
because the config and template re-fires are unscoped and a scopeless
activation cannot see scoped buffers. Every activation drains every
scope's queue head-first; the enrichment runs at consumption time
…arker

The includes corpus leg delivered its bracket framing into the
Template's includes control port, where the framing IPs strand — the
component-level closedown's buffered count never drains and the liquid
wrapper cascade stops (the network hangs with every output written).
The corpus now runs through the same RemoveGroups as the builder leg.
The absent-corpus case (a site without _includes) emits a null marker
through core/Kick on the ReadDir error, completing the render gate's
firing pattern: template + variables + a completed or absent corpus
The released closing machinery and the activation-contract guard
… and the alpha.8 dependency set

The includes corpus marker (core/Kick emitting the null corpus on the
ReadDir error) reaches the liquid adapter's includes port directly,
bypassing the document loader that would reject it; the corpus leg's
bracket framing is consumed by the declarable framing termination
(upstream eeba008e) instead of the RemoveGroups workaround. The
dependency set: the #1093 builds of noflo, graph, assembly, and
assembly-utils (the closedown hardening, the activation-contract guard,
the leak warning, the stranded-framing diagnostics)
core/Merge is an all-inputs barrier: it waits for every attached input,
and the ReadDir error path delivers nothing on the listing edge — the
null-corpus marker from core/Kick stranded behind the barrier. The
listing and the marker fan into their consumers directly
The monorepo packages at the released alpha.9 (the closing machinery,
the activation-contract guard, the leak warning, the stranded-framing
diagnostics) and liquid at alpha.3 (the includes scoped:false). The
parity test's strict comparison covers everything implemented; the
first post in collection order drops under the scoped drain in
DocumentBuilder — a visible todo until the state-store drain-ordering
edge is resolved, with the flowtrace artifacts on every CI failure
The monorepo packages at alpha.9 (the closing machinery, the
activation-contract guard, the leak warning, the stranded-framing
diagnostics, the declarable framing termination, the subgraph lifecycle
recording) and liquid at alpha.3 (the includes scoped:false, the
manifest shipping). The suite: 20 pass, 0 fail, 2 todos — the strict
parity comparison against the 3.9-generated Jekyll reference passes,
with the nested-posts and scoped-drain gaps as visible todos
…ce drops

The .flowtrace/ directory is hidden (dot-prefixed) and
upload-artifact@v4 excludes hidden files by default — the artifact
upload found no files. include-hidden-files: true fixes the upload.

The parity test's gap set: the three 2012 posts drop under CI timing
when the corpus leg loses the race to the posts leg — the same
scoped-drain interaction as the collection-order post, just resolved
differently on the faster runner. The strict comparison covers the
pages and static assets; the todos track the platform gaps
The 2.0.0-alpha.10 platform set carries the control-port gate semantics
(noflo/noflo#1094) and the one-firing-rule retirement of the triggering
property (work document #47). With the gate semantics, the corpus-race
strand that dropped the collection-order post — and, under CI timing,
the remaining posts — is gone: repeated fixture builds produce one
output tree (verified plain and under Flowtrace recording), and the
four root posts join the strict Jekyll-reference comparison again.

The parity gap set now covers only the unimplemented features: the
nested _posts discovery and the nested front-matter pages. The four
posts' todo converts into the strict comparison.

DocumentBuilder's config port keeps its firing-port declaration, now
with the reasoning recorded: the drain's work lives in the per-scope
state store, which the gate machinery cannot see; the port reverts to
control when the builder returns to buffer-waiting under the
one-firing-rule pump (work document #35 update #23).
…tion

The nested discovery joins every root entry with /_posts via
strings/Append — its standing suffix control keeps the value readable
across the whole entry stream, replacing the JoinPath join whose file
port consumed the IIP on the first firing and stranded the rest of the
listing. The discovered directory's listing fans into the posts leg
alongside the root listing.

Two source groups on one connection broke the collector's single-flush
semantics: one source group completing is not the collection being
complete, and flushing on the first group baked an incomplete
collection into every listing and pagination page. The new
jekyll/StreamEnvelope terminates the per-listing framing and emits one
outer bracket frame that closes when the posts leg reaches EOT — the
completion moment arrives as a bracket, which fires the collector under
the one-firing rule. This is the component-level form of the
EOT-to-packet terminator convention (work documents #40 update #10 and
#47 §8): EOT itself does not fire a process, so the completion signal
rides as a bracket. The activation contract is the holder pattern: one
activation consumes and forwards everything until EOT, later arrivals
decline at the has-level and leave their packets for the holder, so the
frame close can never race a forward.

With the complete collection the nested post builds byte-identically
with its path-derived category (PathMetadata's source control is now
fed as a graph IIP and declared scoped: false), and every listing and
pagination page joins the strict comparison. The parity gap set shrinks
to the nested front-matter pages (tango/index.md — a front-matter page
inside a subdirectory still routes through the static-copy leg
verbatim).

Two contract fixes the comparison surfaced, both against the
Jekyll-generated reference: the layout chain contributes the
{{ content }} substitution only (a layout's front matter is the
layout's variables, not page variables — the nocontent page renders a
bare <body>), and the paginator's previous_page_path for page 2 is the
document's own URL (the site root's index.html maps to /), not
/index.html.
@bergie
bergie marked this pull request as ready for review October 17, 2026 22:38
@bergie
bergie merged commit 2de4bba into master Oct 17, 2026
2 checks passed
@bergie
bergie deleted the 2.x branch October 17, 2026 22:38
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

0