ma
marcoroth
GitHub
herb
Workspace
GitHub
CI Pipeline Executions
Runs
Demo
Insights
Compare tasks
Analytics
Sign in
Toggle sidebar
Overview
⌘K
herb
Loading workspace stats
Loading workspace insights...
Statistics interval
7 days
30 days
Latest CI Pipeline Executions
Status
Fix filter
Filter
Fuzzy
Filter range
Sort by
Sort by
Start time
Sort ascending
Sort descending
Succeeded
main
d36e19fb Engine: Introduce `SlotSubtree` to render the markup a slot covers (#2283) This pull request introduces `Herb::Engine::SlotSubtree`, which compiles the markup behind one slot, for the two cases a payload of values cannot carry. `SlotIndex#apply` reports a deferred entry when the page is asked for something values cannot express, which is a conditional taking a branch that never rendered, and a collection gaining an item there is no row to copy. Both name a slot, and nothing on the server answered one. ```ruby subtree = Herb::Engine::SlotSubtree.new(source, filename: "app/views/posts/index.html.erb") view.instance_eval(subtree.source_for(0)) #=> "<li>one</li><li>two</li>" ``` `SubtreeCompiler` renders the node at a `node_path` and `SlotVisitor` records the `node_path` of every slot, so a slot index is all that is needed to reach its markup. The two agreeing on what a `node_path` means is what makes this a lookup. #### An attribute is refused A path indexes an element's body and does not descend into its open tag, so an attribute slot's path names the element holding it. Compiling that would hand back `<div class="card">x</div>` where the caller asked for `card`. Those slots are values, and values is how they come back. #### Asking for only what changed `subtree_slots` says which slots a change needs markup for, so a request naming what changed can be answered without asking the page. ```ruby dependencies.subtree_slots("app/views/posts/index.html.erb", ["@admin"]) #=> [{ file: ".../index.html.erb", version: "a1b2c3d4", index: 0, mode: :structural }] ``` Only structural slots appear, because that is the whole of what values cannot say. Changing something a branch merely displays asks for nothing. #### What this does not do It does not make the render cheaper. `SubtreeCompiler` runs the whole template and keeps one node's output, which its own documentation says is the answer that is correct without knowing which expressions the target depends on: > Pruning the work that only fed discarded output is a separate question, and answering it needs to know which expressions the target actually depends on. Running everything is the answer that is correct without that analysis. That analysis now exists, so the question can be asked. It is not asked here, because answering it means changing what the compiler emits and taking on the side effects that come with skipping work.
by Marco Roth
M
Succeeded
slots/state
e0093a65 Client: Add `state` to set a page's state and write what it can
by Marco Roth
M
Succeeded
slots/subtrees
fb2407e7 Engine: Introduce `SlotSubtree` to render the markup a slot covers
by Marco Roth
M
Succeeded
slots/propagation
3b34301e Analysis: Carry a page's state into the partials that render it
by Marco Roth
M
Succeeded
slots/subtrees
fb2407e7 Engine: Introduce `SlotSubtree` to render the markup a slot covers
by Marco Roth
M
Succeeded
slots/state
e0093a65 Client: Add `state` to set a page's state and write what it can
by Marco Roth
M
Succeeded
slots/subscriptions
53cf748e Analysis: Introduce `SlotDependencies` to say which state a slot reads
by Marco Roth
M
Succeeded
slots/subscriptions
53cf748e Analysis: Introduce `SlotDependencies` to say which state a slot reads
by Marco Roth
M
Succeeded
slots/state
d4c486ec Client: Add `state` to set a page's state and write what it can
by Marco Roth
M
Succeeded
slots/state
d4c486ec Client: Add `state` to set a page's state and write what it can
by Marco Roth
M
Succeeded
slots/propagation
60e3e905 Analysis: Carry a page's state into the partials that render it
by Marco Roth
M
Succeeded
slots/node-path
4204b91b Analysis: Number a node the way `SlotVisitor` and `SubtreeCompiler` do
by Marco Roth
M
Succeeded
slots/subtrees
473ccfeb Engine: Introduce `SlotSubtree` to render the markup a slot covers
by Marco Roth
M
Succeeded
slots/subtrees
473ccfeb Engine: Introduce `SlotSubtree` to render the markup a slot covers
by Marco Roth
M
Succeeded
slots/state
d2debe0a Client: Let a page set state, and write what it can before the server answers
by Marco Roth
M
Succeeded
slots/node-path
b22233c4 Analysis: Number a node the same way in all three languages The Ruby collector walked an element's `child_nodes`, which carries the open and close tag, so every element level shifted a path by one. The other two ports had their own version of the same thing. Rust walked the right children and then added the offset back, reproducing the shifted numbering deliberately (`child_index_offset`). Removing it makes the paths it prints from `herb actionview flow` the ones `SlotVisitor` and `SubtreeCompiler` mean. TypeScript walked `childNodes()` for every node type, so it had the shift and also numbered a conditional's and a block's children by a list that includes their delimiters. It now indexes an element's body, a conditional's statements and a block's body, and reports an attribute against the element carrying it, because a path does not descend into an open tag. TypeScript also never recorded a block at all, so a state that a loop iterates was reported nowhere. It is recorded the way Ruby records it. None of the three had a test that would have noticed. Ruby's was named for the agreement and only ever snapshotted one side of it, TypeScript asserted that a path was a non-empty list of integers, and Rust asserted nothing. All three now assert the same five paths, and each was confirmed to fail without its fix.
by Marco Roth
M
Succeeded
slots/propagation
6701ea62 Analysis: Leave a collection's item template to the server A page names its state once. A template rendered for each of a collection's items renders once per item, so that name says nothing about which of them is meant, and a client writing the slot from the page would put the same value into every one of them. `render partial:, collection: @posts` reported the item template's slots as an identity, which is the one reading that is unsafe. Crossing a collection now downgrades an identity to derived, for that template and everything it renders in turn, so those slots are answered by the server that knows which item each one is. Rendering a partial inside an `each` block is the other shape this takes, and it needs nothing here: the trace does not follow a block parameter across a render call, so the item template is never reached and its slots subscribe to nothing. That is a missing subscription rather than a wrong write, and a slot nobody wrote asks the server like any other. Matching a render call to the template it reached is by name, so two partials that share one under different directories both count as a collection's. That errs towards the server, which is the direction that cannot be wrong.
by Marco Roth
M
Succeeded
slots/subscriptions
0456e4ee Analysis: End an instance variable on a word boundary in all three languages Matching `@post` by substring answers for `@posts` too, so a state change woke nodes that never read it. Ruby was fixed with the name it reads; the two ports carried the same bug and are fixed the same way. An instance variable already begins on a boundary, since a name cannot run into the sigil, so only the end of it needs checking. Rust routes the sigil through the same scan it already used for a bare name and skips the leading test, and TypeScript anchors the pattern at the end alone. Both were confirmed to report `@posts.count` for `@post` before the change.
by Marco Roth
M
Succeeded
slots/subtrees
397ad169 Engine: Answer the two things a payload of values cannot carry `apply` reports a deferred entry when the page is asked for something values cannot express: a conditional taking a branch that never rendered, and a collection gaining an item there is no row to copy. Both name a slot, and nothing on the server answered one. `SubtreeCompiler` renders the node at a `node_path` and `SlotVisitor` records the `node_path` of every slot, so a slot index is all that is needed to reach its markup. `SlotSubtree` resolves one to the other, which the two agreeing on what a `node_path` means now makes possible. An attribute is refused. A path indexes an element's body and does not descend into its open tag, so an attribute slot's path names the element holding it, and compiling that would return the whole element where the caller asked for one attribute. Those slots are values, and values is how they come back. `subtree_slots` says which slots a change needs markup for, so a request naming what changed can be answered without asking the page. Only structural slots appear, because that is the whole of what values cannot say. Changing something a branch merely displays asks for nothing. What this does not do is make the render cheaper. `SubtreeCompiler` runs the whole template and keeps one node's output, which is what its own doc says is correct without knowing which expressions the target depends on. That analysis now exists, so skipping the work behind discarded output is a question that can be asked, and it is not asked here.
by Marco Roth
M
Succeeded
main
d7c8a741 Client: Rename a collection's "row" to an "item" and fold content anchor (#2278) This pull request settles the vocabulary of the slot marker format on one set of words, and reduces the two attributes that anchor a slot to an element down to one. Nothing outside this stack reads the format yet, which is the only reason this is cheap. Every one of these changes is breaking, and each gets more expensive the longer it waits. `row` is table vocabulary, and a collection repeats `<li>`, cards and `<option>` as readily as it repeats `<tr>`. The slot type beside it is already `collection`, and a collection has items, which is also what Rails calls them when `render collection:` renders one partial per item. ```html <!--herb-item:0:42--><li data-herb-slot="1:child">Marco</li><!--/herb-item:0--> ``` ```ruby { 0 => { items: { "42" => { 1 => "Marco" } } } } ``` ```typescript slots.itemsFor(file, 0) slots.slotInItem(file, 0, "42", 2) slots.updateItem(collection, "42", html) ``` Also, an element's content slot had an attribute of its own while every other slot on that element was in `data-herb-slot`, and the index called what the first one produced a `content` anchor, so the format and the index disagreed about its name. The anchor kind is a property of the slot's type and does not need an attribute to carry it. `child` means the value goes inside the element, and every other type on that attribute means the element itself: ```diff -<li id="1" data-herb-slot="1:attribute:id" data-herb-child="2">Marco</li> +<li id="1" data-herb-slot="1:attribute:id 2:child">Marco</li> ``` The list is space-separated now, which is what HTML uses for token lists, and it means a slot can be found with a selector instead of by parsing the attribute: ```javascript document.querySelectorAll('[data-herb-slot~="2:child"]') ``` `~=` matches one token of a whitespace-separated list, so the comma form could not be queried this way. A content-only element grows three characters, an element carrying both roles shrinks by a whole attribute. Some values of `SlotOperation` named what kind of thing changed and two named what happened to one item, and rewriting an item's markup fell through to `markup` with a key set: ```diff -{ operation: "markup", key: "42" } +{ operation: "item-updated", key: "42" } ``` `markup` keeps its meaning for a whole slot rewritten at once, which is the case that carries no key. Nothing read the operation except the Dev Tools flash, which looks it up in a colour map, so this costs one entry there. No test covered that path before, and one does now. Slots are experimental and the marker format is not stable yet.
by Marco Roth
M
Previous
Next