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. That suits CI. It does not suit 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, with the same rules, tiers and verdicts rendered against the drawing.

Nothing new is computed here. 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 --web-dir ../../web

Run it from the project root, examples/jetson-project. The flag list is short on purpose, because the server discovers the rest. --web-dir points at the viewer’s built assets, which sit four levels up in a checkout, as in rung 3.

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)

Read the second line of the startup output twice. --addr :8090 binds every interface, so anyone who can reach your machine can reach the server, and it has no authentication, so they can read whatever you mounted. You usually want that on a workbench, and rarely 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, --web-dir (or web_dir in an agni.yaml) names wherever the built viewer lives; 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/jetson/. project.yaml carries the checklists. A mount rooted inside the design leaves project.yaml above the mount, where the server cannot reach it, and the Review panel then has no checklist to offer.

--review-store is the only other flag. It names a place to keep review runs, and it is the one genuinely new thing here. The server discovers everything else this rung needs.

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 reads the conventions: and checklists: sections of project.yaml, composes profiles/ and params/ from the project root, and takes each design’s intent: section and symbols/ from beside the design.

Passing a flag for one of them does not switch it on, because it is already on. For the conventions that is harmless. A --conventions file replaces the project’s section rather than adding to it, so a file saying the same thing leaves the output unchanged. A directory tier loads a second time instead. For profiles, agni check now refuses the second load outright rather than reporting every profile finding twice.

Use the Rules panel below to confirm that the tier reaches the design, rather than assuming it.

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

What the checks say

The ladder has been building up findings, and running the checks in the panel shows them. Most of them come from rung 2’s built-in rules. The capture below keeps the findings whose rule carries a tier’s namespace, with the run’s total. The panel and the command line report them identically:

$ agni check designs/jetson
  [warning] jetson/active-low-overbar: /USB Debug, PD/PDC_I2C1_IRQn (net name matches no allowed naming pattern)
  [warning] jetson/active-low-overbar: /USB Debug, PD/PDC_I2C2_IRQn (net name matches no allowed naming pattern)
87 finding(s) total

Only the naming rule has findings. The other tiers are present and passing. A findings list cannot show a pass, so ask for their verdicts:

$ agni check --verdicts designs/jetson
jetson/active-low-overbar  2 fail, 1353 pass
intent/exposure-declared  10 pass
intent/voltage-domain-mismatch  6 pass
jetson-profiles/pcie-signal-dangling  48 pass
jetson-profiles/pcie-signal-missing  56 pass

Read the rule column. jetson/ is your conventions section. jetson-profiles/ is your PCIe 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 also records how the tier arrived. A project composes its profiles under the project’s own name, so these read jetson-profiles/. An overlay passed with --profile-path composes under the fixed name profile-overlay/ instead. The file and the rules are the same, and the label tells you which route a run took.

The failure this read cannot have

Rung 1 spent its length on a bad read. Its close cousin is the unresolved-symbol failure. The parts load and the pins do not, because the pins live in the symbols, and the read never found the library holding them. It is the most expensive mistake on the ladder, so it is fair to ask what it looks like here.

On this board it cannot happen in the usual way, because every KiCad sheet embeds a copy of each symbol it places, so the pins travel inside the file. It nearly happened anyway. When an embedded copy differs from its library, KiCad files it under a second name. Until agni issue 1011 the reader looked only at the first name, so one stray sheet in this folder resolved 97 of its 138 symbols. Rung 1’s validate would have shown it. A KiCad project that keeps its symbols in a separate library, and xschem and gEDA schematics generally, can still fail the classic way.

Serve it wrong, on a design that can go wrong

The read that can go wrong here is rung 1’s, one sheet named on its own. Score it with the review layer:

$ agni review designs/jetson/som_power.kicad_sch
**8 pass, 3 fail, 3 n/a, 1 not-automated (of 15)**
| I3 | exposed signals have ESD protection | pass |  |
| I4 | every I2C bus has pull-ups | pass |  |
| A2 | every connector declared internal exists | fail | intent/exposure-declared: J10 (J10 is declared internal, and the design has no component J10); intent/exposure-declared: J11 (J11 is declared internal, and the design has no component J11); intent/exposure-declared: J13 (J13 is declared internal, and the design has no component J13); (+6 more) |

Seven passes, and they are the dangerous part. I3 and I4 pass because their rules found nothing on this sheet to judge, and an item whose rule fires on nothing reads as a pass. Rung 9 warned about this hollow pass, and --verdicts on the same read shows both rules with no subjects at all. Only A2 says something is wrong. Its intent names connectors that are not in the design. The review layer is not a substitute for rung 1. Confirm what was read, then score it.

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 of its panels stack down the right: Checks, Rules, Diff 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 Checks Rules Diff 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.

