create-principles-disciple 1.144.24 → 1.144.26
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/_release/product-identity.json +2 -2
- package/codex-adapter/dist/pd-hook.js +84 -9
- package/codex-adapter/package.json +1 -1
- package/console/dist/ui/App.js +19 -3
- package/console/dist/ui/components/layout/app-sidebar.d.ts +11 -0
- package/console/dist/ui/components/layout/app-sidebar.js +60 -7
- package/console/dist/ui/components/ui/sheet.js +2 -1
- package/console/dist/ui/i18n/en.json +66 -5
- package/console/dist/ui/i18n/index.js +11 -0
- package/console/dist/ui/i18n/zh-CN.json +66 -5
- package/console/dist/ui/pages/control-center/ControlCenterPage.js +4 -2
- package/console/dist/ui/pages/control-center/RuntimeProfileManager.js +3 -3
- package/console/dist/ui/pages/focus/FocusPage.d.ts +92 -0
- package/console/dist/ui/pages/focus/FocusPage.js +139 -8
- package/console/dist/ui/pages/principles/PrincipleDetailPage.d.ts +4 -2
- package/console/dist/ui/pages/principles/PrincipleDetailPage.js +54 -16
- package/console/dist/ui/pages/settings/UpdatePage.js +1 -1
- package/console/dist/web/assets/app.css +109 -20
- package/console/dist/web/assets/app.js +59 -58
- package/core/dist/principle-tree-ledger.d.ts +20 -0
- package/core/dist/principle-tree-ledger.d.ts.map +1 -1
- package/core/dist/principle-tree-ledger.js +13 -9
- package/core/dist/principle-tree-ledger.js.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/activation-dispatcher.js +10 -3
- package/core/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
- package/core/dist/runtime-v2/activation/ledger-identity.d.ts +12 -3
- package/core/dist/runtime-v2/activation/ledger-identity.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/ledger-identity.js +18 -3
- package/core/dist/runtime-v2/activation/ledger-identity.js.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts +8 -0
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js +11 -1
- package/core/dist/runtime-v2/adapter/principle-tree-ledger-adapter.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts +61 -2
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js +68 -3
- package/core/dist/runtime-v2/internalization/artificer-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-runner.js +5 -0
- package/core/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts +18 -0
- package/core/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-output.js +7 -0
- package/core/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts +36 -2
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js +31 -1
- package/core/dist/runtime-v2/internalization/evaluator-prompt-builder.js.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts +52 -12
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +180 -48
- package/core/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/core/dist/runtime-v2/internalization/repair-replay-resolver.d.ts +8 -0
- package/core/dist/runtime-v2/internalization/repair-replay-resolver.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/repair-replay-resolver.js +12 -0
- package/core/dist/runtime-v2/internalization/repair-replay-resolver.js.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts +4 -0
- package/core/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.js +13 -1
- package/core/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +3 -2
- package/core/dist/telemetry-event.js.map +1 -1
- package/core/package.json +1 -1
- package/host-runtime/dist/internalization-consumer-cycle.js +6 -0
- package/host-runtime/package.json +1 -1
- package/package.json +1 -1
- package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-retry.js +9 -3
- package/pd-cli/dist/commands/pain-retry.js.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-internalization-run-once.js +3 -0
- package/pd-cli/dist/commands/runtime-internalization-run-once.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +6 -0
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +449 -440
- package/release-manager/package.json +1 -1
|
@@ -365,14 +365,75 @@ export async function processHookInvocation(rawStdin, _env = process.env, cwd =
|
|
|
365
365
|
return { stdout: {}, exitCode: 0, stderr: [diagnostic(reason, nextAction)] };
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
|
+
// PRI-943: process-boundary fail-open belt. Every resolved path sets exitCode 0,
|
|
369
|
+
// so nothing may let an unwritable stream, an unserializable result, or an
|
|
370
|
+
// escaping rejection reach node's default handling, which terminates the hook
|
|
371
|
+
// with exit 1. Three distinct failure modes, each reproduced from a spawned
|
|
372
|
+
// process in tests/pd-hook-entry-belt.test.ts:
|
|
373
|
+
// * a write that THROWS synchronously — caught per write below;
|
|
374
|
+
// * a host closing the pipe, which raises an ASYNC 'error' event on the
|
|
375
|
+
// Socket. try/catch cannot reach that one, so a listener is required
|
|
376
|
+
// (armStreamBelts) or node aborts with "Unhandled 'error' event".
|
|
377
|
+
// * the stdout JSON object failing to SERIALIZE — guarded separately because
|
|
378
|
+
// the object is the contract (serializeStdout).
|
|
379
|
+
// stdout must stay exactly one JSON object, so a belt diagnostic goes to stderr
|
|
380
|
+
// only; when stderr itself is the dead stream nothing is reported rather than
|
|
381
|
+
// written into the machine channel.
|
|
382
|
+
let beltReported = false;
|
|
383
|
+
let stdoutJsonDelivered = false;
|
|
384
|
+
function writeBeltDiagnostic(reason, nextAction) {
|
|
385
|
+
if (beltReported)
|
|
386
|
+
return;
|
|
387
|
+
beltReported = true;
|
|
388
|
+
try {
|
|
389
|
+
process.stderr.write(`${diagnostic(reason, nextAction)}\n`);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// Both streams are gone: there is no channel left inside the fail-open
|
|
393
|
+
// contract, and adding I/O here would widen the belt itself.
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function reportStreamFailure(error) {
|
|
397
|
+
writeBeltDiagnostic(`hook_stream_failed:${errorMessage(error)}`, 'The host closed the pipe; retry the tool call — the hook failed open.');
|
|
398
|
+
}
|
|
399
|
+
function writeStream(stream, line) {
|
|
400
|
+
try {
|
|
401
|
+
process[stream].write(`${line}\n`);
|
|
402
|
+
// Tracked here rather than at each call site so no future stdout write can
|
|
403
|
+
// make the entry belt believe nothing was delivered yet.
|
|
404
|
+
if (stream === 'stdout')
|
|
405
|
+
stdoutJsonDelivered = true;
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
reportStreamFailure(error);
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function armStreamBelts() {
|
|
414
|
+
process.stdout.on('error', reportStreamFailure);
|
|
415
|
+
process.stderr.on('error', reportStreamFailure);
|
|
416
|
+
}
|
|
417
|
+
/** rc-8: the stdout JSON object is the contract itself, so serializing it is a
|
|
418
|
+
* failure path of the belt rather than of the encoder — degrade to `{}` and
|
|
419
|
+
* report, never let the throw escape into the entry belt's last-resort branch. */
|
|
420
|
+
function serializeStdout(value) {
|
|
421
|
+
try {
|
|
422
|
+
return JSON.stringify(value) ?? '{}';
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
writeBeltDiagnostic(`hook_stdout_serialize_failed:${errorMessage(error)}`, 'Retry the tool call; the hook failed open with an empty JSON object.');
|
|
426
|
+
return '{}';
|
|
427
|
+
}
|
|
428
|
+
}
|
|
368
429
|
async function main() {
|
|
369
430
|
let raw;
|
|
370
431
|
try {
|
|
371
432
|
raw = readFileSync(0, 'utf8');
|
|
372
433
|
}
|
|
373
434
|
catch (error) {
|
|
374
|
-
|
|
375
|
-
|
|
435
|
+
writeStream('stderr', diagnostic(`stdin_read_failed:${errorMessage(error)}`, 'Run the hook from Codex with JSON stdin.'));
|
|
436
|
+
writeStream('stdout', '{}');
|
|
376
437
|
return;
|
|
377
438
|
}
|
|
378
439
|
let result;
|
|
@@ -383,12 +444,16 @@ async function main() {
|
|
|
383
444
|
// Fail-open belt for an unexpected pre-dispatch throw (e.g. a workspace
|
|
384
445
|
// resolution race): Codex must still receive exactly one JSON object on
|
|
385
446
|
// stdout and a bounded diagnostic on stderr — never a bare crash.
|
|
386
|
-
|
|
447
|
+
writeStream('stderr', diagnostic(`hook_pipeline_unexpected:${errorMessage(error)}`, 'Retry the tool call; if it repeats, inspect PD stderr and the Workspace .pd/config.yaml state.'));
|
|
387
448
|
result = { stdout: {}, exitCode: 0, stderr: [] };
|
|
388
449
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
450
|
+
// Each write is guarded on its own: with stderr dead the stdout JSON object
|
|
451
|
+
// is still what Codex needs to fail open, so one stream must not abort the other.
|
|
452
|
+
for (const line of result.stderr) {
|
|
453
|
+
if (!writeStream('stderr', line))
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
writeStream('stdout', serializeStdout(result.stdout));
|
|
392
457
|
process.exitCode = result.exitCode;
|
|
393
458
|
}
|
|
394
459
|
// PRI-892: Node resolves the main module through symlinks/junctions while
|
|
@@ -414,9 +479,19 @@ function isMainModuleEntry() {
|
|
|
414
479
|
// satisfies it, because its argv[1] is the runner, not pd-hook.
|
|
415
480
|
if (path.basename(entry) !== path.basename(fileURLToPath(import.meta.url)))
|
|
416
481
|
return false;
|
|
417
|
-
|
|
482
|
+
writeStream('stderr', diagnostic(`entry_identity_unverified:${errorMessage(error)}`, 'Reinstall the Codex adapter; if it repeats, report this line together with the command that invoked the hook.'));
|
|
418
483
|
return true;
|
|
419
484
|
}
|
|
420
485
|
}
|
|
421
|
-
if (isMainModuleEntry())
|
|
422
|
-
|
|
486
|
+
if (isMainModuleEntry()) {
|
|
487
|
+
armStreamBelts();
|
|
488
|
+
void main().catch((error) => {
|
|
489
|
+
// Last-resort belt: a rejection escaping main's own guards must still leave
|
|
490
|
+
// Codex with exit 0 and, when nothing reached stdout yet, an empty JSON
|
|
491
|
+
// object instead of a bare crash.
|
|
492
|
+
writeBeltDiagnostic(`hook_fatal:${errorMessage(error)}`, 'Retry the tool call; if it repeats, report this line together with the command that invoked the hook.');
|
|
493
|
+
if (!stdoutJsonDelivered)
|
|
494
|
+
writeStream('stdout', '{}');
|
|
495
|
+
process.exitCode = 0;
|
|
496
|
+
});
|
|
497
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/console/dist/ui/App.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback } from "react";
|
|
3
3
|
import { HashRouter, Routes, Route, Navigate, useLocation, useNavigate } from "react-router-dom";
|
|
4
|
-
import { I18nextProvider } from "react-i18next";
|
|
4
|
+
import { I18nextProvider, useTranslation } from "react-i18next";
|
|
5
5
|
import i18n from "./i18n/index.js";
|
|
6
6
|
import { ThemeProvider } from "./components/theme-provider.js";
|
|
7
|
-
import { AppSidebar } from "./components/layout/app-sidebar.js";
|
|
7
|
+
import { AppSidebar, MobileNav } from "./components/layout/app-sidebar.js";
|
|
8
8
|
import { SplashScreen } from "./components/auth/splash-screen.js";
|
|
9
9
|
import { LoginForm } from "./components/auth/login-form.js";
|
|
10
10
|
import { ErrorBoundary } from "./components/error-boundary.js";
|
|
@@ -31,6 +31,22 @@ import { getOnboardingState } from "./utils/onboarding-state.js";
|
|
|
31
31
|
// Vite replaces import.meta.env.DEV with a boolean literal at build time.
|
|
32
32
|
// The cast avoids TS2339 without needing a generated .d.ts file.
|
|
33
33
|
const IS_DEV = import.meta.env.DEV;
|
|
34
|
+
/**
|
|
35
|
+
* SPEC-PD-UIUX-001 R4: the skip control must NOT touch the URL hash — the app
|
|
36
|
+
* routes through HashRouter, so an `#main-content` anchor navigates away and
|
|
37
|
+
* blanks the page. Focus main programmatically instead; the route is kept.
|
|
38
|
+
*/
|
|
39
|
+
function skipToMainContent() {
|
|
40
|
+
const main = document.getElementById("main-content");
|
|
41
|
+
if (main instanceof HTMLElement) {
|
|
42
|
+
main.focus({ preventScroll: true });
|
|
43
|
+
main.scrollIntoView({ block: "start", behavior: "auto" });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function AuthenticatedShell({ children, featureFlags }) {
|
|
47
|
+
const { t } = useTranslation();
|
|
48
|
+
return (_jsxs("div", { className: "min-h-screen bg-paper text-ink", children: [_jsx("button", { type: "button", onClick: skipToMainContent, "data-testid": "skip-to-main", className: "sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-paper focus:text-ink focus:font-medium", children: t("common.skipToMain", { defaultValue: "跳到正文" }) }), _jsx(MobileNav, { featureFlags: featureFlags }), _jsx(AppSidebar, { featureFlags: featureFlags }), _jsx("main", { id: "main-content", tabIndex: -1, "data-testid": "main-content", className: "min-h-screen overflow-y-auto pt-14 lg:pt-0 lg:ml-[256px] focus:outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-gov", children: children })] }));
|
|
49
|
+
}
|
|
34
50
|
function AuthRoutes() {
|
|
35
51
|
const [authed, setAuthed] = useState(null);
|
|
36
52
|
const [showSplash, setShowSplash] = useState(() => window.location.hash.startsWith("#/splash"));
|
|
@@ -146,7 +162,7 @@ function AuthRoutes() {
|
|
|
146
162
|
}, []);
|
|
147
163
|
return (_jsxs(Routes, { children: [_jsx(Route, { path: "/splash", element: _jsx(SplashScreen, { onComplete: () => {
|
|
148
164
|
setShowSplash(false);
|
|
149
|
-
} }) }), _jsx(Route, { path: "/login", element: _jsx(LoginForm, { onAuthSuccess: handleAuthSuccess }) }), _jsx(Route, { path: "/*", element: authed === null ? (_jsx("div", { className: "min-h-screen bg-paper flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("div", { className: "w-8 h-8 border-2 border-gov border-t-transparent rounded-full animate-spin" }), _jsx("span", { className: "text-[12px] font-mono tracking-wider text-ink-3 uppercase", children: "Verifying Session..." })] }) })) : authed ? (_jsx(NotificationProvider, { children:
|
|
165
|
+
} }) }), _jsx(Route, { path: "/login", element: _jsx(LoginForm, { onAuthSuccess: handleAuthSuccess }) }), _jsx(Route, { path: "/*", element: authed === null ? (_jsx("div", { className: "min-h-screen bg-paper flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [_jsx("div", { className: "w-8 h-8 border-2 border-gov border-t-transparent rounded-full animate-spin" }), _jsx("span", { className: "text-[12px] font-mono tracking-wider text-ink-3 uppercase", children: "Verifying Session..." })] }) })) : authed ? (_jsx(NotificationProvider, { children: _jsx(AuthenticatedShell, { featureFlags: featureFlags ?? undefined, children: _jsx(ErrorBoundary, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: "/focus", replace: true }) }), _jsx(Route, { path: "/welcome", element: _jsx(WelcomePage, { workspaceId: currentWorkspaceId }) }), _jsx(Route, { path: "/focus", element: _jsx(FocusPage, { featureFlags: featureFlags ?? undefined }) }), _jsx(Route, { path: "/pain", element: _jsx(PainPage, {}) }), _jsx(Route, { path: "/principles", element: _jsx(PrinciplesPage, {}) }), _jsx(Route, { path: "/principles/core", element: _jsx(CorePrinciplesPage, {}) }), _jsx(Route, { path: "/principles/:id", element: _jsx(PrincipleDetailPage, {}) }), _jsx(Route, { path: "/activation", element: _jsx(ActivationPage, {}) }), _jsx(Route, { path: "/debt", element: _jsx(DebtPage, {}) }), _jsx(Route, { path: "/control-center", element: _jsx(ControlCenterPage, {}) }), _jsx(Route, { path: "/settings", element: _jsx(SettingsPage, {}) }), _jsx(Route, { path: "/update", element: _jsx(UpdatePage, {}) }), _jsx(Route, { path: "/report-problem", element: _jsx(ReportProblemPage, {}) }), _jsx(Route, { path: "/failed-tasks", element: _jsx(FailedTasksPage, {}) }), _jsx(Route, { path: "/intent", element: _jsx(IntentPage, {}) }), IS_DEV && (_jsx(Route, { path: "/design-system", element: _jsx(DesignSystemPage, {}) })), !IS_DEV && (_jsx(Route, { path: "/design-system", element: _jsx(Navigate, { to: "/focus", replace: true }) }))] }) }) }) })) : (_jsx(Navigate, { to: "/login", replace: true })) })] }));
|
|
150
166
|
}
|
|
151
167
|
export function App() {
|
|
152
168
|
return (_jsx(ThemeProvider, { children: _jsxs(I18nextProvider, { i18n: i18n, children: [_jsx(HashRouter, { children: _jsx(AuthRoutes, {}) }), _jsx(Toaster, {})] }) }));
|
|
@@ -4,3 +4,14 @@ export declare function AppSidebar({ featureFlags }: {
|
|
|
4
4
|
enabled: boolean;
|
|
5
5
|
}>;
|
|
6
6
|
}): React.JSX.Element;
|
|
7
|
+
/**
|
|
8
|
+
* Narrow-screen shell replacement for the fixed 256px sidebar (R3): a top bar
|
|
9
|
+
* with a 44px menu button opening a left Sheet drawer. Radix Dialog provides
|
|
10
|
+
* focus trapping, Esc-to-close and focus return to the trigger; selecting a
|
|
11
|
+
* route closes the drawer. Desktop keeps the fixed sidebar (lg and up).
|
|
12
|
+
*/
|
|
13
|
+
export declare function MobileNav({ featureFlags }: {
|
|
14
|
+
featureFlags?: Record<string, {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
}>;
|
|
17
|
+
}): React.JSX.Element;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
2
3
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
|
3
|
-
import { Sun, Moon, Focus, AlertTriangle, AlertCircle, BookOpen, Zap, Archive, Compass, Settings, MessageSquare, RefreshCw, LogOut, Languages, } from "lucide-react";
|
|
4
|
+
import { Sun, Moon, Focus, AlertTriangle, AlertCircle, BookOpen, Zap, Archive, Compass, Settings, MessageSquare, RefreshCw, LogOut, Languages, Menu, } from "lucide-react";
|
|
4
5
|
import { useTheme } from "../theme-provider.js";
|
|
5
6
|
import { useTranslation } from "react-i18next";
|
|
6
7
|
import { useNotifications } from "../notifications/useNotifications.js";
|
|
7
8
|
import { clearToken } from "../../api.js";
|
|
8
9
|
import { cn } from "../../../lib/utils.js";
|
|
10
|
+
import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "../ui/sheet.js";
|
|
9
11
|
function ThresholdMark({ className }) {
|
|
10
12
|
return (_jsxs("svg", { viewBox: "0 0 28 28", className: className, fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: [_jsx("path", { d: "M6 4V24M22 4V24M2 14H26", strokeLinecap: "square" }), _jsx("circle", { cx: "14", cy: "14", r: "2.5", fill: "currentColor", stroke: "none" })] }));
|
|
11
13
|
}
|
|
@@ -24,7 +26,12 @@ const toolNavItems = [
|
|
|
24
26
|
{ id: "settings", labelKey: "components.sidebar.settings", href: "/settings", icon: Settings },
|
|
25
27
|
{ id: "update", labelKey: "components.sidebar.update", href: "/update", icon: RefreshCw },
|
|
26
28
|
];
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Shared sidebar body — rendered by the desktop aside and by the narrow-screen
|
|
31
|
+
* drawer (SPEC-PD-UIUX-001 R3). Display-only: navigation targets and flags are
|
|
32
|
+
* unchanged; `onNavigate` lets the drawer close after a selection.
|
|
33
|
+
*/
|
|
34
|
+
function SidebarContent({ featureFlags, onNavigate, touchTargets, }) {
|
|
28
35
|
const location = useLocation();
|
|
29
36
|
const navigate = useNavigate();
|
|
30
37
|
const { theme, setTheme } = useTheme();
|
|
@@ -44,10 +51,10 @@ export function AppSidebar({ featureFlags }) {
|
|
|
44
51
|
i18n.changeLanguage(nextLang);
|
|
45
52
|
localStorage.setItem("pd-language", nextLang);
|
|
46
53
|
};
|
|
47
|
-
return (_jsxs(
|
|
54
|
+
return (_jsxs(_Fragment, { children: [_jsxs("nav", { className: "flex-1 py-2 overflow-y-auto", "aria-label": t("components.sidebar.navigation", { defaultValue: "主导航" }), children: [_jsx("div", { className: "px-2", children: mainNavItems.map((item) => {
|
|
48
55
|
const Icon = item.icon;
|
|
49
56
|
const active = isActive(item.href);
|
|
50
|
-
return (_jsxs(Link, { to: item.href, "data-testid": `nav-${item.id}`, className: cn("flex items-center gap-3 px-3
|
|
57
|
+
return (_jsxs(Link, { to: item.href, "data-testid": `nav-${item.id}`, "aria-current": active ? "page" : undefined, onClick: onNavigate, className: cn("flex items-center gap-3 px-3 rounded-[var(--radius-sm)] text-[13px] transition-all duration-150", touchTargets ? "min-h-[44px] py-2" : "py-2", active
|
|
51
58
|
? "border-l-2 border-l-gov bg-paper-2 text-ink"
|
|
52
59
|
: "border-l-2 border-l-transparent text-ink-3 hover:text-ink hover:bg-paper-2"), children: [_jsx(Icon, { className: "h-4 w-4 flex-shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "flex-1", children: t(item.labelKey) }), item.id === "focus" && pendingCount > 0 ? (_jsx("span", { className: "ml-auto h-2 w-2 rotate-45 rounded-[1px] border border-rose-400/50 bg-rose-400/15 shadow-[0_0_3px_rgba(251,113,133,0.35)]", role: "status", "aria-label": t("components.sidebar.pendingApprovalsAria", { count: pendingCount }) })) : null] }, item.id));
|
|
53
60
|
}) }), _jsxs("div", { className: "mt-4 pt-3 border-t border-line mx-4", children: [_jsx("div", { className: "px-1 pb-1 text-[10px] font-mono tracking-[0.1em] uppercase text-ink-4", children: "Tools" }), _jsx("div", { className: "px-0", children: toolNavItems.map((item) => {
|
|
@@ -60,8 +67,54 @@ export function AppSidebar({ featureFlags }) {
|
|
|
60
67
|
}
|
|
61
68
|
const Icon = item.icon;
|
|
62
69
|
const active = isActive(item.href);
|
|
63
|
-
return (_jsxs(Link, { to: item.href, "data-testid": `nav-${item.id}`, className: cn("flex items-center gap-3 px-3
|
|
70
|
+
return (_jsxs(Link, { to: item.href, "data-testid": `nav-${item.id}`, "aria-current": active ? "page" : undefined, onClick: onNavigate, className: cn("flex items-center gap-3 px-3 rounded-[var(--radius-sm)] text-[12px] transition-all duration-150", touchTargets ? "min-h-[44px] py-2" : "py-1.5", active
|
|
64
71
|
? "border-l-2 border-l-gov bg-paper-2 text-ink"
|
|
65
72
|
: "border-l-2 border-l-transparent text-ink-4 hover:text-ink-3 hover:bg-paper-2"), children: [_jsx(Icon, { className: "h-3.5 w-3.5 flex-shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "flex-1", children: t(item.labelKey) }), item.id === "control-center" && degradedCount > 0 ? (_jsx("span", { className: "ml-auto h-[7px] w-[7px] rotate-45 rounded-[1px] border border-amber-400/50 bg-amber-400/15 shadow-[0_0_3px_rgba(251,191,36,0.35)]", role: "status", "aria-label": t("components.sidebar.degradedSignalsAria", { count: degradedCount }) })) : null] }, item.id));
|
|
66
|
-
}) })] })] }), _jsxs("div", { className: "px-3 py-3 border-t border-line space-y-1", children: [_jsxs("button", { type: "button", onClick: () => setTheme(theme === "dark" ? "light" : "dark"), className: "flex items-center gap-3 px-3
|
|
73
|
+
}) })] })] }), _jsxs("div", { className: "px-3 py-3 border-t border-line space-y-1", children: [_jsxs("button", { type: "button", onClick: () => setTheme(theme === "dark" ? "light" : "dark"), className: cn("flex items-center gap-3 px-3 rounded-[var(--radius-sm)] text-[12px] text-ink-4 hover:text-ink-3 hover:bg-paper-2 transition-colors w-full", touchTargets ? "min-h-[44px] py-2" : "py-1.5"), title: theme === "dark" ? t("components.sidebar.themeToLight") : t("components.sidebar.themeToDark"), children: [theme === "dark" ? (_jsx(Sun, { className: "h-3.5 w-3.5 flex-shrink-0", "aria-hidden": "true" })) : (_jsx(Moon, { className: "h-3.5 w-3.5 flex-shrink-0", "aria-hidden": "true" })), _jsx("span", { children: theme === "dark" ? t("components.sidebar.themeToLight") : t("components.sidebar.themeToDark") })] }), _jsxs("button", { type: "button", onClick: toggleLanguage, className: cn("flex items-center gap-3 px-3 rounded-[var(--radius-sm)] text-[12px] text-ink-4 hover:text-ink-3 hover:bg-paper-2 transition-colors w-full", touchTargets ? "min-h-[44px] py-2" : "py-1.5"), title: t("components.sidebar.switchLanguage"), children: [_jsx(Languages, { className: "h-3.5 w-3.5 flex-shrink-0", "aria-hidden": "true" }), _jsx("span", { children: i18n.language === "zh-CN" ? "English" : "中文" })] }), _jsxs("button", { type: "button", onClick: handleSignOut, className: cn("flex items-center gap-3 px-3 rounded-[var(--radius-sm)] text-[12px] text-ink-4 hover:text-ink-3 hover:bg-paper-2 transition-colors w-full", touchTargets ? "min-h-[44px] py-2" : "py-1.5"), children: [_jsx(LogOut, { className: "h-3.5 w-3.5 flex-shrink-0", "aria-hidden": "true" }), _jsx("span", { children: t("components.sidebar.signOut") })] })] })] }));
|
|
74
|
+
}
|
|
75
|
+
export function AppSidebar({ featureFlags }) {
|
|
76
|
+
return (_jsxs("aside", { className: "w-[256px] h-screen fixed left-0 top-0 hidden lg:flex flex-col bg-surface border-r border-line flex-shrink-0 z-20", children: [_jsx("div", { className: "px-5 pt-5 pb-4", children: _jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx(ThresholdMark, { className: "w-6 h-6 text-gov" }), _jsxs("div", { children: [_jsx("div", { className: "font-mono text-[14px] tracking-[0.16em] font-bold text-ink", children: "PD" }), _jsx("div", { className: "font-mono text-[10px] tracking-[0.14em] text-ink-3", children: "GOVERNANCE WORKSPACE" })] })] }) }), _jsx(SidebarContent, { featureFlags: featureFlags })] }));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Narrow-screen shell replacement for the fixed 256px sidebar (R3): a top bar
|
|
80
|
+
* with a 44px menu button opening a left Sheet drawer. Radix Dialog provides
|
|
81
|
+
* focus trapping, Esc-to-close and focus return to the trigger; selecting a
|
|
82
|
+
* route closes the drawer. Desktop keeps the fixed sidebar (lg and up).
|
|
83
|
+
*/
|
|
84
|
+
export function MobileNav({ featureFlags }) {
|
|
85
|
+
const { t } = useTranslation();
|
|
86
|
+
const [open, setOpen] = React.useState(false);
|
|
87
|
+
const location = useLocation();
|
|
88
|
+
// Route changes (e.g. browser back/forward) close the drawer so the main
|
|
89
|
+
// content is never left behind a stale overlay.
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
setOpen(false);
|
|
92
|
+
}, [location.pathname]);
|
|
93
|
+
// SPEC-PD-UIUX-001 R3 (Owner review Q2): the Sheet portals to <body>, so
|
|
94
|
+
// growing a 390px window to desktop left the overlay + focus trap alive over
|
|
95
|
+
// the now-visible fixed sidebar. Crossing INTO the desktop breakpoint closes
|
|
96
|
+
// the drawer; where focus lands is decided in onCloseAutoFocus below (the
|
|
97
|
+
// hidden trigger cannot receive it on desktop).
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
const desktop = window.matchMedia("(min-width: 1024px)");
|
|
100
|
+
const onChange = (event) => {
|
|
101
|
+
if (event.matches && open) {
|
|
102
|
+
setOpen(false);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
desktop.addEventListener("change", onChange);
|
|
106
|
+
return () => desktop.removeEventListener("change", onChange);
|
|
107
|
+
}, [open]);
|
|
108
|
+
return (_jsxs("header", { className: "lg:hidden fixed top-0 left-0 right-0 z-30 h-14 flex items-center gap-2 px-3 bg-surface border-b border-line", children: [_jsxs(Sheet, { open: open, onOpenChange: setOpen, children: [_jsx(SheetTrigger, { asChild: true, children: _jsx("button", { type: "button", "data-testid": "mobile-nav-trigger", "aria-label": t("components.sidebar.openMenu"), className: "inline-flex items-center justify-center h-11 w-11 rounded-[var(--radius-sm)] text-ink-3 hover:text-ink hover:bg-paper-2 transition-colors", children: _jsx(Menu, { className: "h-5 w-5", "aria-hidden": "true" }) }) }), _jsxs(SheetContent, { side: "left", className: "w-[280px] max-w-[85vw] p-0 border-r border-line bg-surface flex flex-col", "data-testid": "mobile-nav-drawer", onCloseAutoFocus: (event) => {
|
|
109
|
+
// On desktop the trigger is display:none and cannot take focus
|
|
110
|
+
// back (review Q2): hand focus to the main content instead. On
|
|
111
|
+
// mobile keep Radix's default return-to-trigger behavior.
|
|
112
|
+
if (window.matchMedia("(min-width: 1024px)").matches) {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
const main = document.getElementById("main-content");
|
|
115
|
+
if (main instanceof HTMLElement) {
|
|
116
|
+
main.focus({ preventScroll: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, children: [_jsx(SheetTitle, { className: "sr-only", children: t("components.sidebar.navigation") }), _jsx(SheetDescription, { className: "sr-only", children: t("components.sidebar.navigationDescription") }), _jsx("div", { className: "px-5 pt-5 pb-4 border-b border-line", children: _jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx(ThresholdMark, { className: "w-6 h-6 text-gov" }), _jsxs("div", { children: [_jsx("div", { className: "font-mono text-[14px] tracking-[0.16em] font-bold text-ink", children: "PD" }), _jsx("div", { className: "font-mono text-[10px] tracking-[0.14em] text-ink-3", children: "GOVERNANCE WORKSPACE" })] })] }) }), _jsx(SidebarContent, { featureFlags: featureFlags, onNavigate: () => setOpen(false), touchTargets: true })] })] }), _jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx(ThresholdMark, { className: "w-5 h-5 text-gov flex-shrink-0" }), _jsx("span", { className: "font-mono text-[12px] tracking-[0.12em] font-bold text-ink truncate", children: "PD" })] })] }));
|
|
67
120
|
}
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { X } from "lucide-react";
|
|
6
|
+
import i18n from "../../i18n/index.js";
|
|
6
7
|
import { cn } from "../../../lib/utils.js";
|
|
7
8
|
const Sheet = SheetPrimitive.Root;
|
|
8
9
|
const SheetTrigger = SheetPrimitive.Trigger;
|
|
@@ -23,7 +24,7 @@ const sheetVariants = cva("fixed z-50 gap-4 bg-background p-6 shadow-lg transiti
|
|
|
23
24
|
side: "right",
|
|
24
25
|
},
|
|
25
26
|
});
|
|
26
|
-
const SheetContent = React.forwardRef(({ side = "right", className, children, ...props }, ref) => (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(SheetPrimitive.Content, { ref: ref, className: cn(sheetVariants({ side }), className), ...props, children: [_jsxs(SheetPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary", children: [_jsx(X, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "
|
|
27
|
+
const SheetContent = React.forwardRef(({ side = "right", className, children, ...props }, ref) => (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(SheetPrimitive.Content, { ref: ref, className: cn(sheetVariants({ side }), className), ...props, children: [_jsxs(SheetPrimitive.Close, { className: "absolute right-4 top-4 flex h-11 w-11 items-center justify-center rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary", children: [_jsx(X, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: i18n.t("common.close", { defaultValue: "关闭" }) })] }), children] })] })));
|
|
27
28
|
SheetContent.displayName = SheetPrimitive.Content.displayName;
|
|
28
29
|
const SheetHeader = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col gap-2 text-center sm:text-left", className), ...props }));
|
|
29
30
|
SheetHeader.displayName = "SheetHeader";
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"date": "Date",
|
|
44
44
|
"time": "Time",
|
|
45
45
|
"actions": "Actions",
|
|
46
|
-
"copied": "Copied"
|
|
46
|
+
"copied": "Copied",
|
|
47
|
+
"skipToMain": "Skip to main content"
|
|
47
48
|
},
|
|
48
49
|
"pages": {
|
|
49
50
|
"focus": {
|
|
@@ -395,6 +396,26 @@
|
|
|
395
396
|
"forbiddenBehavior": "Forbidden behavior",
|
|
396
397
|
"evidenceSource": "Evidence source",
|
|
397
398
|
"validationExpectation": "Validation expectation"
|
|
399
|
+
},
|
|
400
|
+
"conclusion": {
|
|
401
|
+
"sectionTitle": "Current status",
|
|
402
|
+
"allClear": "Nothing needs your attention right now.",
|
|
403
|
+
"pendingTotal": "{{count}} item(s) waiting on you",
|
|
404
|
+
"actionableUnlocked": "{{count}} of them actionable now",
|
|
405
|
+
"actionableLocked": "{{count}} actionable object(s) — governance actions are locked, see the notice above",
|
|
406
|
+
"countsNote": "Scope: \"waiting on you\" = actionable objects on screen + inbox pending items (decisions / individually-attended blocked / recovery) not already shown; each object counts once; historical unknowns stay explanatory and are not counted.",
|
|
407
|
+
"pendingUncertain": "Pending count cannot be confirmed — decision updates are unavailable.",
|
|
408
|
+
"actionableWithoutTotal": "{{count}} item(s) actionable now",
|
|
409
|
+
"degradedLabel": "Decision updates degraded:"
|
|
410
|
+
},
|
|
411
|
+
"ownerAttention": {
|
|
412
|
+
"sectionTitle": "Blocked & needing recovery",
|
|
413
|
+
"blockedCount": "blocked {{count}}",
|
|
414
|
+
"recoveryCount": "needing recovery {{count}}",
|
|
415
|
+
"empty": "No blocked or recovery items need individual attention.",
|
|
416
|
+
"unmatchedTitle": "These objects have decision updates but no available action surface yet (missing association or incomplete data — not merged away):",
|
|
417
|
+
"blockedAggregate": "{{count}} blocked object(s) need no individual attention and are not counted in the total above.",
|
|
418
|
+
"unmatchedItemNote": "No action surface available for this entry yet; open the principle page for its current state."
|
|
398
419
|
}
|
|
399
420
|
},
|
|
400
421
|
"pain": {
|
|
@@ -694,9 +715,8 @@
|
|
|
694
715
|
"receipts": {
|
|
695
716
|
"title": "Receipt History",
|
|
696
717
|
"headline": "{{effectCount}} behavior-influence record(s) retained (last {{lastEffectAt}}) — historical observations, not a claim of current enforcement",
|
|
697
|
-
"headlinePresence": "
|
|
718
|
+
"headlinePresence": "Context injections recorded — no matching behavior-effect records yet",
|
|
698
719
|
"counts": "{{effectCount}} behavior effect(s) · {{presenceCount}} context injection(s)",
|
|
699
|
-
"zeroEffect": "Why 0 effects? Effects need evidence: a block, an auto-correction, or an agent self-report. So far this principle has only entered task context (Presence). Being present in a decision does not mean behavior changed — the first effect record will appear here as runtime evidence accumulates.",
|
|
700
720
|
"empty": "No recent records — this principle has not been triggered at runtime yet.",
|
|
701
721
|
"note": "Two levels: Effect = the principle actually changed behavior (block / auto-correction / agent self-report); Presence = the principle entered task context (gray rows). Presence in a decision ≠ changed behavior. Self-reports are probabilistic agent claims, listed separately from deterministic events.",
|
|
702
722
|
"unavailableReason": "Receipt history is unavailable right now.",
|
|
@@ -723,7 +743,11 @@
|
|
|
723
743
|
"auto_correct_applied": "Auto-correct",
|
|
724
744
|
"self_reported": "Agent self-report",
|
|
725
745
|
"prompt_injected": "Context injection"
|
|
726
|
-
}
|
|
746
|
+
},
|
|
747
|
+
"headlineNone": "No context injection or effect records observed within the current retention window",
|
|
748
|
+
"zeroEffectPresence": "Why 0 effects? Effects need evidence: a block, an auto-correction, or an agent self-report. So far this principle only has context-presence records — being present in a decision does not mean behavior changed. The first effect record will appear here as runtime evidence accumulates.",
|
|
749
|
+
"zeroEffectNone": "Why both counts are 0: zero means no records observed within the current retention window — not that nothing ever happened. History outside the retention window or during disabled collection does not appear here. Counts start moving as soon as the principle enters task context or produces its first effect.",
|
|
750
|
+
"presenceMissingNote": "No context-injection (presence) records for this principle within the current retention window; what is listed below are the effect records themselves."
|
|
727
751
|
},
|
|
728
752
|
"trajectory": "View full trajectory when tracing is needed",
|
|
729
753
|
"stageEvidence": "Evidence",
|
|
@@ -949,6 +973,36 @@
|
|
|
949
973
|
"invalidTarget": "Action target missing; cannot submit.",
|
|
950
974
|
"actionDone": "Submitted.",
|
|
951
975
|
"actionFailed": "Refused: {{detail}} Available actions were refreshed — decide again from the latest state."
|
|
976
|
+
},
|
|
977
|
+
"evidenceAudit": {
|
|
978
|
+
"title": "Intervention evidence audit",
|
|
979
|
+
"headline": "Deliveries {{d}} · Applications {{a}} · Episodes {{e}} · Effects {{f}} · Outcomes {{o}}",
|
|
980
|
+
"unresolved": "{{count}} record(s) reference objects that have not arrived yet (pending association, not \"never happened\")",
|
|
981
|
+
"sectionDelivery": "Delivery",
|
|
982
|
+
"sectionApplication": "Application",
|
|
983
|
+
"sectionEpisode": "Behavior episode",
|
|
984
|
+
"sectionEffect": "Effect",
|
|
985
|
+
"sectionOutcome": "Outcome",
|
|
986
|
+
"emptySection": "None observed within this scope (not the same as never happened)",
|
|
987
|
+
"pending": "pending association",
|
|
988
|
+
"loading": "Loading…",
|
|
989
|
+
"loadOlder": "Load earlier records",
|
|
990
|
+
"capabilities": "Host capability declarations (Supported / Unsupported / Unknown)",
|
|
991
|
+
"technicalSummary": "Technical details (Phase 1 · revisions / digests / source identifiers)",
|
|
992
|
+
"technicalNote": "Engineering traceability data: evidence IDs, revision references, payload digests, activation and native lineage. The default view shows records and gaps; these fields do not change the semantics above.",
|
|
993
|
+
"sourceUnknown": "source verifiability unknown",
|
|
994
|
+
"contentRedacted": "content cleared on {{date}}",
|
|
995
|
+
"contentReference": "Content reference: {{resolution}} · {{principleId}}{{artifactId}}{{payloadDigest}}",
|
|
996
|
+
"referenceResolved": "resolved",
|
|
997
|
+
"referenceUnresolved": "revision unresolved",
|
|
998
|
+
"activationReference": "Activation: {{activationId}}{{activatedAt}}{{snapshotDigest}}",
|
|
999
|
+
"nativeLineage": "Native lineage: {{lineage}}",
|
|
1000
|
+
"note": "This section records facts only: delivery confirmation levels, agent self-reports, and runtime facts are listed separately; missing means unknown, not invalid. No success-rate or effectiveness scoring.",
|
|
1001
|
+
"unavailable": "Intervention evidence audit unavailable",
|
|
1002
|
+
"noPayloadDigest": "no payload digest",
|
|
1003
|
+
"noSnapshotDigest": "no snapshot digest",
|
|
1004
|
+
"payloadDigestLabel": "payload digest",
|
|
1005
|
+
"snapshotDigestLabel": "snapshot digest"
|
|
952
1006
|
}
|
|
953
1007
|
}
|
|
954
1008
|
},
|
|
@@ -1918,7 +1972,14 @@
|
|
|
1918
1972
|
"update": "Updates",
|
|
1919
1973
|
"pendingApprovalsAria": "{{count}} pending approvals",
|
|
1920
1974
|
"degradedSignalsAria": "{{count}} degraded signals",
|
|
1921
|
-
"intent": "Owner Intent"
|
|
1975
|
+
"intent": "Owner Intent",
|
|
1976
|
+
"themeToLight": "Switch to light mode",
|
|
1977
|
+
"themeToDark": "Switch to dark mode",
|
|
1978
|
+
"switchLanguage": "Switch language",
|
|
1979
|
+
"signOut": "Sign out",
|
|
1980
|
+
"openMenu": "Open navigation menu",
|
|
1981
|
+
"navigation": "Main navigation",
|
|
1982
|
+
"navigationDescription": "Navigate the console pages; the drawer closes after a selection."
|
|
1922
1983
|
},
|
|
1923
1984
|
"approvalCard": {
|
|
1924
1985
|
"confidence": {
|
|
@@ -7,6 +7,15 @@ const getInitialLanguage = () => {
|
|
|
7
7
|
return "zh-CN";
|
|
8
8
|
return localStorage.getItem("pd-language") || "zh-CN";
|
|
9
9
|
};
|
|
10
|
+
// SPEC-PD-UIUX-001 R6: keep the document language in sync with the UI language
|
|
11
|
+
// on first load AND on every switch, so assistive tech reads English pages with
|
|
12
|
+
// an English voice. Persistence stays in localStorage via the existing switcher.
|
|
13
|
+
function applyDocumentLanguage(lng) {
|
|
14
|
+
if (typeof document === "undefined")
|
|
15
|
+
return;
|
|
16
|
+
// eslint-disable-next-line no-undef
|
|
17
|
+
document.documentElement.lang = lng.toLowerCase();
|
|
18
|
+
}
|
|
10
19
|
i18n.use(initReactI18next).init({
|
|
11
20
|
resources: {
|
|
12
21
|
"zh-CN": { common: zhCN, pages: zhCN.pages, components: zhCN.components },
|
|
@@ -18,4 +27,6 @@ i18n.use(initReactI18next).init({
|
|
|
18
27
|
defaultNS: "common",
|
|
19
28
|
interpolation: { escapeValue: false },
|
|
20
29
|
});
|
|
30
|
+
applyDocumentLanguage(i18n.language ?? "zh-CN");
|
|
31
|
+
i18n.on("languageChanged", applyDocumentLanguage);
|
|
21
32
|
export default i18n;
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"date": "日期",
|
|
44
44
|
"time": "时间",
|
|
45
45
|
"actions": "操作",
|
|
46
|
-
"copied": "已复制"
|
|
46
|
+
"copied": "已复制",
|
|
47
|
+
"skipToMain": "跳到正文"
|
|
47
48
|
},
|
|
48
49
|
"pages": {
|
|
49
50
|
"focus": {
|
|
@@ -395,6 +396,26 @@
|
|
|
395
396
|
"forbiddenBehavior": "禁止行为",
|
|
396
397
|
"evidenceSource": "证据来源",
|
|
397
398
|
"validationExpectation": "验收要求"
|
|
399
|
+
},
|
|
400
|
+
"conclusion": {
|
|
401
|
+
"sectionTitle": "当前状态",
|
|
402
|
+
"allClear": "当前没有需要你处理的治理事项。",
|
|
403
|
+
"pendingTotal": "待你处理 {{count}} 项",
|
|
404
|
+
"actionableUnlocked": "其中现在可操作 {{count}} 项",
|
|
405
|
+
"actionableLocked": "可操作对象 {{count}} 项——治理动作已锁定,原因见上方说明",
|
|
406
|
+
"countsNote": "口径:「待你处理」= 屏上可操作对象 + 决定动态中未上屏的待决定/被阻塞(需逐条关注)/需要恢复事项;同一对象只计一次;历史未知项只作说明,不计入。",
|
|
407
|
+
"pendingUncertain": "待处理数暂不可确认——决定动态不可用。",
|
|
408
|
+
"actionableWithoutTotal": "现在可操作 {{count}} 项",
|
|
409
|
+
"degradedLabel": "决定动态降级:"
|
|
410
|
+
},
|
|
411
|
+
"ownerAttention": {
|
|
412
|
+
"sectionTitle": "被阻塞与需要恢复",
|
|
413
|
+
"blockedCount": "被阻塞 {{count}}",
|
|
414
|
+
"recoveryCount": "需要恢复 {{count}}",
|
|
415
|
+
"empty": "没有需要逐条关注的被阻塞或需要恢复的对象。",
|
|
416
|
+
"unmatchedTitle": "以下对象有决定动态,但当前没有可用的操作面(关联缺失或数据不完整,不作合并):",
|
|
417
|
+
"blockedAggregate": "另有 {{count}} 项被阻塞对象无需逐条关注,不计入上方待处理数。",
|
|
418
|
+
"unmatchedItemNote": "此条目暂无可用操作面;打开原则页查看其最新状态。"
|
|
398
419
|
}
|
|
399
420
|
},
|
|
400
421
|
"pain": {
|
|
@@ -694,9 +715,8 @@
|
|
|
694
715
|
"receipts": {
|
|
695
716
|
"title": "生效履历",
|
|
696
717
|
"headline": "保留记录中有 {{effectCount}} 次行为影响(最近 {{lastEffectAt}})——记录属历史观察,不代表当前仍在生效",
|
|
697
|
-
"headlinePresence": "
|
|
718
|
+
"headlinePresence": "已记录上下文投递,尚无对应效果记录",
|
|
698
719
|
"counts": "行为生效 {{effectCount}} 次 · 参与上下文 {{presenceCount}} 次",
|
|
699
|
-
"zeroEffect": "为什么「行为生效」是 0?生效需要证据:一次拦截、一次自动纠正,或智能体自述遵循。目前这条原则只进入过任务上下文(Presence)——参与决策 ≠ 已改变行为。随着运行积累出第一条证据,「行为生效」会从这里开始计数。",
|
|
700
720
|
"empty": "最近没有记录——这条原则尚未在运行中被触发。",
|
|
701
721
|
"note": "两级语义:Effect(行为生效)= 拦截 / 自动纠正 / 智能体自述遵循;Presence(参与上下文)= 原则进入任务上下文(灰色行)。参与决策 ≠ 已改变行为。智能体自述为概率性自报,与确定性事件分列。",
|
|
702
722
|
"unavailableReason": "暂无法读取生效履历。",
|
|
@@ -723,7 +743,11 @@
|
|
|
723
743
|
"auto_correct_applied": "自动纠正",
|
|
724
744
|
"self_reported": "智能体自述",
|
|
725
745
|
"prompt_injected": "注入上下文"
|
|
726
|
-
}
|
|
746
|
+
},
|
|
747
|
+
"headlineNone": "当前保留范围内未观察到上下文投递或效果记录",
|
|
748
|
+
"zeroEffectPresence": "为什么「行为生效」是 0?生效需要证据:一次拦截、一次自动纠正,或智能体自述遵循。目前这条原则只有参与任务上下文(Presence)的记录——参与决策 ≠ 已改变行为。随着运行积累出第一条证据,「行为生效」会从这里开始计数。",
|
|
749
|
+
"zeroEffectNone": "为什么两项都是 0?「0」表示当前保留范围内没有观察到记录,不等于从未发生:保留窗口之外、采集关闭期间的历史不会显示在这里。当这条原则被注入任务上下文或产生第一条效果证据时,计数会从这里开始变化。",
|
|
750
|
+
"presenceMissingNote": "当前保留范围内没有这条原则的上下文投递(Presence)记录;以下展示的是效果记录本身。"
|
|
727
751
|
},
|
|
728
752
|
"trajectory": "需要追溯时,查看完整轨迹",
|
|
729
753
|
"stageEvidence": "证据",
|
|
@@ -949,6 +973,36 @@
|
|
|
949
973
|
"invalidTarget": "动作目标缺失,无法提交。",
|
|
950
974
|
"actionDone": "操作已提交。",
|
|
951
975
|
"actionFailed": "操作被拒绝:{{detail}} 可用操作已刷新,请基于最新状态重新决定。"
|
|
976
|
+
},
|
|
977
|
+
"evidenceAudit": {
|
|
978
|
+
"title": "干预证据链",
|
|
979
|
+
"headline": "投递 {{d}} · 应用 {{a}} · 行为 {{e}} · 效果 {{f}} · 结果 {{o}}",
|
|
980
|
+
"unresolved": "{{count}} 条记录的关联对象尚未到达(pending association,非「未发生」)",
|
|
981
|
+
"sectionDelivery": "投递(Delivery)",
|
|
982
|
+
"sectionApplication": "应用(Application)",
|
|
983
|
+
"sectionEpisode": "行为(Episode)",
|
|
984
|
+
"sectionEffect": "效果(Effect)",
|
|
985
|
+
"sectionOutcome": "结果(Outcome)",
|
|
986
|
+
"emptySection": "本范围内未观察到(≠ 从未发生)",
|
|
987
|
+
"pending": "待关联",
|
|
988
|
+
"loading": "读取中…",
|
|
989
|
+
"loadOlder": "读取更早记录",
|
|
990
|
+
"capabilities": "宿主能力声明(Supported / Unsupported / Unknown)",
|
|
991
|
+
"technicalSummary": "技术详情(Phase 1 · 修订引用 / 摘要 / 来源标识)",
|
|
992
|
+
"technicalNote": "以下为工程追溯信息:证据 ID、修订引用、载荷摘要、激活与原生线索。默认视图只展示记录与缺口;这些字段不改变上方的语义。",
|
|
993
|
+
"sourceUnknown": "源可复核性未知",
|
|
994
|
+
"contentRedacted": "正文已于 {{date}} 清除",
|
|
995
|
+
"contentReference": "内容引用:{{resolution}} · {{principleId}}{{artifactId}}{{payloadDigest}}",
|
|
996
|
+
"referenceResolved": "resolved",
|
|
997
|
+
"referenceUnresolved": "revision 未解析",
|
|
998
|
+
"activationReference": "激活发生:{{activationId}}{{activatedAt}}{{snapshotDigest}}",
|
|
999
|
+
"nativeLineage": "原生线索:{{lineage}}",
|
|
1000
|
+
"note": "本区只记录事实:投递确认程度、智能体自述与 Runtime 事实分开列出,缺失=未知而非无效。不提供成功率或有效性评分。",
|
|
1001
|
+
"unavailable": "干预证据链暂不可用",
|
|
1002
|
+
"noPayloadDigest": "无载荷摘要",
|
|
1003
|
+
"noSnapshotDigest": "无快照摘要",
|
|
1004
|
+
"payloadDigestLabel": "载荷摘要",
|
|
1005
|
+
"snapshotDigestLabel": "快照摘要"
|
|
952
1006
|
}
|
|
953
1007
|
}
|
|
954
1008
|
},
|
|
@@ -1918,7 +1972,14 @@
|
|
|
1918
1972
|
"update": "更新",
|
|
1919
1973
|
"pendingApprovalsAria": "{{count}} 条待审批原则",
|
|
1920
1974
|
"degradedSignalsAria": "{{count}} 个系统降级信号",
|
|
1921
|
-
"intent": "拥有者意图"
|
|
1975
|
+
"intent": "拥有者意图",
|
|
1976
|
+
"themeToLight": "切换亮色模式",
|
|
1977
|
+
"themeToDark": "切换暗色模式",
|
|
1978
|
+
"switchLanguage": "切换语言",
|
|
1979
|
+
"signOut": "退出",
|
|
1980
|
+
"openMenu": "打开导航菜单",
|
|
1981
|
+
"navigation": "主导航",
|
|
1982
|
+
"navigationDescription": "导航到控制台各页面;选择后抽屉自动关闭。"
|
|
1922
1983
|
},
|
|
1923
1984
|
"approvalCard": {
|
|
1924
1985
|
"confidence": {
|