Rework SEO analytics workspace UX

This commit is contained in:
DCCONSTRUCTIONS 2026-06-29 23:35:52 +03:00
parent 6ed1df6803
commit 54398f86d7
5 changed files with 12242 additions and 363 deletions

View File

@ -0,0 +1,79 @@
# NDC SEO mod: UX contract стадий 5-8
Этот документ фиксирует продуктовый смысл середины SEO-pipeline до model review. Цель: оставить Dev Mode доказательным, но не превращать GUI в сырую консоль SEO-инженера.
## Stage 5: Рыночный спрос
Вопрос пользователя: есть ли реальный поисковый спрос по темам сайта?
Основная ценность:
- показать, какие semantic seeds подтверждены внешним market evidence;
- показать, какие фразы слабые, шумные, related-only или пустые;
- отделить deterministic site meaning от реального спроса;
- подготовить только evidence-backed inputs для SEO-плана страниц.
Dev-details могут показывать Wordstat job, provider readiness, raw rows и external-service gaps. Основной экран не должен выглядеть так, будто строки Wordstat уже являются финальными SEO-решениями.
Выход этапа:
- подтвержденные сигналы спроса;
- фразы, требующие проверки;
- фразы, которые нельзя двигать дальше;
- недостающие evidence для уверенного решения.
## Stage 6: SEO-план страниц
Вопрос пользователя: что именно SEO-модуль будет улучшать, где и почему?
Основная ценность:
- разложить approved phrases по страницам, секциям и полям;
- определить роль: главный интент, дополнительный, поддержка или проверка;
- задать лимиты естественного использования;
- сохранить причину и evidence-link для каждого решения;
- собрать контролируемый contract перед diff/model review.
Это не ручная таблица ключей. Пользователь подтверждает или корректирует предложенные назначения.
Выход этапа:
- page-level SEO plan;
- field-level bindings: title, description, H1, body section;
- materialization queue для planned pages, которых нет в scan;
- blockers перед rewrite diff.
## Stage 7: Model review
Вопрос пользователя: корректны ли предложенные изменения текста, не уехал ли смысл и есть ли польза?
Основная ценность:
- ревьюить model-generated variants только после approved SEO-плана;
- показывать before/after diff по полям и секциям;
- объяснять, какой intent или keyword закрывает каждое изменение;
- держать в контексте forbidden claims, project ontology, tone и style limits;
- требовать human acceptance перед validation/apply.
Выбор модели, provider, plugins и runtime намеренно не решается здесь. Это обсуждаем отдельно только когда diff contract и review interface будут готовы.
Выход этапа:
- accepted/rejected/edited rewrite variants;
- unresolved model risks;
- final working draft candidates для validation.
## Stage 8: Quality gate
Вопрос пользователя: можно ли безопасно применять эти изменения?
Основная ценность:
- блокировать apply на hard SEO, meaning, style или safety errors;
- отделять soft warnings от blockers;
- проверять, что не появились unsupported claims или legacy context;
- проверять anti-spam, ru-text, structure и pending diff state;
- давать ясный final apply readiness verdict.
Выход этапа:
- can apply / cannot apply;
- hard blockers;
- accepted warnings;
- changeset candidate для dry-run/apply.
## Product rule
Dev Mode может показывать raw data, но каждый видимый stage сначала должен отвечать на пользовательское решение. Raw evidence уходит в drill-down details, а не становится главной ценностью экрана.

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,19 @@ import {
useReactFlow useReactFlow
} from "@xyflow/react"; } from "@xyflow/react";
import "@xyflow/react/dist/style.css"; import "@xyflow/react/dist/style.css";
import { FileText, Globe2, Layers3, LockKeyhole, Maximize2, Minus, PanelTop, Plus, RefreshCw, Save, Settings2 } from "lucide-react"; import {
FileText,
Globe2,
Layers3,
LockKeyhole,
Maximize2,
Minus,
PanelTop,
Plus,
RefreshCw,
Save,
Settings2
} from "lucide-react";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { import {
fetchProjectScopeLayout, fetchProjectScopeLayout,
@ -1079,8 +1091,6 @@ function shouldAutosaveNodeChanges(changes: NodeChange<ScopeFlowNode>[]) {
export default function ProjectScopeFlow({ export default function ProjectScopeFlow({
isBusy, isBusy,
onApplyRecommendedScope,
onClearSelection,
onTogglePage, onTogglePage,
projectId, projectId,
projectName, projectName,
@ -1287,41 +1297,38 @@ export default function ProjectScopeFlow({
}, [scheduleLayoutAutosave, selectionSignature]); }, [scheduleLayoutAutosave, selectionSignature]);
const nodeTypes = useMemo(() => ({ scopeNode: ScopeNode }), []); const nodeTypes = useMemo(() => ({ scopeNode: ScopeNode }), []);
const groups = scopePriority const scopeDashboardItems = [
.map((scope) => ({ {
count: scope === "template_block" ? scan.siteSections.length : scan.pages.filter((page) => page.scope === scope).length, icon: <FileText size={13} />,
selected: key: "indexable",
scope === "template_block" label: "Публичные страницы",
? scan.siteSections.filter((section) => section.selected && section.enabled && section.pageId).length value: `${stats.selectedIndexable}/${stats.indexable}`
: scan.pages.filter((page) => page.scope === scope && page.selected).length, },
scope {
})) icon: <Layers3 size={13} />,
.filter((group) => group.count > 0); key: "sections",
label: "Секции главной",
value: `${stats.selectedSections}/${scan.siteSections.length}`
},
{
icon: <LockKeyhole size={13} />,
key: "admin",
label: "Админка",
value: `${stats.selectedAdmin}/${stats.admin}`
}
];
return ( return (
<section className="project-scope-flow-panel"> <section className="project-scope-flow-panel">
<div className="project-scope-flow-head"> <div className="project-scope-flow-canvas">
<strong>Конфигурация проекта</strong> <div className="project-scope-flow-stats nodrag nopan">
</div> {scopeDashboardItems.map((item) => (
<div className="project-scope-flow-controls"> <span key={item.key}>
<div className="project-scope-flow-legend"> {item.icon}
{groups.map((group) => ( {item.label} · {item.value}
<span className={`scope-legend-item ${group.scope}`} key={group.scope}>
<FileText size={13} />
{scopeGroupNames[group.scope]} · {group.selected}/{group.count}
</span> </span>
))} ))}
</div> </div>
<div className="project-scope-flow-actions">
<button className="active" disabled={isBusy} onClick={onApplyRecommendedScope} type="button">
SEO scope
</button>
<button disabled={isBusy || stats.selectedWorkspaceItems === 0} onClick={onClearSelection} type="button">
Снять все
</button>
</div>
</div>
<div className="project-scope-flow-canvas">
<ReactFlowProvider> <ReactFlowProvider>
<ReactFlow <ReactFlow
colorMode="light" colorMode="light"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff