Skip to Content

gtmesh ui

Open the mesh’s local, read-only control room. gtmesh ui starts a small loopback dev-server (bound to 127.0.0.1 — no external network) and opens your browser on the Dashboard: how the site is doing in Google — visits, rankings, trends — without opening Search Console. One tab over, Topics lists every topic you’re fighting for and how each is doing, in plain language; Pages does the same per page — its status, whether Google has indexed it, and what it earns; Links shows the external authority arriving into the mesh — who links to the site, and where those links land. The deeper surfaces — Data, Graph — render the committed graph artifacts (graph/nodes.jsonl, graph/edges.tsv) for when you want the structure itself.

gtmesh ui [--view dashboard|topics|pages|links|db|graph|manifest] [--port <n>] [--open|--no-open] [--env <env>]

It is read-only, in the mold of doctor — it never writes committed state — and it is not part of the deterministic pipeline: it’s a dev tool for looking at your working mesh while you iterate. It runs until you press Ctrl+C; there is no committed output (scratch lives under the git-ignored .gtmesh/ui/). As you apply/seal in another terminal — or pull a fresh performance snapshot — the view live-reloads.

The six tabs

TabQuestion it answers--view
Dashboard (landing)How is the site doing in Google?dashboard
TopicsWhat are we fighting for, and is it working?topics
PagesIs every page live, indexed, and earning?pages
LinksWho links to the site, and where does that authority land?links
DataWhat exactly is in the graph, record by record?db
GraphWhat does the whole mesh look like as a structure?graph

Pages replaces the earlier Site tab — the website-as-a-hierarchy view lives on inside it as the Tree toggle, and old links keep working: --view manifest and #manifest permalinks open Pages with the Tree toggle active.

Dashboard, Topics, and Pages read the latest performance snapshot per provider — the committed files pull performance --source gsc (and each other configured performance provider) writes under data/raw/performance/<provider>/ — plus the AI-visibility snapshot when one exists, joined against the committed graph at view time. It’s the same join topic performance prints, rendered visually; the snapshot is a read-time overlay next to the graph, and plan/apply keep working from demand alone, exactly as before.

Dashboard — how is the site doing?

