Repository navigation
Conversation
The scaffold from May had five files in it, two of them empty. This is the graph. What it replaces is github:quartz-community/graph, which draws a good picture and fetches d3 and pixi.js from jsDelivr to do it: about a megabyte and a half of somebody else's JavaScript, from a third party, on every page view, before a single node appears. Its script opens with `@ts-nocheck`, so none of it is checked by anything. The reader's IP reaches a CDN whether or not they ever look at the sidebar. This is 54 KB of compiled Elm, 19 KB over the wire, served from here and fetched only on a page that has a graph on it. It draws SVG, so the browser does the compositing and the stylesheet does the colour -- `is-current`, `is-tag`, `is-visited`, `is-hovered`, `is-dim` -- where the old one read six custom properties out of getComputedStyle at startup and had to be told to rebuild itself when the theme changed. The forces are d3-force's on purpose, so the graph settles into the shape it used to: the same charge, link, centring and collision, the same alpha decay. Two things are not d3's. Nothing is random -- positions come from the phyllotaxis spiral d3 keeps as a fallback, so a page draws the same graph every time it is opened -- and the first layout is run before anything is drawn. A simulation starting from its seed positions spends its first second looking like an explosion, and a graph in a background tab is handed no animation frames at all, so without it the sidebar would sit there as a spiral. That runs against a budget of node pairs rather than a fixed number of steps: a step compares every node with every other, so the whole-site graph's 94 nodes cost 4,000 times what the sidebar's three do. Opening it blocked the page for 376 ms before the budget and 52 ms after it, and the sidebar still settles completely in 4 ms. prefers-reduced-motion gets the layout finished and left still. Elm owns the graph and nothing else; four ports carry what belongs to the page. `follow` hands a clicked node's id back so Quartz's own router does the navigating -- a node is an SVG `<a href>` for focus and for the accessibility tree, but the router reads `href` off the anchor a click came from and on an SVG anchor that is an SVGAnimatedString rather than a string, so the anchor carries data-router-ignore and the id goes out through the port instead. `halt` exists because a soft navigation patches the sidebar back to the empty container the incoming page has, taking the drawing with it and leaving the app rendering into nothing; it is stopped and another is started around the page the reader arrived at. Drag and zoom are not ported yet. `scale` is gone for good: it was a pixi camera setting with nothing to configure in an SVG. The build gets `site elm`, which is `elm make` and then esbuild -- Elm's own guide is explicit that --optimize does no minifying and that the output is meant to be handed to a minifier, and the compiler guarantees there is no eval or reflection in it to be broken by one. `site wasm` calls it, since content/js is the directory it empties and fills. The one thing that needed care is that Elm ends its output by calling itself with `this`: fine in a script tag, undefined in a module, and esbuild reads it as a module and folds it to `}(void 0));`, which dies reading Elm of undefined. It is repointed at globalThis, with a test that fails loudly if a future compiler stops emitting that tail rather than skipping it. elm is in the flake's dev shell now, which means CI compiles it and so needs package.elm-lang.org to be reachable the first time.
The port had the picture and none of the handling. This is the rest of what the old graph did with a mouse, and the tests that say it is right. Dragging a node pins it -- the forces keep pushing at it, it does not move, and the rest of the graph rearranges itself around where it has been put. d3 throws a pinned node's velocity away every tick and so does this, so letting go of one after hauling it across the box leaves it there rather than firing it off with everything the forces wanted to do to it in the meantime. A node is also a link, and the click that ends a drag is the same click that follows one, so a drag remembers whether the mouse travelled more than three pixels and the click after one that did is swallowed. Dragging the background pans, and the wheel zooms about the pointer, between a quarter and four times, on d3's own two-to-the-delta-over-500. Both are the viewBox and nothing else: no transform on the nodes, no second coordinate system to keep in step, and a stylesheet still measuring strokes and font sizes in the units it was written in. Labels fade in above life size, which is the old graph's arithmetic -- fifty labels at once is not reading. Two things had to cross the boundary for any of it. A mouse event says where it happened in the window, and an Elm event decoder can read properties but cannot call getBoundingClientRect, so `resized` now carries the box's corner as well as its size and the page re-sends it while the reader scrolls; without that, zooming a graph somebody had scrolled to would zoom about the wrong point. And the background needed something to catch a drag that misses a node, which is a rect the size of the view, painted in transparent because fill:none is not hit-tested. The tests are the point of the commit as much as the features are. Forty nine of them, run by `site elm-test` and by `site check`: that a link settles at about the length it asked for, that the neighbourhood walks a link in both directions, that a tag is drawn as a tag, that a drag carries a node by exactly the distance the mouse moved and half that at twice the zoom, that the wheel keeps what is under the pointer under it, that the zoom stops at both ends, that the click ending a drag is not a link being followed -- and that it is swallowed once rather than for ever. Inverting one sign in the zoom fails four of them, which is the check that they bite. None of this could be seen from here: an Elm view redraws on an animation frame, and a browser pane that is not displayed never gets one. The tests are what stands in for the eye, and Main is exposed wholesale so they can read it -- an application is built from `main` outwards whatever the module line says, so nothing extra is compiled into the page for it. elm-test comes from the flake in CI and from `bun x` at a pinned 0.19.1-revision12 elsewhere, because plain `latest` is 0.19.2-1, written for a compiler that is not released: it emits an elm.json asking for Elm 0.19.2 and then refuses the 0.19.1 that answers.
Two things from watching it in use. The whole-site graph was behind a button in the corner of the box, which is easy to miss and easy to mis-click. A click on the graph's own background opens it now -- down and up in the same place, since dragging the background is how the picture is panned and that must not open anything. Elm says only that a click happened, through a port; the page decides what it means, which is how the big graph's own background can click without asking to be opened again. And the big graph had a background of its own, so it hid the page it is a graph of. It has none now: the dimming behind it is the whole effect, the article goes on showing through it the way the sidebars already did, and the thing reads as this site's graph rather than a window laid over the top of it. The drag threshold now covers a held node too, not just a panned background. Pressing a trackpad moves the pointer a pixel or two on the way down, and every one of those was a drag rather than a click, so a node would refuse to follow its own link about half the time. Three pixels of travel is the same allowance panning already had. Fifty three tests now, including that a two-pixel twitch still follows the link and that a click on the background asks to expand while the end of a pan does not.
origin/elm held one commit adding an empty scaffold: component.ts, index.ts and Main.elm were all zero bytes. The branch grew its own implementation from the same base instead, so elm.json and package.json conflicted add/add and are resolved to the branch's versions, and the empty component.ts is dropped in favour of components.ts. The merged tree is identical to the branch before it.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.