n-seo 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +1 -0
- package/docs/DEPLOY.md +9 -0
- package/docs/PRD.md +98 -32
- package/docs/SCHEDULING.md +22 -0
- package/package.json +4 -2
- package/src/views.tsx +137 -89
- package/ingest/__pycache__/analyze_metadata.cpython-312.pyc +0 -0
- package/ingest/__pycache__/google_auth.cpython-312.pyc +0 -0
- package/ingest/__pycache__/http_util.cpython-312.pyc +0 -0
- package/ingest/__pycache__/seo_config.cpython-312.pyc +0 -0
- package/ops/__pycache__/daily.cpython-312.pyc +0 -0
- package/ops/__pycache__/daily_diff.cpython-312.pyc +0 -0
- package/ops/__pycache__/demo_data.cpython-312.pyc +0 -0
- package/ops/__pycache__/doctor.cpython-312.pyc +0 -0
- package/ops/__pycache__/export_static.cpython-312.pyc +0 -0
- package/ops/__pycache__/llm.cpython-312.pyc +0 -0
- package/ops/__pycache__/publish.cpython-312.pyc +0 -0
- package/ops/__pycache__/update_check.cpython-312.pyc +0 -0
- package/probes/__pycache__/site_probe.cpython-312.pyc +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,39 @@ uses [Semantic Versioning](https://semver.org/).
|
|
|
8
8
|
|
|
9
9
|
Nothing yet.
|
|
10
10
|
|
|
11
|
+
## [0.7.0] - 2026-09-18
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **A site's indexing problems, on its own page.** `/site/<host>` said nothing
|
|
15
|
+
about indexing, so anyone looking at one site had to leave for `/indexing`
|
|
16
|
+
and find their host in a list — while the data was already keyed by host and
|
|
17
|
+
already rendered per host one page over. The site page now carries that
|
|
18
|
+
host's coverage: indexed against checked, problems grouped by coverage
|
|
19
|
+
state, never-crawled. The explanations and the Request Indexing verdict come
|
|
20
|
+
with it, because both pages now render from one component and cannot drift
|
|
21
|
+
into disagreeing about what Google said. No new pull and no new data file.
|
|
22
|
+
- A host that is all clear, or that has no sitemap Search Console can fetch,
|
|
23
|
+
says which rather than rendering an empty panel. An empty panel is
|
|
24
|
+
indistinguishable from a broken one.
|
|
25
|
+
- **Update checking is documented where people schedule things.**
|
|
26
|
+
`update-check` has been a step in the daily run since 0.4.1 and
|
|
27
|
+
`modules.updateCheck` is the only module enabled by default, but nothing
|
|
28
|
+
said so in `docs/SCHEDULING.md` — the one place someone would otherwise
|
|
29
|
+
conclude they need a second scheduled job for it. They do not: a weekly
|
|
30
|
+
`n-seo upgrade --check` would ask the registry the same question the daily
|
|
31
|
+
run already asks and write the answer to a log nobody opens. The Upgrades
|
|
32
|
+
section of `docs/DEPLOY.md` says the same for a deployed instance, where
|
|
33
|
+
nobody is watching a terminal and the notice waits on the dashboard.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- **The package no longer ships Python bytecode.** `files` lists `ops/`,
|
|
37
|
+
`ingest/` and `probes/` wholesale, and whatever ran the tests before
|
|
38
|
+
packing left `__pycache__` beside the sources — so a release built on CI
|
|
39
|
+
carried the runner's `.pyc` files. 0.6.1 shipped thirteen of them, a little
|
|
40
|
+
over a tenth of the package, compiled for a Python version no user is
|
|
41
|
+
guaranteed to have. `.gitignore` covers `__pycache__`, which is why this was
|
|
42
|
+
invisible in a checkout and only appeared in the tarball.
|
|
43
|
+
|
|
11
44
|
## [0.6.1] - 2026-09-15
|
|
12
45
|
|
|
13
46
|
### Fixed
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**An SEO, self-hosted.** Website: https://n-seo.dev/ · From the makers of [n-dx](https://n-dx.dev).
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/n-seo)
|
|
6
|
+
[](https://www.npmjs.com/package/n-seo)
|
|
6
7
|
[](https://github.com/en-dash-consulting/n-seo/actions/workflows/ci.yml)
|
|
7
8
|
[](LICENSE)
|
|
8
9
|
|
package/docs/DEPLOY.md
CHANGED
|
@@ -273,6 +273,15 @@ checks. The instance directory is never touched by an upgrade — that
|
|
|
273
273
|
separation is the point of `docs/INSTANCE.md`. Roll back by checking out the
|
|
274
274
|
previous engine commit and running `manage up`.
|
|
275
275
|
|
|
276
|
+
**You will hear about a release without going to look.** The daily run's
|
|
277
|
+
`update-check` step asks the registry once a day and writes
|
|
278
|
+
`data/update-check.json`; the Settings page and `doctor` read it. That matters
|
|
279
|
+
more on a deployed instance than on a laptop, because nobody is watching a
|
|
280
|
+
terminal here — the notice is waiting on the dashboard the next time you open
|
|
281
|
+
it. Do not add a separate scheduled `n-seo upgrade --check`; it would duplicate
|
|
282
|
+
the step the daily run already performs. `modules.updateCheck.enabled: false`
|
|
283
|
+
turns it off.
|
|
284
|
+
|
|
276
285
|
## Watching it
|
|
277
286
|
|
|
278
287
|
| Where | What |
|
package/docs/PRD.md
CHANGED
|
@@ -6,6 +6,13 @@ deliberately does not do, and the capabilities that exist or are planned.
|
|
|
6
6
|
Each feature lists acceptance criteria in the form a test or a reviewer can
|
|
7
7
|
check. Status markers: **[shipped]**, **[planned]**, **[idea]**.
|
|
8
8
|
|
|
9
|
+
Some capabilities are stated as epic-level acceptance criteria rather than as
|
|
10
|
+
their own `## Feature:` section — the epic *is* the capability, and splitting
|
|
11
|
+
it would add a heading without adding a claim. Those still appear as features
|
|
12
|
+
in the `.rex/` tree, which needs a node to schedule work against. The two
|
|
13
|
+
files agree on what exists and on its status; the tree is simply finer-grained
|
|
14
|
+
in places.
|
|
15
|
+
|
|
9
16
|
## Purpose
|
|
10
17
|
|
|
11
18
|
n-seo is a local-first control plane for organic growth across one or more
|
|
@@ -89,7 +96,8 @@ a fresh checkout runs.
|
|
|
89
96
|
- Acceptance: `ops/doctor.py` reports the exact email to add in Search
|
|
90
97
|
Console and GA4 and which configured properties are not yet accessible.
|
|
91
98
|
|
|
92
|
-
# Epic: Ingest [shipped]
|
|
99
|
+
# Epic: Ingest and site health [shipped]
|
|
100
|
+
|
|
93
101
|
|
|
94
102
|
## Feature: Search Console pulls [shipped]
|
|
95
103
|
|
|
@@ -105,7 +113,7 @@ a fresh checkout runs.
|
|
|
105
113
|
`funnel` report only for `conversions.site`, falling back when the custom
|
|
106
114
|
dimension is unregistered.
|
|
107
115
|
|
|
108
|
-
## Feature: Time series, index coverage, metadata audit
|
|
116
|
+
## Feature: Time series, index coverage, metadata audit and trend analysis [shipped]
|
|
109
117
|
|
|
110
118
|
- Acceptance: date × page series for 180 days from both sources.
|
|
111
119
|
- Acceptance: URL Inspection verdict for every sitemap URL (sitemap indexes
|
|
@@ -119,7 +127,8 @@ a fresh checkout runs.
|
|
|
119
127
|
- Acceptance: trend file with branded/generic split, rising/falling queries
|
|
120
128
|
(84d vs prior 84d), monthly trajectory and AI-referral sessions by month.
|
|
121
129
|
|
|
122
|
-
|
|
130
|
+
## Feature: No-auth health probe with regression alerts [shipped]
|
|
131
|
+
|
|
123
132
|
|
|
124
133
|
- Acceptance: robots.txt (incl. AI-crawler disallows), sitemap.xml, llms.txt
|
|
125
134
|
and llms-full.txt, homepage metadata/JSON-LD/visible-text bytes, and a real
|
|
@@ -127,7 +136,8 @@ a fresh checkout runs.
|
|
|
127
136
|
- Acceptance: the daily diff raises an ALERT line on any regression versus
|
|
128
137
|
the previous snapshot.
|
|
129
138
|
|
|
130
|
-
# Epic: Action engine [shipped]
|
|
139
|
+
# Epic: Action engine and profiles [shipped]
|
|
140
|
+
|
|
131
141
|
|
|
132
142
|
- Acceptance: six rules over the 90-day window (metadata findings, CTR
|
|
133
143
|
gaps, striking distance, probe hygiene, engagement mismatch, traffic drop)
|
|
@@ -164,6 +174,11 @@ published once and installed many times. Full reference: `docs/PROFILES.md`.
|
|
|
164
174
|
corrupting the sort.
|
|
165
175
|
- Acceptance: a priority can never create a card. Everything in the queue
|
|
166
176
|
still came from data.
|
|
177
|
+
- Acceptance: `principles` carries the part of a method that is not a number —
|
|
178
|
+
a title, a body, and a kind. `hard` is a constraint an agent must not cross;
|
|
179
|
+
`guide` is judgement it should apply. They render at the top of the action
|
|
180
|
+
queue and on Settings, and `n-seo init` writes them into the instance's
|
|
181
|
+
`CLAUDE.md`, so the owner and their agent read the same rules.
|
|
167
182
|
- Decided: a profile is data only and may not ship executable code. Installing
|
|
168
183
|
a method should not mean running its author's code on the machine holding
|
|
169
184
|
your Search Console credentials. `priorities` covers reordering and hiding
|
|
@@ -177,7 +192,8 @@ published once and installed many times. Full reference: `docs/PROFILES.md`.
|
|
|
177
192
|
with the engine defaults documented in `src/config.ts` and mirrored in
|
|
178
193
|
`ingest/seo_config.py`; tests cover an override changing a rule's output.
|
|
179
194
|
|
|
180
|
-
# Epic: Dashboard [shipped]
|
|
195
|
+
# Epic: Dashboard and agent access [shipped]
|
|
196
|
+
|
|
181
197
|
|
|
182
198
|
- Acceptance: routes `/`, `/actions`, `/insights`, `/trends[/N]`,
|
|
183
199
|
`/content`, `/drafts/:slug`, `/campaigns/:slug`, `/site/:host`,
|
|
@@ -199,12 +215,41 @@ published once and installed many times. Full reference: `docs/PROFILES.md`.
|
|
|
199
215
|
pages and conversions, and writes the config file (creating it from the
|
|
200
216
|
example on first save); invalid topic lines are rejected with a message.
|
|
201
217
|
|
|
218
|
+
## Feature: Read-only MCP over stdio and authenticated HTTP [shipped]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
- Acceptance: stdio transport with no secret; HTTP transport that returns
|
|
222
|
+
503 with no token configured and compares tokens in constant time.
|
|
223
|
+
- Acceptance: every tool is annotated read-only; tools cover the queue, a
|
|
224
|
+
single action, sites, per-site report, top queries, striking distance, CTR
|
|
225
|
+
gaps, metadata audit, time series, ops status, daily log, proposals,
|
|
226
|
+
conversions, campaigns, settings and engine info; docs exposed as
|
|
227
|
+
resources.
|
|
228
|
+
|
|
229
|
+
## Feature: Indexing problems on the individual site page [shipped]
|
|
230
|
+
|
|
231
|
+
`/site/:host` says nothing about indexing today, so someone looking at one
|
|
232
|
+
site has to leave for `/indexing` and find their host in a list. The data is
|
|
233
|
+
already keyed by host — `indexStatus()` carries problems, never-crawled,
|
|
234
|
+
indexed and checked per site — so this is surfacing, not a new pull.
|
|
235
|
+
|
|
236
|
+
- Acceptance: the site page shows that host's indexed/checked counts, its
|
|
237
|
+
problems grouped by coverage state, and its never-crawled count.
|
|
238
|
+
- Acceptance: each problem keeps its coverage-state explanation and whether
|
|
239
|
+
Request Indexing helps — true only where Google has formed no judgement,
|
|
240
|
+
false where it fetched and declined.
|
|
241
|
+
- Acceptance: a host that is all clear, or has no sitemap coverage, says so
|
|
242
|
+
explicitly rather than rendering an empty panel; it links through to
|
|
243
|
+
`/indexing` for the cross-site view.
|
|
244
|
+
- Acceptance: renders from demo data with no server error, in both themes.
|
|
245
|
+
|
|
202
246
|
## Feature: Custom pages and rules in instance mode [idea]
|
|
203
247
|
|
|
204
248
|
Allow an instance to register extra routes and rules without forking the
|
|
205
249
|
engine (e.g. `instance/src/extensions.ts`).
|
|
206
250
|
|
|
207
|
-
# Epic:
|
|
251
|
+
# Epic: Optional modules [shipped]
|
|
252
|
+
|
|
208
253
|
|
|
209
254
|
- Acceptance: each of indexStatus, metadataAudit, opportunityScan, llm,
|
|
210
255
|
hackerNews, reddit, indexNow, staticExport, gitAutoCommit, notifications,
|
|
@@ -218,37 +263,23 @@ engine (e.g. `instance/src/extensions.ts`).
|
|
|
218
263
|
briefings.
|
|
219
264
|
- Acceptance: briefing prompts forbid generating comment text.
|
|
220
265
|
|
|
221
|
-
## Feature: HTTP LLM adapter [
|
|
266
|
+
## Feature: HTTP LLM adapter [shipped]
|
|
222
267
|
|
|
223
268
|
Optional adapter that calls an OpenAI-compatible or Anthropic HTTP endpoint
|
|
224
269
|
instead of a CLI, with the key read from `.env`.
|
|
225
270
|
|
|
271
|
+
- Acceptance: `modules.llm.http` takes `provider` (`anthropic` or `openai`),
|
|
272
|
+
`model`, an optional `baseUrl` and a key resolved from the environment;
|
|
273
|
+
`http` wins when configured and its key resolves, otherwise the CLI command
|
|
274
|
+
runs, and with neither the scan records candidates only.
|
|
275
|
+
|
|
226
276
|
## Feature: Bing Webmaster ingest [idea]
|
|
227
277
|
|
|
228
278
|
## Feature: Core Web Vitals via CrUX [idea]
|
|
229
279
|
|
|
230
|
-
# Epic: Daily run and scheduling [shipped]
|
|
231
|
-
|
|
232
|
-
- Acceptance: `ops/daily.py` waits for the network, runs the enabled steps
|
|
233
|
-
in order, retries a failed step once, tees output to `data/daily-ops.log`,
|
|
234
|
-
writes `data/last-run.json` with per-step timing, honours `--only`,
|
|
235
|
-
`--skip`, `--list`, `--no-network-wait`, and exits 1 on any failure.
|
|
236
|
-
- Acceptance: hooks (`beforeRun`, `afterRun`, `afterStep`) run in the
|
|
237
|
-
instance directory and are logged like steps without aborting the run.
|
|
238
|
-
- Acceptance: launchd, cron and systemd templates plus an installer script.
|
|
239
|
-
|
|
240
|
-
# Epic: MCP server [shipped]
|
|
241
|
-
|
|
242
|
-
- Acceptance: stdio transport with no secret; HTTP transport that returns
|
|
243
|
-
503 with no token configured and compares tokens in constant time.
|
|
244
|
-
- Acceptance: every tool is annotated read-only; tools cover the queue, a
|
|
245
|
-
single action, sites, per-site report, top queries, striking distance, CTR
|
|
246
|
-
gaps, metadata audit, time series, ops status, daily log, proposals,
|
|
247
|
-
conversions, campaigns, settings and engine info; docs exposed as
|
|
248
|
-
resources.
|
|
249
|
-
|
|
250
280
|
# Epic: Engine and instance [shipped]
|
|
251
281
|
|
|
282
|
+
|
|
252
283
|
- Acceptance: `N_SEO_INSTANCE` relocates every instance-owned path; in-place
|
|
253
284
|
mode is unchanged when it is unset.
|
|
254
285
|
- Acceptance: `n-seo init|start|dev|daily|doctor|demo|mcp|check|export|
|
|
@@ -264,6 +295,17 @@ instead of a CLI, with the key read from `.env`.
|
|
|
264
295
|
- Acceptance: `n-seo upgrade` refuses to leave the engine on a commit that
|
|
265
296
|
fails `npm run check` without printing the rollback command.
|
|
266
297
|
|
|
298
|
+
## Feature: Portable orchestrator with hooks and scheduler templates [shipped]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
- Acceptance: `ops/daily.py` waits for the network, runs the enabled steps
|
|
302
|
+
in order, retries a failed step once, tees output to `data/daily-ops.log`,
|
|
303
|
+
writes `data/last-run.json` with per-step timing, honours `--only`,
|
|
304
|
+
`--skip`, `--list`, `--no-network-wait`, and exits 1 on any failure.
|
|
305
|
+
- Acceptance: hooks (`beforeRun`, `afterRun`, `afterStep`) run in the
|
|
306
|
+
instance directory and are logged like steps without aborting the run.
|
|
307
|
+
- Acceptance: launchd, cron and systemd templates plus an installer script.
|
|
308
|
+
|
|
267
309
|
## Feature: Publish the engine to npm [shipped]
|
|
268
310
|
|
|
269
311
|
- Acceptance: `npm i n-seo` installs a working engine; `npx n-seo init`
|
|
@@ -284,13 +326,30 @@ instead of a CLI, with the key read from `.env`.
|
|
|
284
326
|
and both compare against the engine running now — so an upgrade stops the
|
|
285
327
|
notice immediately rather than at the next daily run.
|
|
286
328
|
|
|
287
|
-
|
|
329
|
+
## Feature: Update checking is visible where people schedule things [shipped]
|
|
288
330
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
331
|
+
The capability shipped in 0.4.1 and nobody needs a second scheduled job for
|
|
332
|
+
it: `update-check` is a step in the daily run, `modules.updateCheck` is the
|
|
333
|
+
only module enabled by default, and the Settings page and `doctor` read the
|
|
334
|
+
file it writes. What was missing was saying so where someone goes to set
|
|
335
|
+
scheduling up, which is exactly where they would otherwise invent a redundant
|
|
336
|
+
weekly job.
|
|
337
|
+
|
|
338
|
+
- Acceptance: `docs/SCHEDULING.md` names the `update-check` step, that
|
|
339
|
+
`modules.updateCheck` is on by default, and that the result appears on
|
|
340
|
+
Settings and in `doctor` — so a reader setting up scheduling does not add a
|
|
341
|
+
second job for it.
|
|
342
|
+
- Acceptance: the Upgrades section of `docs/DEPLOY.md` says the same for a
|
|
343
|
+
deployed instance, where nobody is watching a terminal.
|
|
344
|
+
- Acceptance: neither document suggests scheduling `n-seo upgrade --check`
|
|
345
|
+
separately; both say why that would duplicate the daily step.
|
|
346
|
+
- Decided: no weekly scheduler template. A second job would make a duplicate
|
|
347
|
+
registry request and write the answer to a log nobody opens. The narrow case
|
|
348
|
+
it would serve — an install that never schedules the daily run — is a
|
|
349
|
+
contradiction of how n-seo is meant to work.
|
|
350
|
+
|
|
351
|
+
# Epic: Onboarding, docs and quality [shipped]
|
|
292
352
|
|
|
293
|
-
# Epic: Onboarding and docs [shipped]
|
|
294
353
|
|
|
295
354
|
- Acceptance: `npm run demo` populates every page with synthetic data before
|
|
296
355
|
any Google setup.
|
|
@@ -298,6 +357,13 @@ instead of a CLI, with the key read from `.env`.
|
|
|
298
357
|
INSTANCE, MCP, OPERATING-RULES, PLAYBOOK, FAQ, CONTRIBUTING, SECURITY,
|
|
299
358
|
CHANGELOG exist and match the CLI contracts.
|
|
300
359
|
|
|
360
|
+
## Feature: Tests and CI [shipped]
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
- Acceptance: `npm run check` runs typecheck, TypeScript tests (sandboxed
|
|
364
|
+
copy) and Python tests; CI runs both suites, a demo-data dashboard smoke,
|
|
365
|
+
`doctor --offline`, and a grep that fails on any private name.
|
|
366
|
+
|
|
301
367
|
## Feature: Interactive setup wizard [idea]
|
|
302
368
|
|
|
303
369
|
`n-seo init --guided`: asks for hosts, property ids and the key path, then
|
package/docs/SCHEDULING.md
CHANGED
|
@@ -135,6 +135,28 @@ the dashboard shows as the run status chip), and appends a dated entry to
|
|
|
135
135
|
`docs/daily-log.md`. Re-running on the same day replaces that day's entry
|
|
136
136
|
rather than stacking a second one.
|
|
137
137
|
|
|
138
|
+
## Checking for a newer n-seo
|
|
139
|
+
|
|
140
|
+
You do not need a second scheduled job for this. `update-check` is a step in
|
|
141
|
+
the daily run, and `modules.updateCheck` is the only module enabled by
|
|
142
|
+
default: once a day it asks the registry whether a newer engine has been
|
|
143
|
+
published and writes `data/update-check.json`. The Settings page and
|
|
144
|
+
`n-seo doctor` both read that file, so the notice reaches you without either
|
|
145
|
+
of them making a network request.
|
|
146
|
+
|
|
147
|
+
Scheduling `n-seo upgrade --check` weekly on top of that would ask the
|
|
148
|
+
registry the same question a second time and write the answer to a log nobody
|
|
149
|
+
opens. Run it by hand when you want to look:
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
n-seo upgrade --check # reports what is available, changes nothing
|
|
153
|
+
n-seo upgrade # performs it, prints the changelog and the rollback
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Switch the daily check off with `modules.updateCheck.enabled: false`; nothing
|
|
157
|
+
about your instance is sent either way — it is the same public metadata
|
|
158
|
+
request `npm view n-seo version` makes from any machine.
|
|
159
|
+
|
|
138
160
|
## Logs, in one place
|
|
139
161
|
|
|
140
162
|
| File | What |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n-seo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Agentic, local-first SEO / AEO / GEO control plane: pulls Search Console + GA4, probes your sites, and ranks the next moves. Your LLM writes the proposals; your coding agent works the queue over MCP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "En Dash Consulting (https://endash.us)",
|
|
@@ -56,10 +56,12 @@
|
|
|
56
56
|
".claude/skills/n-seo-review/",
|
|
57
57
|
".claude/skills/n-seo-deploy/",
|
|
58
58
|
"!.claude/skills/README.md",
|
|
59
|
+
"!ops/install-git-filters.sh",
|
|
59
60
|
"docs/",
|
|
60
61
|
"README.md",
|
|
61
62
|
"CHANGELOG.md",
|
|
62
|
-
"LICENSE"
|
|
63
|
+
"LICENSE",
|
|
64
|
+
"!**/__pycache__"
|
|
63
65
|
],
|
|
64
66
|
"engines": {
|
|
65
67
|
"node": ">=20"
|
package/src/views.tsx
CHANGED
|
@@ -658,6 +658,42 @@ export const SiteDetail: FC<{ site: SiteCfg; days?: number }> = ({ site, days =
|
|
|
658
658
|
</table></div>
|
|
659
659
|
</>
|
|
660
660
|
)}
|
|
661
|
+
<SiteIndexing site={site} />
|
|
662
|
+
</>
|
|
663
|
+
);
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
/** This site's index coverage, on the page someone is already looking at.
|
|
667
|
+
* /indexing is the cross-site view; needing to leave a site page and find
|
|
668
|
+
* your host in a list is the thing this removes. Same snapshot, same
|
|
669
|
+
* component, no second pull. */
|
|
670
|
+
const SiteIndexing: FC<{ site: SiteCfg }> = ({ site }) => {
|
|
671
|
+
const status = data.indexStatus();
|
|
672
|
+
const s = status?.sites[site.host];
|
|
673
|
+
return (
|
|
674
|
+
<>
|
|
675
|
+
{!status ? (
|
|
676
|
+
<>
|
|
677
|
+
<h2>Indexing</h2>
|
|
678
|
+
<p class="empty">
|
|
679
|
+
No index snapshot yet — enable the <b>Index coverage sweep</b> module and run{" "}
|
|
680
|
+
<code>n-seo daily --only index-status</code>
|
|
681
|
+
</p>
|
|
682
|
+
</>
|
|
683
|
+
) : !s ? (
|
|
684
|
+
<>
|
|
685
|
+
<h2>Indexing</h2>
|
|
686
|
+
<p class="empty">
|
|
687
|
+
Nothing for {site.host} in the latest snapshot — the sweep reads sitemap URLs, so a
|
|
688
|
+
site with no sitemap Search Console can fetch will not appear here.
|
|
689
|
+
</p>
|
|
690
|
+
</>
|
|
691
|
+
) : (
|
|
692
|
+
<HostIndexing host={site.host} s={s} title="Indexing" />
|
|
693
|
+
)}
|
|
694
|
+
<p class="sub">
|
|
695
|
+
<a href="/indexing">Every site's coverage →</a>
|
|
696
|
+
</p>
|
|
661
697
|
</>
|
|
662
698
|
);
|
|
663
699
|
};
|
|
@@ -794,6 +830,104 @@ const SitemapLine: FC<{ sitemap?: data.IndexStatus["sites"][string]["sitemap"] }
|
|
|
794
830
|
);
|
|
795
831
|
};
|
|
796
832
|
|
|
833
|
+
/** One host's index coverage. Rendered per host on /indexing, and for a
|
|
834
|
+
* single host on its own site page — same verdicts, same explanations, so
|
|
835
|
+
* the two pages cannot drift into disagreeing about what Google said. */
|
|
836
|
+
export const HostIndexing: FC<{
|
|
837
|
+
host: string;
|
|
838
|
+
s: data.IndexStatus["sites"][string];
|
|
839
|
+
/** Heading text when the host name would be redundant (a site page). */
|
|
840
|
+
title?: string;
|
|
841
|
+
}> = ({ host, s, title }) => {
|
|
842
|
+
if (s.problems.length === 0) {
|
|
843
|
+
return (
|
|
844
|
+
<section class="idx-host">
|
|
845
|
+
<h2>
|
|
846
|
+
{title ?? host} <small>{s.indexed}/{s.checked} indexed — all clear</small>
|
|
847
|
+
</h2>
|
|
848
|
+
</section>
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
const groups = [...s.problems].sort(
|
|
852
|
+
(a, b) =>
|
|
853
|
+
(COVERAGE_ORDER.indexOf(a.coverage) + 1 || 99) -
|
|
854
|
+
(COVERAGE_ORDER.indexOf(b.coverage) + 1 || 99) ||
|
|
855
|
+
a.url.localeCompare(b.url),
|
|
856
|
+
);
|
|
857
|
+
let lastCoverage = "";
|
|
858
|
+
return (
|
|
859
|
+
<section class="idx-host">
|
|
860
|
+
<h2>
|
|
861
|
+
{title ?? host}{" "}
|
|
862
|
+
<small>
|
|
863
|
+
{s.indexed}/{s.checked} indexed · {s.neverCrawled} never crawled
|
|
864
|
+
</small>
|
|
865
|
+
</h2>
|
|
866
|
+
<SitemapLine sitemap={s.sitemap} />
|
|
867
|
+
<div class="tbl-wrap">
|
|
868
|
+
<table>
|
|
869
|
+
<thead>
|
|
870
|
+
<tr><th>URL</th><th>Last crawled</th></tr>
|
|
871
|
+
</thead>
|
|
872
|
+
<tbody>
|
|
873
|
+
{groups.map((p) => {
|
|
874
|
+
const header = p.coverage !== lastCoverage ? p.coverage : null;
|
|
875
|
+
lastCoverage = p.coverage;
|
|
876
|
+
return (
|
|
877
|
+
<>
|
|
878
|
+
{header && (
|
|
879
|
+
<tr class="idx-group">
|
|
880
|
+
<td colspan={2}>
|
|
881
|
+
<strong>{header}</strong>{" "}
|
|
882
|
+
<span class="idx-help">{COVERAGE_HELP[header]?.means ?? p.detail ?? ""}</span>
|
|
883
|
+
{COVERAGE_HELP[header] && (
|
|
884
|
+
<div class="idx-fix">
|
|
885
|
+
<span
|
|
886
|
+
class={`idx-ri ${REQUEST_INDEXING_HELPS.has(header) ? "yes" : "no"}`}
|
|
887
|
+
title={
|
|
888
|
+
REQUEST_INDEXING_HELPS.has(header)
|
|
889
|
+
? "Google has not judged this content yet — a request moves it"
|
|
890
|
+
: "Google already fetched and judged this page; re-requesting repeats the same verdict and spends quota"
|
|
891
|
+
}
|
|
892
|
+
>
|
|
893
|
+
{REQUEST_INDEXING_HELPS.has(header)
|
|
894
|
+
? "Request Indexing helps"
|
|
895
|
+
: "Request Indexing will not fix this"}
|
|
896
|
+
</span>{" "}
|
|
897
|
+
{COVERAGE_HELP[header].fix}
|
|
898
|
+
</div>
|
|
899
|
+
)}
|
|
900
|
+
</td>
|
|
901
|
+
</tr>
|
|
902
|
+
)}
|
|
903
|
+
<tr>
|
|
904
|
+
<td class="mono trunc">
|
|
905
|
+
<a href={p.url} target="_blank" rel="noopener noreferrer">
|
|
906
|
+
{p.url.replace(/^https?:\/\/[^/]+/, "") || "/"}
|
|
907
|
+
</a>
|
|
908
|
+
{p.canonicalMismatch && (
|
|
909
|
+
<span class="chip"> canonical → {p.googleCanonical}</span>
|
|
910
|
+
)}
|
|
911
|
+
</td>
|
|
912
|
+
<td class="mono">
|
|
913
|
+
{p.lastCrawl ? p.lastCrawl.slice(0, 10) : "never"}
|
|
914
|
+
{staleVerdict(p.lastCrawl) && (
|
|
915
|
+
<span class="idx-stale" title="Google has not looked since this verdict — recheck before treating it as a live bug">
|
|
916
|
+
{" "}stale
|
|
917
|
+
</span>
|
|
918
|
+
)}
|
|
919
|
+
</td>
|
|
920
|
+
</tr>
|
|
921
|
+
</>
|
|
922
|
+
);
|
|
923
|
+
})}
|
|
924
|
+
</tbody>
|
|
925
|
+
</table>
|
|
926
|
+
</div>
|
|
927
|
+
</section>
|
|
928
|
+
);
|
|
929
|
+
};
|
|
930
|
+
|
|
797
931
|
export const IndexingPage: FC = () => {
|
|
798
932
|
const status = data.indexStatus();
|
|
799
933
|
if (!status) {
|
|
@@ -816,95 +950,9 @@ export const IndexingPage: FC = () => {
|
|
|
816
950
|
Search Console's verdict on every sitemap URL · {totalProblems} not indexed,{" "}
|
|
817
951
|
{totalNever} never crawled · snapshot {status.generated.slice(0, 16).replace("T", " ")}
|
|
818
952
|
</p>
|
|
819
|
-
{hosts.map(([host, s]) =>
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
<section class="idx-host">
|
|
823
|
-
<h2>
|
|
824
|
-
{host} <small>{s.indexed}/{s.checked} indexed — all clear</small>
|
|
825
|
-
</h2>
|
|
826
|
-
</section>
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
const groups = [...s.problems].sort(
|
|
830
|
-
(a, b) =>
|
|
831
|
-
(COVERAGE_ORDER.indexOf(a.coverage) + 1 || 99) -
|
|
832
|
-
(COVERAGE_ORDER.indexOf(b.coverage) + 1 || 99) ||
|
|
833
|
-
a.url.localeCompare(b.url),
|
|
834
|
-
);
|
|
835
|
-
let lastCoverage = "";
|
|
836
|
-
return (
|
|
837
|
-
<section class="idx-host">
|
|
838
|
-
<h2>
|
|
839
|
-
{host}{" "}
|
|
840
|
-
<small>
|
|
841
|
-
{s.indexed}/{s.checked} indexed · {s.neverCrawled} never crawled
|
|
842
|
-
</small>
|
|
843
|
-
</h2>
|
|
844
|
-
<SitemapLine sitemap={s.sitemap} />
|
|
845
|
-
<div class="tbl-wrap">
|
|
846
|
-
<table>
|
|
847
|
-
<thead>
|
|
848
|
-
<tr><th>URL</th><th>Last crawled</th></tr>
|
|
849
|
-
</thead>
|
|
850
|
-
<tbody>
|
|
851
|
-
{groups.map((p) => {
|
|
852
|
-
const header = p.coverage !== lastCoverage ? p.coverage : null;
|
|
853
|
-
lastCoverage = p.coverage;
|
|
854
|
-
return (
|
|
855
|
-
<>
|
|
856
|
-
{header && (
|
|
857
|
-
<tr class="idx-group">
|
|
858
|
-
<td colspan={2}>
|
|
859
|
-
<strong>{header}</strong>{" "}
|
|
860
|
-
<span class="idx-help">{COVERAGE_HELP[header]?.means ?? p.detail ?? ""}</span>
|
|
861
|
-
{COVERAGE_HELP[header] && (
|
|
862
|
-
<div class="idx-fix">
|
|
863
|
-
<span
|
|
864
|
-
class={`idx-ri ${REQUEST_INDEXING_HELPS.has(header) ? "yes" : "no"}`}
|
|
865
|
-
title={
|
|
866
|
-
REQUEST_INDEXING_HELPS.has(header)
|
|
867
|
-
? "Google has not judged this content yet — a request moves it"
|
|
868
|
-
: "Google already fetched and judged this page; re-requesting repeats the same verdict and spends quota"
|
|
869
|
-
}
|
|
870
|
-
>
|
|
871
|
-
{REQUEST_INDEXING_HELPS.has(header)
|
|
872
|
-
? "Request Indexing helps"
|
|
873
|
-
: "Request Indexing will not fix this"}
|
|
874
|
-
</span>{" "}
|
|
875
|
-
{COVERAGE_HELP[header].fix}
|
|
876
|
-
</div>
|
|
877
|
-
)}
|
|
878
|
-
</td>
|
|
879
|
-
</tr>
|
|
880
|
-
)}
|
|
881
|
-
<tr>
|
|
882
|
-
<td class="mono trunc">
|
|
883
|
-
<a href={p.url} target="_blank" rel="noopener noreferrer">
|
|
884
|
-
{p.url.replace(/^https?:\/\/[^/]+/, "") || "/"}
|
|
885
|
-
</a>
|
|
886
|
-
{p.canonicalMismatch && (
|
|
887
|
-
<span class="chip"> canonical → {p.googleCanonical}</span>
|
|
888
|
-
)}
|
|
889
|
-
</td>
|
|
890
|
-
<td class="mono">
|
|
891
|
-
{p.lastCrawl ? p.lastCrawl.slice(0, 10) : "never"}
|
|
892
|
-
{staleVerdict(p.lastCrawl) && (
|
|
893
|
-
<span class="idx-stale" title="Google has not looked since this verdict — recheck before treating it as a live bug">
|
|
894
|
-
{" "}stale
|
|
895
|
-
</span>
|
|
896
|
-
)}
|
|
897
|
-
</td>
|
|
898
|
-
</tr>
|
|
899
|
-
</>
|
|
900
|
-
);
|
|
901
|
-
})}
|
|
902
|
-
</tbody>
|
|
903
|
-
</table>
|
|
904
|
-
</div>
|
|
905
|
-
</section>
|
|
906
|
-
);
|
|
907
|
-
})}
|
|
953
|
+
{hosts.map(([host, s]) => (
|
|
954
|
+
<HostIndexing key={host} host={host} s={s} />
|
|
955
|
+
))}
|
|
908
956
|
</>
|
|
909
957
|
);
|
|
910
958
|
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|