13. Drive it in the browser

The same catalog, the same verdicts, against the drawing instead of a terminal.

Every rung so far ran at the command line, which is right for CI and wrong for the part of review where somebody points at a net and asks what is wrong with it. agni serve puts the same engine behind a browser: same rules, same tiers, same verdicts, rendered against the drawing.

Nothing new is computed here. That is the point. If the panel disagreed with the CLI, one of them would be lying.

Serve the project

agni serve --addr :8090 --mount proj=. --review-store ./reviews

Run it from the project root. That is the whole command, and the short flag list is the lesson rather than an omission.

serving web at http://localhost:8090/ with 1 mount(s) (Ctrl-C to stop)
  on this network: http://192.168.1.23:8090/ (all interfaces, no auth)

That second line of the startup output is the one to read twice. --addr :8090 binds every interface, so anyone who can reach your machine can reach the server, and it has no authentication: whatever you mounted is readable by them. That is usually what you want on a workbench and rarely what you want on shared Wi-Fi. --addr 127.0.0.1:8090 binds this machine only, and the line disappears when it applies to nobody.

Outside a checkout, add --web-dir (or set web_dir in an agni.yaml) so the server can find the viewer’s own assets; Running the server covers it.

--mount name=path exposes a folder in the file browser, and it is repeatable, so a real deployment mounts several project folders at once. Point it at the project root, not at designs/gateway/. A mount rooted inside the design puts project.yaml and review.yaml above the mount, where the server cannot reach them, and the Review panel then has no checklist to offer.

--review-store is the only other flag, and it is the one genuinely new thing here: somewhere to keep review runs. Everything else this rung needs, the server discovers.

The tiers arrive on their own

Rungs 4 through 7 each added a tier, and none of them is a flag here. A project descriptor names its own layout, so FSStore composes conventions.yaml, profiles/, params/ and review.yaml from the project root, and each design’s intent.yaml and symbols/ from beside the design.

Passing a flag for one of them does not switch it on, because it is already on. It loads the tier a second time. Rung 4’s run shows the harmless version of that, where --conventions names the file the project already composed and the output does not change; agni check now refuses the profile case outright rather than reporting every profile finding twice.

That the tier reaches the design is worth confirming rather than assuming, which is what the Rules panel below is for.

Open http://localhost:8090/, pick the mount, choose a design, and open it.

What the checks say

Run the checks in the panel and you get the findings the ladder has been building up, most of them built-in rules from rung 2. These are the six that carry a tier’s namespace, and the panel and the command line report them identically:

$ agni check designs/gateway/gateway.edn
  [warning] gateway-profiles/can-esd-missing: CAN1_CANH (CAN signal net CAN1_CANH is exposed on a connector with no ESD protection in reach)
  [warning] gateway-profiles/can-esd-missing: CAN1_CANL (CAN signal net CAN1_CANL is exposed on a connector with no ESD protection in reach)
  [error] gateway-profiles/can-host-incomplete: U4 (CAN host U4 declares the interface but is missing required signal STB)
  [warning] gateway/signal-net-naming: XTAL_IN (net name matches no allowed naming pattern)
  [warning] gateway/signal-net-naming: XTAL_OUT (net name matches no allowed naming pattern)
  [warning] intent/voltage-domain-mismatch: PMIC_IO_1V8 (rail "PMIC_IO_1V8" is declared in voltage domain "core" (3.3V) but its name declares 1.8V)

Read the rule column. gateway/ is your conventions file. gateway-profiles/ is your CAN profile superseding the built-in. intent/ is your architecture declaration. Every tier you added is present, namespaced exactly as it is at the command line, because it is the same catalog.

The namespace is worth a second look, because it records how the tier arrived. A project composes its profiles under the project’s own name, so these read gateway-profiles/. An overlay passed with --profile-path composes under the fixed name profile-overlay/ instead. Same file, same rules, different label, and the label is how you tell which route a run took.

The failure this read cannot have

Rung 1 spent its length on the unresolved-symbol failure, where the parts load, the pins do not, and the checks report a board in ruins that is really a bad read. It is the most expensive mistake on the ladder, so it is fair to ask what it looks like here.

It does not look like anything here, and the reason is worth knowing. This rung serves the EDIF netlist, and EDIF declares its own pins:

$ agni query designs/gateway/gateway.edn 'pin.net(?r,?p,?n) => count(?p)'
count(p)  provenance
56        designs/gateway/gateway.edn

Fifty-six pins, with no symbol path anywhere. --symbol-path points at a directory of .sym files, which an .edn never references, so there is nothing for the flag to do and nothing that removing it can break. A netlist format that carries its own pins cannot suffer the failure at all.

The formats that can are the ones whose symbols live in a separate library: gateway.kicad_sch here, and xschem and gEDA schematics generally. This project ships both views of the board, so the failure is one file away.

Serve it wrong, on a design that can go wrong

Point the same project at the schematic, with its symbol library moved aside, and it is available again. That is rung 1’s recipe, scored this time by the review layer:

