diff --git a/cmd/creator-web/static/assets/console.css b/cmd/creator-web/static/assets/console.css
index 8dbd237..6fe659a 100644
--- a/cmd/creator-web/static/assets/console.css
+++ b/cmd/creator-web/static/assets/console.css
@@ -63,6 +63,9 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
.wordmark strong { font: 400 20px/1 var(--serif); }
.wordmark small { margin-top: 4px; color: var(--muted); font: 9px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; }
.topbar-actions { display: flex; align-items: center; gap: 10px; }
+.topbar-nav { display: flex; align-items: center; gap: 4px; margin-right: 8px; }
+.topbar-nav a { padding: 9px 10px; color: var(--muted); text-decoration: none; font: 600 9px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
+.topbar-nav a:hover, .topbar-nav a:focus-visible { color: var(--ink); background: var(--panel-high); outline: 0; }
.connection { margin-right: 12px; color: var(--muted); font: 10px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
.connection i { width: 7px; height: 7px; display: inline-block; margin-right: 8px; border-radius: 50%; background: var(--good); box-shadow: 0 0 14px rgba(120, 198, 163, .6); }
.connection.offline i { background: var(--bad); box-shadow: none; }
@@ -77,6 +80,12 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
.context-form label:first-child { margin-top: 0; }
.context-form input { height: 40px; padding: 0 10px; font: 11px/1 var(--mono); }
.secondary-button { height: 42px; margin-top: 20px; color: #11150f; background: var(--signal); font-size: 12px; }
+.section-nav { display: grid; gap: 3px; margin-top: 4px; }
+.section-nav-link { display: grid; grid-template-columns: 28px 1fr; gap: 2px 10px; padding: 11px 10px; border-left: 2px solid transparent; color: var(--muted); text-decoration: none; transition: background .18s, border-color .18s, color .18s; }
+.section-nav-link:hover, .section-nav-link:focus-visible, .section-nav-link.active { border-left-color: var(--signal); color: var(--ink); background: rgba(201,168,95,.07); outline: 0; }
+.section-nav-link span { grid-row: 1 / span 2; color: var(--signal); font: 9px/1.4 var(--mono); }
+.section-nav-link b { font: 600 11px/1.2 var(--sans); }
+.section-nav-link small { color: var(--faint); font: 9px/1.2 var(--mono); }
.session-stamp { margin-top: auto; padding-top: 20px; display: grid; gap: 6px; border-top: 1px solid var(--line); }
.session-stamp span { margin-top: 8px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .14em; }
.session-stamp strong { overflow: hidden; color: var(--muted); font: 10px/1.4 var(--mono); text-overflow: ellipsis; }
@@ -94,6 +103,46 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
.signal-strip strong[data-state="error"] { color: var(--bad); }
.signal-strip strong[data-state="loading"] { color: var(--warn); animation: pulse 1.1s ease-in-out infinite; }
+.diagnostics-panel, .settings-panel { margin-bottom: 18px; border: 1px solid var(--line); background: rgba(16,23,20,.82); }
+.diagnostics-panel { border-color: rgba(120,198,163,.32); box-shadow: inset 3px 0 0 rgba(120,198,163,.45); }
+.panel-heading { min-height: 68px; padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid var(--line); }
+.panel-heading p { margin: 0 0 6px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .13em; text-transform: uppercase; }
+.panel-heading h2 { margin: 0; font: 400 23px/1 var(--serif); }
+.panel-heading-tools { display: flex; align-items: center; gap: 14px; }
+.panel-heading-tools > span, .settings-saved { color: var(--muted); font: 9px/1.3 var(--mono); letter-spacing: .06em; text-transform: uppercase; }
+.compact-button { width: auto; min-width: 142px; height: 38px; margin: 0; padding: 0 13px; }
+.diagnostic-intro { padding: 13px 18px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--line-soft); color: var(--muted); }
+.diagnostic-intro p { margin: 0; font-size: 11px; line-height: 1.45; }
+.diagnostic-pulse { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%; background: var(--good); box-shadow: 0 0 12px rgba(120,198,163,.7); }
+.diagnostic-list { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
+.diagnostic-list[aria-busy="true"] { opacity: .7; }
+.diagnostic-item { min-width: 0; min-height: 92px; padding: 14px 16px; display: grid; grid-template-columns: 9px 1fr auto; gap: 9px; align-items: start; border-right: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
+.diagnostic-item:nth-child(4n) { border-right: 0; }
+.diagnostic-item > i { width: 7px; height: 7px; margin-top: 4px; border-radius: 50%; background: var(--faint); }
+.diagnostic-item[data-state="ok"] > i { background: var(--good); box-shadow: 0 0 9px rgba(120,198,163,.6); }
+.diagnostic-item[data-state="error"] > i { background: var(--bad); }
+.diagnostic-item[data-state="loading"] > i { background: var(--warn); animation: pulse 1.1s ease-in-out infinite; }
+.diagnostic-item strong { display: block; color: var(--ink); font: 600 10px/1.3 var(--mono); }
+.diagnostic-item span { display: block; margin-top: 6px; color: var(--muted); font-size: 10px; line-height: 1.35; }
+.diagnostic-item em { color: var(--faint); font: normal 9px/1.2 var(--mono); text-transform: uppercase; }
+.diagnostic-item[data-state="ok"] em { color: var(--good); }
+.diagnostic-item[data-state="error"] em { color: var(--bad); }
+.settings-grid { padding: 18px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; }
+.setting-field, .setting-check { min-width: 0; display: grid; gap: 7px; color: var(--ink); }
+.setting-field > span, .setting-check b { font: 600 10px/1.2 var(--mono); letter-spacing: .08em; text-transform: uppercase; }
+.setting-field small, .setting-check small { color: var(--muted); font-size: 11px; line-height: 1.4; }
+.setting-field select { width: 100%; min-height: 40px; padding: 0 10px; border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink); background: #0b110e; font: 11px/1 var(--mono); }
+.setting-check { grid-template-columns: 18px 1fr; align-items: start; gap: 10px; padding: 10px 12px; border: 1px solid var(--line-soft); }
+.setting-check input { width: 16px; height: 16px; margin: 2px 0 0; accent-color: var(--signal); }
+.setting-check span { display: grid; gap: 7px; }
+.settings-actions { display: flex; align-items: center; justify-content: flex-end; gap: 14px; grid-column: 1 / -1; padding-top: 2px; }
+.settings-actions .text-button { margin: 0; }
+html[data-density="compact"] .data-card { padding: 18px; }
+html[data-density="compact"] .data-card dl div { padding-block: 10px; }
+html[data-density="compact"] .activity-row { min-height: 58px; padding-block: 8px; }
+html[data-density="compact"] .learning-row { min-height: 54px; padding-block: 9px; }
+html[data-reduced-motion="true"] *, html[data-reduced-motion="true"] *::before, html[data-reduced-motion="true"] *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: .01ms !important; }
+
.advisor-panel { margin-bottom: 18px; padding: 24px; display: grid; grid-template-columns: minmax(220px, .72fr) minmax(320px, 1.28fr); gap: 18px 34px; border: 1px solid rgba(201,168,95,.42); background: linear-gradient(125deg, rgba(201,168,95,.1), rgba(16,23,20,.88) 44%, rgba(12,20,17,.96)); box-shadow: inset 3px 0 0 rgba(201,168,95,.58); }
.advisor-intro { align-self: center; }
.advisor-intro > p { margin: 0 0 10px; color: var(--signal); font: 600 9px/1 var(--mono); letter-spacing: .15em; }
@@ -278,6 +327,9 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
.advisor-panel { grid-template-columns: 1fr; }
.capability-card { grid-column: auto; }
.prospective-card { grid-column: auto; }
+ .diagnostic-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .diagnostic-item:nth-child(4n) { border-right: 1px solid var(--line-soft); }
+ .diagnostic-item:nth-child(2n) { border-right: 0; }
}
@media (max-width: 680px) {
@@ -285,6 +337,7 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
.console { display: block; }
.topbar { height: 66px; padding: 0 14px; }
.wordmark small, .connection, .text-button { display: none; }
+ .topbar-nav { display: none; }
.context-rail { padding: 24px 18px; border-right: 0; border-bottom: 1px solid var(--line); }
.rail-copy { max-width: 48ch; }
.session-stamp { margin-top: 8px; grid-template-columns: auto 1fr; align-items: center; }
@@ -293,6 +346,13 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
.workspace-head { align-items: start; flex-direction: column; }
.last-update { text-align: left; }
.signal-strip { grid-template-columns: 1fr; }
+ .panel-heading { align-items: flex-start; flex-direction: column; }
+ .panel-heading-tools { width: 100%; justify-content: space-between; }
+ .diagnostic-list { grid-template-columns: 1fr; }
+ .diagnostic-item, .diagnostic-item:nth-child(2n), .diagnostic-item:nth-child(4n) { border-right: 0; }
+ .settings-grid { grid-template-columns: 1fr; padding: 16px; }
+ .settings-actions { grid-column: auto; align-items: stretch; flex-direction: column; }
+ .settings-actions .primary-button { width: 100%; }
.signal-strip div { border-right: 0; border-bottom: 1px solid var(--line); }
.advisor-panel { padding: 20px 16px; gap: 20px; }
.advisor-controls { align-items: stretch; flex-direction: column; }
diff --git a/cmd/creator-web/static/assets/console.js b/cmd/creator-web/static/assets/console.js
index c4e32f5..3eb6830 100644
--- a/cmd/creator-web/static/assets/console.js
+++ b/cmd/creator-web/static/assets/console.js
@@ -7,14 +7,17 @@ const advisoryForm = byId("advisory-form");
const loginError = byId("login-error");
const toast = byId("toast");
const contextFields = ["session-id", "workspace-id", "task-id", "prospective-id", "capability-id"];
+const settingsStorageKey = "domovoy.creator.settings";
let sessionToken = sessionStorage.getItem("domovoy.creator.session") || "";
let toastTimer;
+let refreshTimer;
let currentTask = null;
let currentProspective = null;
let pendingCommand = null;
let pendingNotification = null;
let lastAuthorizedActionID = "";
let advisoryPolicy = {Mode: "deterministic", ExternalEgress: false, AcknowledgementRequired: false};
+let uiSettings = {density: "comfortable", refresh: "off", motion: false};
function setText(id, value, fallback = "—") {
byId(id).textContent = value === undefined || value === null || value === "" ? fallback : String(value);
@@ -92,6 +95,7 @@ async function showConsole() {
setText("session-expiry", expiry);
loginView.hidden = true;
consoleView.hidden = false;
+ restoreSettings();
restoreContext();
await loadAdvisoryPolicy();
const context = saveContext();
@@ -100,10 +104,13 @@ async function showConsole() {
loadActionHistory();
loadLearning();
}
+ runDiagnostics();
}
function endSession(message = "Сессия завершена.") {
sessionToken = "";
+ clearInterval(refreshTimer);
+ refreshTimer = null;
sessionStorage.removeItem("domovoy.creator.session");
consoleView.hidden = true;
loginView.hidden = false;
@@ -160,10 +167,59 @@ contextForm.addEventListener("submit", (event) => {
loadDashboard();
});
+function restoreSettings() {
+ try {
+ const saved = JSON.parse(localStorage.getItem(settingsStorageKey) || "{}");
+ uiSettings = {
+ density: ["comfortable", "compact"].includes(saved.density) ? saved.density : "comfortable",
+ refresh: ["off", "30", "60", "300"].includes(saved.refresh) ? saved.refresh : "off",
+ motion: saved.motion === true,
+ };
+ } catch (_) {
+ uiSettings = {density: "comfortable", refresh: "off", motion: false};
+ }
+ byId("setting-density").value = uiSettings.density;
+ byId("setting-refresh").value = uiSettings.refresh;
+ byId("setting-motion").checked = uiSettings.motion;
+ applySettings();
+}
+
+function applySettings() {
+ document.documentElement.dataset.density = uiSettings.density;
+ document.documentElement.dataset.reducedMotion = uiSettings.motion ? "true" : "false";
+ clearInterval(refreshTimer);
+ refreshTimer = null;
+ if (sessionToken && uiSettings.refresh !== "off") {
+ refreshTimer = setInterval(() => loadDashboard(), Number(uiSettings.refresh) * 1000);
+ }
+}
+
+byId("settings-form").addEventListener("submit", (event) => {
+ event.preventDefault();
+ uiSettings = {
+ density: byId("setting-density").value,
+ refresh: byId("setting-refresh").value,
+ motion: byId("setting-motion").checked,
+ };
+ localStorage.setItem(settingsStorageKey, JSON.stringify(uiSettings));
+ applySettings();
+ setText("settings-saved", "Сохранено только в этом браузере");
+ notify("Настройки кабинета сохранены.");
+});
+
+byId("reset-settings").addEventListener("click", () => {
+ uiSettings = {density: "comfortable", refresh: "off", motion: false};
+ localStorage.removeItem(settingsStorageKey);
+ restoreSettings();
+ setText("settings-saved", "Восстановлены значения по умолчанию");
+ notify("Настройки сброшены.");
+});
+
byId("refresh-all").addEventListener("click", loadDashboard);
byId("refresh-actions").addEventListener("click", loadActionHistory);
byId("refresh-learning").addEventListener("click", loadLearning);
byId("logout").addEventListener("click", () => endSession());
+byId("run-diagnostics").addEventListener("click", runDiagnostics);
advisoryForm.addEventListener("submit", async (event) => {
event.preventDefault();
@@ -258,6 +314,56 @@ async function loadDashboard() {
setText("last-update", `Обновлено ${new Intl.DateTimeFormat("ru-RU", {hour: "2-digit", minute: "2-digit", second: "2-digit"}).format(new Date())}`);
}
+async function runDiagnostics() {
+ const list = byId("diagnostic-list");
+ const summary = byId("diagnostic-summary");
+ const checks = [
+ ["Сессия", "identity", () => request("/api/session/current")],
+ ["Политика совета", "advisory", () => request("/api/advisory-policy")],
+ ["Когнитивная возможность", "capability", () => request("/api/capabilities/cognitive-proposal/health")],
+ ["Сегодня", "today", () => request("/api/today")],
+ ["Внимание", "attention", () => request("/api/attention")],
+ ["Действия", "actions", () => request("/api/actions?limit=1")],
+ ["Learning evidence", "learning", () => request("/api/learning/recent?limit=1")],
+ ];
+ list.setAttribute("aria-busy", "true");
+ list.replaceChildren();
+ checks.forEach(([label, key]) => list.append(createDiagnosticItem(label, key, "loading")));
+ setText("diagnostic-summary", "Проверяю…");
+ const results = await Promise.all(checks.map(async ([label, key, run]) => {
+ const started = performance.now();
+ try {
+ await run();
+ return {label, key, state: "ok", detail: `Ответ получен · ${Math.round(performance.now() - started)} мс`};
+ } catch (_) {
+ return {label, key, state: "error", detail: "Сервис или контекст недоступен"};
+ }
+ }));
+ list.replaceChildren(...results.map((result) => createDiagnosticItem(result.label, result.key, result.state, result.detail)));
+ list.setAttribute("aria-busy", "false");
+ const passed = results.filter((result) => result.state === "ok").length;
+ setText("diagnostic-summary", `${passed}/${results.length} проверок пройдено`);
+}
+
+function createDiagnosticItem(label, key, state, detail = "Проверяю…") {
+ const item = document.createElement("article");
+ item.className = "diagnostic-item";
+ item.dataset.state = state;
+ item.dataset.check = key;
+ const marker = document.createElement("i");
+ marker.setAttribute("aria-hidden", "true");
+ const copy = document.createElement("div");
+ const title = document.createElement("strong");
+ title.textContent = label;
+ const description = document.createElement("span");
+ description.textContent = detail;
+ copy.append(title, description);
+ const status = document.createElement("em");
+ status.textContent = state === "ok" ? "OK" : state === "error" ? "ошибка" : "…";
+ item.append(marker, copy, status);
+ return item;
+}
+
async function loadLearning() {
const experienceList = byId("experience-list");
const reflectionList = byId("reflection-list");
diff --git a/cmd/creator-web/static/index.html b/cmd/creator-web/static/index.html
index 86e9619..aa1ab78 100644
--- a/cmd/creator-web/static/index.html
+++ b/cmd/creator-web/static/index.html
@@ -44,6 +44,12 @@
@@ -68,6 +74,12 @@
+
ACTOR
—
@@ -77,7 +89,7 @@
-
+
операционная картина
Состояние сейчас
@@ -92,6 +104,20 @@
ВОЗМОЖНОСТЬожидает
+
+
+
+
+
Здесь видны только ответы текущей авторизованной сессии. Проверка не меняет данные и помогает отличить отсутствие контекста от недоступности сервиса.
+
+
+
Нажмите «Проверить всё», чтобы получить актуальный снимок.
+
+
+
COGNITIVE / ADVISORY V1
@@ -120,7 +146,7 @@
-
+
Continuity / ближайшие
Реестр ожиданий
0 ЗАПИСЕЙ
@@ -167,6 +193,19 @@
+
+
+ Локальные предпочтения
Настройки рабочего кабинета
+ Сохраняются в этом браузере
+
+
+
+
diff --git a/dev/ACTIVE_TASK.json b/dev/ACTIVE_TASK.json
index 268f982..2a3d773 100644
--- a/dev/ACTIVE_TASK.json
+++ b/dev/ACTIVE_TASK.json
@@ -1,5 +1,5 @@
{
- "task_file": "dev/tasks/DEV-000073-production-compose-fail-closed.json",
- "task_id": "DEV-000073",
+ "task_file": "dev/tasks/DEV-000074-creator-cabinet-operations.json",
+ "task_id": "DEV-000074",
"task_revision": 1
}
diff --git a/dev/tasks/DEV-000074-creator-cabinet-operations.json b/dev/tasks/DEV-000074-creator-cabinet-operations.json
new file mode 100644
index 0000000..d4db07e
--- /dev/null
+++ b/dev/tasks/DEV-000074-creator-cabinet-operations.json
@@ -0,0 +1,92 @@
+{
+ "task_id": "DEV-000074",
+ "task_revision": 1,
+ "architecture_revision": "DEV-000073-baseline",
+ "base_repo_revision": "407275c",
+ "risk_tier": "E",
+ "intent": "Make the authenticated Creator Web cabinet understandable and operable for a human by adding section navigation, read-only diagnostics, and browser-local interface preferences without changing domain state or authority boundaries.",
+ "requirement_ids": [
+ "Human can move between overview, diagnostics, operations and settings",
+ "Human can run all currently exposed read-only Creator checks from one diagnostic panel",
+ "Interface preferences remain local to the browser and never change canonical state",
+ "Existing explicit confirmation remains required for canonical mutations",
+ "Desktop and mobile layouts remain usable"
+ ],
+ "allowed_change_scope": {
+ "modules": ["cmd/creator-web"],
+ "paths": [
+ "cmd/creator-web/static/index.html",
+ "cmd/creator-web/static/assets/console.css",
+ "cmd/creator-web/static/assets/console.js",
+ "cmd/creator-web/server_test.go",
+ "cmd/creator-web/chat_test.go",
+ "docs/PROJECT_STATUS.md",
+ "dev/ACTIVE_TASK.json",
+ "dev/tasks/DEV-000074-creator-cabinet-operations.json"
+ ]
+ },
+ "forbidden_change_scope": {
+ "paths": [
+ "architecture/**",
+ "security/**",
+ "database/**",
+ "modules/**",
+ "contracts/**",
+ "adapters/**",
+ "cmd/core-runtime/**",
+ "cmd/commit-runtime/**",
+ "cmd/continuity-runtime/**",
+ "deploy/**"
+ ]
+ },
+ "allowed_architecture_delta": {
+ "new_cross_module_dependencies": 0,
+ "new_canonical_owners": 0,
+ "new_privileges": 0,
+ "new_third_party_dependencies": 0,
+ "new_provider_adapters": 0,
+ "new_database_owner_roles": 0
+ },
+ "contracts": {
+ "consume": ["existing authenticated Creator gateway endpoints"],
+ "may_change": []
+ },
+ "semantic_owner": "none-interface-adapter",
+ "canonical_state": [],
+ "state_transitions": [
+ "diagnostic request -> read-only gateway observation",
+ "settings form submit -> browser-local preference update"
+ ],
+ "side_effects_and_privileges": [
+ "No new privilege or mutation endpoint",
+ "No database migration",
+ "Existing Commit actions retain explicit confirmation dialogs",
+ "No deployment or neighboring project mutation"
+ ],
+ "persistence_and_migrations": ["No canonical persistence; localStorage only for interface preferences"],
+ "failure_retry_restart": ["Diagnostic failures are shown per check without exposing upstream error details"],
+ "assumptions": [
+ "Existing Creator API routes remain the source of truth",
+ "Browser-local settings are disposable and do not affect domain behavior"
+ ],
+ "acceptance_scenarios": [
+ "Authenticated cabinet exposes navigation to all four human-facing sections",
+ "read-only diagnostics checks session, advisory policy, capability, today, attention, actions and learning",
+ "density, auto-refresh and reduced-motion preferences save and reset locally",
+ "mobile layout remains readable",
+ "existing Creator Web tests pass",
+ "architecture and diff scope checks pass"
+ ],
+ "required_tests": [
+ "node --check cmd/creator-web/static/assets/console.js",
+ "go test ./cmd/creator-web",
+ "go test ./...",
+ "go vet ./...",
+ "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json",
+ "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json",
+ "go run ./cmd/devctl check-architecture",
+ "git diff --check",
+ "responsive browser inspection"
+ ],
+ "terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT"]
+}