The header names the data you’re looking at: the snapshot’s report window and its age (“Search data Jul 16 – Aug 12 · pulled 2 days ago”) plus the refresh command. Below it:

  • Four stat tilesVisits · Times shown · Average ranking · Click rate (with the “typical for #N” context) — each showing the current window against the previous one, with a delta chip and a daily trend line. When more than one search engine reports, the two event tiles (visits, times shown) sum across sources with a per-engine split line under each (“Google 1,204 · Bing 87”), while ranking and click-rate context stay in one engine’s labeled frame — see Sources.
  • Where to focus — the four health states as buckets, with counts and topic chips that link straight into the Topics tab.
  • Biggest moves — the top ranking gainers and losers by topic since the previous window.
  • Coverage — topics fought for, pages live and in progress, topics with data — and Outside the mesh: the share of the property’s search traffic landing on URLs the mesh doesn’t manage.
  • Indexing — how many published pages Google has indexed, the not-indexed reasons in plain words (with a note for pages too new to judge), and any sitemap errors when the property reports them. Click through to the Pages tab for the per-page detail.
  • Authority — the site’s backlink profile at a glance: Site strength (Ahrefs Domain Rating) with its trend, live referring-domain and backlink totals, and the top referring domains — linking through to the Links tab. Its empty state points at gtmesh pull links.
  • AI visibility — how often public AI answers (ChatGPT, Google AI Overviews, Gemini, Perplexity, Copilot) cite the site as a source: per-platform counts of citing answers and cited pages, with an empty state pointing at the pull. See AI visibility for what the numbers mean.

Sources — which engine you’re looking at

Once more than one search engine has a performance snapshot (say Bing joins Google), each performance page grows a source filter below the navigation — it lives on the pages it applies to, never in the global header (Data and Graph don’t read performance). The Dashboard offers All sources · Google · Bing; Topics and Pages offer just Google · Bing, because everything on them is a per-engine judgment with no blended view. The choice sticks as you switch tabs and rides the URL (#topics?src=bing) so a scoped view is shareable. The refresh command becomes gtmesh pull performance, which pulls every configured provider in one go.

The rule underneath is aggregate events, never judgments:

  • Event counts add up. A visit is a visit whichever engine sent it, so “All sources” sums visits and times shown across engines (and recomputes the click rate from the sums). Each tile’s sparkline draws one line per engine in that engine’s color, and the split line beneath (”● Google 1,204 · ● Bing 87”) doubles as the legend.
  • Judgments don’t. A #3 in Google is not a #3 in Bing, and each engine’s click-rate baseline is its own curve — so rankings, CTR-vs-typical, and every health verdict always read within one engine’s frame, never blended. In “All sources” those surfaces show Google (the default judgment frame), clearly labeled; scope the filter to another engine to see the same scoreboard in its frame.

Filter-and-compare is the diagnostic this exists for: a topic slipping in one engine points at that engine’s ranking systems; slipping in both points at the content. With a single connected source there is no filter and everything reads exactly as before. (Indexing detail is Google URL-Inspection data whichever source is active, and the AI-visibility card has its own providers — the source filter doesn’t touch it.)

Topics — the scoreboard

A ranked table, not a graph. Each row: the topic (with its main term) · a health chip · Opportunity as a relative bar · searches/mo · ranking + trend · visits · referring domains (how many distinct sites link to the cluster, from the latest backlink snapshot — an em-dash until one exists) · a short next step. Every numeric column sorts. Filter by health state (chips with counts) or free text; the default sort is the CLI’s own order — worst health first, then opportunity — so the top of the table is where effort goes.

Click a row and it expands into the topic’s full story:

  • What’s happening — the engine’s evidence, in page-1/page-2 language rather than raw deltas.
  • What to do — the fix, with the exact command in a copyable block.
  • External authority — the cluster’s referring domains and backlinks, and the apex’s share (“6 of 8 referring domains point at the topic’s home page”) — the “content problem vs link problem” read: a topic that’s worth a push with strong content and no referring domains needs links, not a rewrite. The sums count a domain once per page it links to.
  • The cluster — the hub/pillar and its member pages, each with its own ranking / visits / trend (and its referring-domain count once a backlink snapshot exists), plus a one-line reminder of how members flow authority up to the apex.

A “How to read this” glossary sits at the foot of the page, and rows are deep-linkable (#topics?sel=<value>), so a topic’s story has a URL you can share.

The four health states

Every topic wears one of four chips. They are plain-language groupings of the engine’s verdicts — the same topic performance prints — and each chip’s tooltip names the underlying signal and threshold, so nothing is hidden behind the translation:

Health stateEngine verdict(s) underneathReads asWhat to do
Losing ground (red)decayingrankings dropped vs the previous windowact here first — refresh and improve the slipping pages
Worth a push (amber)apex-unbuilt (“Home page not live”) · striking-distance (“Within reach”) · ctr-gap (“Ranking, not clicked”) · head-unranked (“Main term not ranking”)the opportunity pile — the chip text names which kindpublish the topic’s home · strengthen the apex · rework titles/meta · point the apex at the main term
Healthy (green)servedranking with a healthy click rateleave it alone
Quiet (gray)no-data (“No search data yet”) · unbuilt (“Not built yet” — a hollow chip)too new, or not startedgive it a window · build it

The verdicts, thresholds, and fix pointers all come from topic performance — see its reference entry for exactly when each fires.

How to read the numbers

The same translation covers the metrics: every number gets a plain name, and its ⓘ carries the definition.

On screenMeaningEngine field
Visits from Google (from Bing / from search, per the active source)times someone clicked through from a search resultclicks
Times shownhow often a page appeared in results, clicked or notimpressions
Average ranking (#12.4)the average spot in results across every search you were shown for, weighted by times shown — lower is better; #1–10 is page 1. Starting to appear for new, lower-ranked searches can pull it down even as visibility grows, so read it next to Times shownposition
Click rate (vs “typical for #N”)the share of viewers who clicked, against your site’s own average at that rankingctr / expected_ctr
Trend arrow ▲/▼ (“up/down N places”)ranking movement vs the previous window, as direction wordsposition_delta
Opportunity (relative bar)monthly searches × click value — the same score the CLI ranks byopportunity
Main termthe search the topic exists to winhead_keyword

Pages — every page, its status, and what it earns

Where Topics is the scoreboard per topic, Pages is the same story per page — a filterable list of every page in the mesh, not a tree to click through. Each row:

  • the page — slug and title;
  • its topic — the topic it belongs to, with a HUB / PILLAR badge when the page is that topic’s apex;
  • its lifecycle status — where the page sits in the build (writing, review, published, …);
  • an indexing badge — whether Google has the page (see the states below);
  • its ranking + trend and visits — the same plain-language numbers as Topics;
  • its referring domains — distinct sites linking to the page, from the latest backlink snapshot (an em-dash until one exists). Sortable, like every numeric column.

Filter by section, lifecycle status, indexing state, or free text. Click a row and a drawer opens with the page’s full story (the same pattern as a Topics row):

  • What people searched to find it — the page’s top queries in the current window, each with its visits, times shown, and ranking.
  • External links — the page’s backlinks, referring domains, and page strength (Ahrefs URL Rating) when the link index serves them.
  • Indexing detail — the state in plain language, when Google last crawled the page, and whether Google agreed with your canonical.
  • Its topic — the membership, linking straight to the topic’s story on the Topics tab.
  • Jump-offs: the live page ↗ (published pages only) and the authored source (content/<id>/index.yaml) via the Data record.

Rows are deep-linkable (#pages?sel=<id>), and the cluster rows in a Topics drawer link here — so “this topic is slipping” drills straight down to “which page, and why”.

The Tree toggle. The website-as-a-hierarchy view (home → section indexes → leaf pages, read from the compiled site.manifest.json, with each page’s internal-link relations and planned-vs-published up-link divergence) is one click away behind the Tree toggle — the list answers “how is this page doing?”, the tree answers “where does it sit?”. --view manifest and #manifest permalinks open Pages with the Tree active.

The indexing states

A published page Google hasn’t indexed earns nothing — this is the health axis the badge surfaces. The states are plain-language readings of the URL-Inspection data the snapshot carries:

BadgeReads asWhat it usually means
IndexedGoogle has itnothing to do
Crawled, not indexedGoogle looked and passedusually a content-quality or duplication signal — improve the page
Discovered, not crawledGoogle knows the URL but hasn’t visitedinternal links and a sitemap entry help it get there
Canonical mismatchGoogle chose a different canonicalusually duplication — two pages competing for the same search
Not live yetthe page isn’t publishednothing to inspect until it ships

The data comes from the same pull performance --source gsc pull — it now also sweeps every published page’s URL through Google’s URL Inspection API (plus your sitemap status) and commits the result beside the analytics snapshot. The sweep needs site.base_url set (the command warns with the fix if it’s missing) and inspects published pages only; --no-index skips it. The operator skill carries the matching playbook — what to actually do about each state.

AI visibility

People increasingly ask ChatGPT instead of Google — this is the surface that tells you whether those answers cite you. gtmesh pull ai fans out to every configured AI provider (today: Ahrefs, reusing the token you already have; one flat-cost request per pull) and snapshots, per platform — ChatGPT, Google AI Overviews, Gemini, Perplexity, Copilot — how many public AI answers cite the site and how many distinct pages they cite. It needs site.base_url set — the command’s error carries the fix — and commits each provider’s snapshot to data/raw/ai/<provider>/, beside the Search Console snapshot.

  • A citation means an AI answer used a page of yours as a source — the AI-search equivalent of ranking. The Dashboard card shows each platform’s count of citing answers and cited pages.
  • Providers are never merged — each samples AI answers its own way — so when more than one reports (Bing joins with the provider phase), the card names each source.
  • Zero citations is a normal starting state for a young site — AI answers cite established sources, and citations follow the authority you’re already building. The empty states say so rather than showing a wall of zeros; keep building, and let answer-quality (the AEO gate) do its work.

Like the Search Console data, this is an observation channel: the snapshot is read at view time, and plan/apply keep working from demand alone — what AI cites never silently changes what the engine builds.

The mesh controls internal authority flow — spokes route weight up to their hub. Backlinks are the external authority arriving into that structure, and the Links tab is where you watch it: gtmesh pull links snapshots the site’s backlink profile from each configured link index (Ahrefs primary; Bing’s free per-page counts as a cross-check) into data/raw/links/<provider>/, and the tab joins the latest snapshot against the graph at view time.

  • The header carries the site-level read: Site strength (Ahrefs Domain Rating, 0–100) with its trend line, live referring-domain and backlink totals, the snapshot’s age, and the refresh command.
  • The referring-domains table is the who-links-to-us list, laid out like Topics: a full-width sortable table with each linking domain’s own site strength, how many links it points at you, and when it was first seen. A free-text filter narrows it, and clicking a row opens the domain’s drawer (the selection rides the permalink, #links?sel=<domain>).
  • The domain drawer tells one domain’s story: its rating, link count, first-seen date, and spam status, then the pages it links to — from the snapshot’s attribution edges. A mesh page jumps straight into its Pages drawer; a target that maps to no mesh page shows as its raw URL, labeled legacy URL — not a mesh page. Those legacy URLs are redirect-reclaim candidates: they still hold live links, so pointing a redirect at a live page flows that authority into the mesh — the drawer says so whenever any exist.
  • The spam filter (All | Clean | Flagged as spam, with counts) splits the table by Ahrefs’ own known-spam-domain classification — the flag is theirs, carried verbatim. Flagged domains also wear a muted “flagged as spam” chip on their row (their links carry little or no weight — a young site attracting a few is normal, not an emergency).
  • The “Linking to” filter scopes the table to the domains that link into one topic’s cluster pages or one page (picked by slug) — “who links to this topic?” in one control, with a clear affordance while it’s active.
  • The Pages drawer’s External links block lists the page’s actual referring domains from the same edges — the verbatim referring host, subdomain included (blog.example.com stays distinct from example.com) — each with its link count and a spam chip when flagged, under the counts line.
  • Apex share — on the Topics drawer — answers the question the whole model exists for: is external authority pooling at the topic’s home page, or scattered across the cluster? Paired with the health verdicts it makes “content problem vs link problem” measurable: a topic that’s worth a push, with strong content and no referring domains, needs links — the cluster’s internal anchors are already the engine’s job.
  • Zero referring domains is the honest starting state for a young site. The empty states say so rather than showing a wall of zeros — authority accrues as the mesh earns links; keep building. A snapshot from before the backlinks pull existed still renders every count — the drawer and the Pages list simply say the next gtmesh pull links adds the page-level attribution.

The channel has its own provider notion (link indexes, not search engines), so the performance source filter doesn’t touch it: the page reads the primary link index (Ahrefs when present) and names its source. A counts-only index (Bing) still shows backlink counts everywhere, with the rating-and-domains sections naming what serves them. Like every observation channel it is a read-time joinplan/apply never look at it.

Before the first snapshot

With no performance snapshot yet, Dashboard and Topics open on an empty state that walks you through the setup: run gtmesh pull performance --source gsc, and on the first run follow the service-account walkthrough its error message carries (the command checks its own prerequisites — see the reference for the four steps). Once the snapshot lands in data/raw/performance/, the running viewer picks it up and live-reloads. Until then, Topics still lists every topic from the graph — all Quiet — Pages lists every page (statuses and the tree come from the graph; the indexing badges and query data wait for the snapshot), and Data/Graph work as normal. A snapshot pulled before the index sweep existed renders the same way on Pages: the list works, and the indexing column points you at a fresh gtmesh pull performance --source gsc.

The deeper surfaces

  • Data — your nodes as browsable records (Neo4j-browser-meets-Airtable). Filter by label, search by id or property, and sort by any column — the table doubles as a ranked build queue (see sorting). Open any node for a full detail panel: every label, every property, and its relationships split into the two layers — the topics it belongs to (MEMBER_OF, by axis) and the domain hubs it points at (HAS_<axis>) — plus a 1-hop neighborhood mini-graph you can click through to walk the mesh. A root topic (a pull frontier) carries a gold ★ root badge; a page node offers a jump straight to its Pages-tab drawer (queries, indexing, relations, authored source).
  • Graph — the whole graph, force-directed, color by label and styled by edge layer. Filter by label, edge type, and layer (see just the topic-membership mesh, or just the domain relationships); click a node to isolate its ego-network and walk it hop by hop. Root topics (see topic) stand out with a gold ring and a -prefixed, always-on label. It’s the structure-spelunking surface — last in the tab order because Dashboard, Topics, and Pages answer the everyday questions first.

Sorting the Data table

Click a column header to sort; click it again to flip the direction. Alongside id/label/slug/status, the table offers the two ranking columns whenever the current label scope carries them:

ColumnLives onMeaning
prioritypage nodesthe page’s score from scoring.formula — what next builds first
opportunitytopic nodesthe topic’s value at stake (scoring.topic_opportunity) — see topic status

They appear only where they mean something: select topic in the label rail and you get opportunity; select page and you get priority. A ranking column sorts highest first on the first click.

A row with no value shows and always sorts last, whichever way the column points — an off-calendar page (one matching a scoring.exempt rule) has priority: null, which means unranked, never ranked zero.

Retired pages (tombstones)

gtmesh page retire tombstones a page whose demand has faded or folded away. The node stays in the committed graph — redirects still resolve — so the viewer keeps showing it, just clearly marked as out of the build:

  • In the Data table and detail panel a tombstone is dimmed, its id and slug struck through, and it carries a dashed † retired badge. Hovering or selecting it restores full contrast so you can still inspect it.
  • In the Graph a tombstone draws as a hollow, dashed node (rather than a filled dot) with a -prefixed, struck label, and a Tombstones legend entry counts them.
  • In the Pages tab’s Tree view they are absent — a retired page isn’t served, so the compiled site tree correctly leaves it out.

The Data list is the build queue, so it hides tombstones by default. To bring them in, select retired in the Status filter — or click the † N retired hidden — show button in the toolbar, which selects it for you (widening an empty “all” filter to every status, so nothing else disappears). Because the gate reads straight off the status filter, it rides the permalink: #db?status=planned,retired reopens the table with tombstones showing.

Arguments & flags

OptionDescriptionDefault
--view <view>Initial tab: dashboard | topics | pages | links | db (Data) | graph | manifest (Pages with the Tree toggle active)dashboard
--port <n>Pin the dev-server porta free port is chosen and printed
--open / --no-openForce opening / not opening the browseropen on a TTY
--env <env>Initial render-set environment for the Pages Tree (local | dev | prod)local
--project <dir>Project directory (global flag).

Invariant-safety

The loopback server is a read-only dev tool, not the deterministic pipeline. It never writes committed state (writers remain apply/seal/lifecycle), it makes no external network calls (it binds 127.0.0.1 — the performance data it shows is the committed snapshot on disk, pulled earlier by pull), and the determinism that matters lives in the model it renders — the same artifacts always produce the same view. Deep engineering detail is in docs/spec/gtmesh-ui-spec.md.

When there’s no graph yet

If the mesh has no committed graph (graph/nodes.jsonl), the viewer opens on an empty state pointing you at gtmesh apply. Build the graph first, then gtmesh ui.

Last updated on