$ mv designs/gateway/symbols designs/gateway/symbols-off
$ agni review designs/gateway/gateway.kicad_sch --as-named
**2 pass, 1 fail, 3 n/a, 1 not-automated, 8 inconclusive (of 15)**
| P1 | every rail carries a bulk capacitor | inconclusive | the check ran but could not decide for gateway:CONN4, gateway:TVS, gateway:BUCK, gateway:LDO, gateway:MCU, gateway:CANXCVR, gateway:EEPROM, gateway:CAP, gateway:RES, gateway:XTAL, gateway:TESTPOINT |

Eight of the fifteen items are inconclusive, and that is the outcome worth knowing. The rules ran. They had the design. They could not reach a verdict, because the pins they needed were never resolved, and rather than pass, fail, or stay silent, each one says so and names the parts it could not resolve.

Compare that to the plain catalog on the same broken read, where rung 1 counted a hundred and fourteen confident and entirely wrong findings. The difference is not that the tool got cleverer between the two rungs. It is that rung 9’s vocabulary has a word for “I looked and I cannot tell” and a bare finding list does not.

Serve that design and the panel shows the same eight, styled apart from the passes, which is the whole reason the review layer is worth the extra tier.

What the panels are for

A map of the viewer window. A file tree runs down the left, the canvas fills the middle and carries a per-sheet finding badge, and four panels stack down the right: Findings, Rules, Compare and Review. Each panel is annotated with the rung of the tutorial it comes from, because every one of them is a tier built earlier at the command line. localhost:8090 the tree mounts and designs Canvas faithful when the design carries geometry, a computed layout when it does not, rung 3 sheet 1 · 3 the finding count per sheet Findings Rules Compare Review select a row to light the net, rung 2 the composed catalog, rung 5 the revision diff, rung 10 your checklist, scored, rung 8 every panel is a tier you built at the command line, namespaced exactly as it is there the same catalog and the same verdicts, so neither surface is a second opinion on the other

The sheet badge carries the finding count, so a multi-sheet design shows you where the problems are before you open anything.

Findings is the table above. Selecting a row highlights its subject on the canvas, doing the one thing a terminal cannot: going from “net CAN1_CANH has no ESD protection” to seeing where that net actually runs.

Canvas renders faithfully when the design carries geometry and computes a layout when it does not, exactly as rung 3 described. The WebGL and SVG toggle matters on large boards.

Rules lists the composed catalog, which is how you confirm a tier actually loaded rather than inferring it from findings that did or did not appear. Select the design first: with no design chosen the panel lists the server’s own catalog, and a project’s tiers compose per design, so gateway/, gateway-profiles/ and intent/ appear only once the panel knows which design it is listing for.

This is also where supersession from rung 5 is visible, and it shows up as arithmetic rather than as a message. Selecting the design moves the built-in profile/ count down by five and adds five gateway-profiles/ rules, because the house CAN profile replaces the built-in one wholesale. The CLI prints a supersedes note when an overlay is composed at the command line; a project that composes its own profiles prints nothing, so the count is the thing to read.

Compare is rung 10’s diff with a revision picker.

Review is your checklist from rung 8, scored in the browser. It needs no second server: --review-store was on the command at the top, and review.yaml sits at the project root, which is inside the mount.

Pick your review.yaml and press Run review. What comes back is the same verdict rung 9 read in the terminal, item by item, with the same vocabulary: an item that could not be evaluated is styled differently from one that passed, because the two mean opposite things. The headline leads with coverage rather than pass/fail, for the reason rung 9 gave about what a bare pass count hides.

One item does disagree with the command line today, and it is the exception to this page’s opening claim rather than a refinement of it. B1, the fab’s minimum track width, is a board question. agni review reads it fail, because naming the design attaches the gateway.kicad_pcb the descriptor declares as a companion. The panel reads it not-applicable, because the viewer scores the entry netlist alone and never attaches that board. Same design, same checklist, two outcomes.

Read a not-applicable board item in the panel as “not measured here”, and confirm it at the command line. The rule is not in dispute, and any board-tier item reads the same way. It is tracked in agni issue 646.

A failing item lists the findings that failed it, and clicking one highlights it on the canvas, which is the same move Findings offers one level down.

Runs are kept, so the panel opens on the latest one and the picker holds the history. That is the browser half of rung 11: comparing this week’s verdict against last month’s, without either of them being a file somebody had to remember to save. Each stored run also carries the checklist it actually scored, so a run from before you edited review.yaml still shows the questions it really asked.

Where this fits

The CLI is for the gate. It runs in CI, returns an exit code, and writes the archive.

The browser is for the conversation. It is what you open in a review meeting when somebody asks “where is that net”, and what you hand to an engineer who has a finding and needs to see the circuit around it.

They read the same catalog and produce the same verdicts, so neither is a second opinion on the other. Choosing between them is about who is looking and why.

That is the ladder

Thirteen rungs, one board, from confirming a file was read correctly through to a house checklist that gates a merge, an archive that outlives the design, and a browser view of the same result.

The two things worth revisiting once you are running this for real are coverage and the parameter corpus. Coverage tells you how much of your checklist is genuinely being decided. Seeding parts is usually the cheapest way to move it.