Checks holds the findings table above. Selecting a row highlights its subject on the canvas. A terminal cannot do that. You go from “net /SFP/SFI_CONN_RX_P has no ESD protection” to seeing where that net actually runs. Nothing is lit until you select one, since a sheet with a few dozen findings would otherwise be mostly highlight. Highlight › Show every finding lights the whole run at once, and the viewer remembers the choice. Grouped by rule, each rule’s header links to its page in the rule reference, which says what the rule checks and why.

Canvas renders faithfully when the design carries geometry and computes a layout when it does not, exactly as rung 3 described. It draws in SVG. WebGL is offered only to a page opened with ?mode=webgl, since it lags on a large board.

Rules lists the composed catalog. Use it to confirm a tier actually loaded, rather than inferring that from findings that did or did not appear. Select the design first. With no design chosen, the panel lists the server’s own catalog. A project’s tiers compose per design, so jetson/, jetson-profiles/ and intent/ appear only once the panel knows which design it is listing for. A rule agni documents links to its reference page, and a project’s own rule has none.

The Rules panel also shows rung 5’s supersession, as arithmetic. Selecting the design moves the built-in profile/ count down by four and adds three jetson-profiles/ rules, because the project’s PCIe profile replaces the built-in one wholesale and declares one requirement fewer. The CLI says the same thing in its supersedes note.

Properties follows the selection. Click something on the canvas and it shows what that thing is, what it connects to, and for a part whether the corpus holds its datasheet spec. Its ask row offers questions about the selection, and each opens in the Query panel with the query visible before it runs. Shift-drag a rectangle to select a block of the sheet as one area, and Shift-click to add or remove a part. Properties then lists the nets crossing the area’s edge, which are the block’s interface, and its Ask rows ask about every part at once.

Query is the querying guide with a drawing attached. Each question gets a tab of its own, so an answer does not change while you ask the next one, and clicking a cell locates its net or part on the canvas. The Sets menu opens the query sets the project keeps in queries/, one tab per query. A query that does not parse shows its line with a caret under the place it stopped, as agni query prints it.

Trace asks for two pins and follows the signal through the series parts between them, as agni trace does, then lights the route on the drawing. A pin name that matches nothing in the design says so. That is a different answer from two pins with no route between them. Each end lists the design’s parts and pins as you type, matching letters in order, so u13 finds both U13 and U1.3, and a pin’s datasheet name finds it too. Choosing a part lists its pins with their nets. Typing a full pin and pressing Enter traces it as typed. A part or pin selected in Properties offers trace from here and to here.

Tables shows the run as rows. It lists the findings, the verdicts and the skipped rules, and a comparison’s changes while one is open. A header click sorts it the way --order-by does, and Save CSV writes the csv the CLI would write for the same answer.

Coverage shows each interface profile the design matched, with a chip per required signal, so the PCIe profile from rung 5 reads as signals found and signals missing. A found signal’s chip locates its net. Run checks matches the profiles, so the panel is empty until then.

Parts lists every part whose MPN has a published spec, with its parameters, limits and the datasheet page each came from. Rung 6 seeded params/, and the panel reads it, so a part with no spec there does not appear.

Compare in the top bar opens rung 10’s diff with a revision picker, in the Diff and Changes panels.

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

The checklist picker lists the project’s checklists by name, in the order project.yaml declares them, so its first entry is the project’s default. This project declares one, review. Pick review and press Run review. Rung 9 read this verdict in the terminal, and the panel returns the same one, 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. Rung 9 gave the reason, in what it said about a bare pass count.

Look at B1, the board’s minimum track width. It is a board question, and the entry is a schematic, which carries no copper. It reads pass in the panel exactly as it does in the terminal. The descriptor declares the .kicad_pcb as a companion, and both surfaces attach it. Until agni issue 646 the panel scored the entry alone and read a board item as not-applicable. An older run in the history may disagree for that reason.

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

Runs are kept, so the panel opens on the latest one and the picker holds the history. The history is the browser half of rung 11. It compares this week’s verdict against last month’s, and nobody had to remember to save either one as a file. Each stored run also carries the checklist it actually scored, so a run from before you edited the checklist 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. You open it in a review meeting when somebody asks “where is that net”, and you hand it 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.

Once you are running this for real, revisit two things, 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.