create-principles-disciple 1.120.0 → 1.120.2

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.
@@ -658,6 +658,47 @@ function depsMeaningfullyChanged(oldDeps, newDeps) {
658
658
  return true;
659
659
  return aKeys.some((k, i) => bKeys[i] !== k || a[k] !== b[k]);
660
660
  }
661
+ /**
662
+ * Create the node_modules/@principles/host-runtime resolution links for the
663
+ * installed console and pd-cli packages, if missing.
664
+ *
665
+ * Mirrors installer.ts syncPdCli: junction on Windows (no elevation needed),
666
+ * relative symlink elsewhere. Fresh installs get these links via npm install
667
+ * (the bundled package.json rewrites the dep to file:../host-runtime); the
668
+ * full update deliberately skips npm install, so it must create them itself.
669
+ * Without the link, the updated console dist — which statically imports
670
+ * @principles/host-runtime since 2026-08-21 (41cf97ee5) — crashes at startup
671
+ * with ERR_MODULE_NOT_FOUND on installs created before the installer bundled
672
+ * host-runtime (PRI-561).
673
+ *
674
+ * Returns undefined on success, or an error message (rc-9: observable, never
675
+ * silent — a missing link means the updated console cannot start).
676
+ */
677
+ function ensureHostRuntimeResolutionLinks(extDir) {
678
+ const hostRuntimeDir = path.join(extDir, 'host-runtime');
679
+ const linkPaths = [
680
+ path.join(extDir, 'console', 'node_modules', '@principles', 'host-runtime'),
681
+ path.join(extDir, 'pd-cli', 'node_modules', '@principles', 'host-runtime'),
682
+ ];
683
+ for (const linkPath of linkPaths) {
684
+ if (fs.existsSync(linkPath))
685
+ continue;
686
+ try {
687
+ fs.mkdirSync(path.dirname(linkPath), { recursive: true });
688
+ if (process.platform === 'win32') {
689
+ fs.symlinkSync(hostRuntimeDir, linkPath, 'junction');
690
+ }
691
+ else {
692
+ // Relative from <ext>/<pkg>/node_modules/@principles/ → <ext>/
693
+ fs.symlinkSync('../../../host-runtime', linkPath, 'dir');
694
+ }
695
+ }
696
+ catch (error) {
697
+ return `Failed to create host-runtime resolution link at ${linkPath}: ${error instanceof Error ? error.message : String(error)}`;
698
+ }
699
+ }
700
+ return undefined;
701
+ }
661
702
  async function doInlineFullUpdate(workspaceDir) {
662
703
  const extDir = resolvePluginDir(workspaceDir);
663
704
  // Legacy-contract preflight BEFORE stopping the gateway or touching files:
@@ -724,7 +765,43 @@ async function doInlineFullUpdate(workspaceDir) {
724
765
  }
725
766
  catch { /* best-effort comparison */ }
726
767
  }
727
- // 5. Copy files — 4 subdirectory mappings
768
+ // 5. Copy files — host-runtime FIRST, then the 4 subdirectory mappings.
769
+ //
770
+ // a0. host-runtime/ → extDir/host-runtime/ (overlay, skip node_modules),
771
+ // then create resolution links — BEFORE any plugin/console/core/
772
+ // pd-cli byte is swapped. A link-creation failure must abort with
773
+ // the installed packages untouched (same placement logic as the
774
+ // legacy-rule preflight above); swapping first would leave a half-
775
+ // updated install whose next console start crashes with
776
+ // ERR_MODULE_NOT_FOUND (PRI-561). The bundled console and pd-cli
777
+ // statically import @principles/host-runtime, but installs created
778
+ // before the installer bundled it (2026-08-14, PR #1315) have
779
+ // neither the directory nor the links. Only applied when the
780
+ // tarball carries it; pre-2026-08-14 installers also bundle a
781
+ // console that does not import it, so skipping is consistent for
782
+ // them. On fresh installs this is an overlay refresh + link no-op.
783
+ const hostRuntimeSrc = path.join(tempDir, 'host-runtime');
784
+ const hostRuntimeDest = path.join(extDir, 'host-runtime');
785
+ if (fs.existsSync(hostRuntimeSrc) &&
786
+ fs.existsSync(path.join(hostRuntimeSrc, 'package.json')) &&
787
+ fs.existsSync(path.join(hostRuntimeSrc, 'dist'))) {
788
+ copyDirRecursive(hostRuntimeSrc, hostRuntimeDest, SKIP_DIRS);
789
+ const linkError = ensureHostRuntimeResolutionLinks(extDir);
790
+ if (linkError) {
791
+ appendUpdateHistory(workspaceDir, {
792
+ fromVersion,
793
+ toVersion: 'failed',
794
+ success: false,
795
+ });
796
+ return {
797
+ success: false,
798
+ message: linkError,
799
+ reason: 'host_runtime_link_failed',
800
+ nextAction: 'Resolve the link error above, then re-run the update. No plugin/console/core/pd-cli files were changed.',
801
+ requiresRestart: false,
802
+ };
803
+ }
804
+ }
728
805
  // a. plugin/* → extDir/* (flattened, skip node_modules)
729
806
  const pluginSrc = path.join(tempDir, 'plugin');
730
807
  if (fs.existsSync(pluginSrc)) {
@@ -367,9 +367,24 @@
367
367
  "compareField": "Field",
368
368
  "showMore": "Show more",
369
369
  "showLess": "Show less",
370
- "reviewTitle": "Principle Review",
371
- "reviewSubtitle": "You review future behavior, not refined text",
372
- "reviewDescription": "Each card represents one principle. Review its text, status, and channel before deciding.",
370
+ "reviewTitle": "Principle Evolution Timeline",
371
+ "reviewSubtitle": "Principle Growth",
372
+ "reviewDescription": "These are not cold rules they are behavioral principles your Agent has distilled from real use.",
373
+ "metricDeposited": "Deposited Principles",
374
+ "metricActive": "Active Principles",
375
+ "metricLatest": "Last Updated",
376
+ "blockBasis": "Formation Basis",
377
+ "blockBasisFallback": "This principle was distilled from historical behavior calibration. Its detailed source is not yet linked.",
378
+ "blockImpact": "Current Impact",
379
+ "blockImpactActive": "This principle is currently shaping the Agent's subsequent behavior.",
380
+ "blockImpactPending": "Not yet active — awaiting Owner confirmation.",
381
+ "blockImpactRejected": "Rejected by Owner — no longer shapes the Agent's behavior.",
382
+ "blockImpactParked": "Parked — currently not shaping the Agent's behavior.",
383
+ "blockBehavior": "Behavior Change",
384
+ "blockBehaviorFallback": "No deterministic behavior rule recorded; the principle guides subsequent similar scenarios as a behavioral policy.",
385
+ "govDecision": "Governance Decision",
386
+ "govApproved": "Owner Approved",
387
+ "govPending": "Pending Review",
373
388
  "loading": "Loading…",
374
389
  "retry": "Retry",
375
390
  "statusPending": "Pending",
@@ -367,9 +367,24 @@
367
367
  "compareField": "字段",
368
368
  "showMore": "展开更多",
369
369
  "showLess": "收起",
370
- "reviewTitle": "原则审查",
371
- "reviewSubtitle": "你审查的是未来行为,而非精炼文本",
372
- "reviewDescription": "每张卡片代表一条原则。审查其正文、状态和通道后再做决定。",
370
+ "reviewTitle": "原则演化轨迹",
371
+ "reviewSubtitle": "原则成长",
372
+ "reviewDescription": "这些不是冰冷的规则,而是智能体在真实使用过程中沉淀下来的行为准则。",
373
+ "metricDeposited": "沉淀原则",
374
+ "metricActive": "已生效原则",
375
+ "metricLatest": "最近更新",
376
+ "blockBasis": "形成依据",
377
+ "blockBasisFallback": "该原则由历史行为校准沉淀而来,详细来源尚未关联。",
378
+ "blockImpact": "当前影响",
379
+ "blockImpactActive": "该原则正在影响智能体后续行为。",
380
+ "blockImpactPending": "尚未生效,等待拥有者确认。",
381
+ "blockImpactRejected": "拥有者已拒绝,不再影响智能体后续行为。",
382
+ "blockImpactParked": "已暂存,当前不影响智能体后续行为。",
383
+ "blockBehavior": "行为改变",
384
+ "blockBehaviorFallback": "暂无确定性行为规则记录;该原则作为行为准则影响后续同类场景。",
385
+ "govDecision": "治理决策",
386
+ "govApproved": "拥有者已批准",
387
+ "govPending": "等待拥有者审查",
373
388
  "loading": "加载中…",
374
389
  "retry": "重试",
375
390
  "statusPending": "待审查",
@@ -11,6 +11,17 @@ import { Button } from "../../components/ui/button.js";
11
11
  import { fetchAllActivations, disableActivation, fetchLifecycleMetrics, fetchReceiptCounts, fetchRuleCodeOwnerReview, ruleCodeDecision, pauseAllRuleCode, releaseRuleCodePause, } from "../../api.js";
12
12
  import { validateActivationsData, validateLifecycleMetricsData, isReversibleChannel, } from "./ActivationValidators.js";
13
13
  import { enumLabel } from "../../utils/enum-labels.js";
14
+ import { formatDate } from "../../utils/format-date.js";
15
+ // Date+time rendering options — mirrors the old default toLocaleString() shape
16
+ // (numeric date, hours/minutes/seconds) while following the UI language.
17
+ const DATETIME_OPTS = {
18
+ year: "numeric",
19
+ month: "numeric",
20
+ day: "numeric",
21
+ hour: "numeric",
22
+ minute: "2-digit",
23
+ second: "2-digit",
24
+ };
14
25
  // ── Channel label helper ─────────────────────────────────────────────────────
15
26
  function getChannelLabel(channel, t) {
16
27
  return enumLabel('channel', channel, t);
@@ -78,7 +89,7 @@ function RuleCodeOwnerPanel({ record, onChanged }) {
78
89
  return _jsxs("div", { className: "mt-4 rounded-[5px] border border-gov/25 bg-surface/70 p-4", "data-testid": `owner-review-${record.activationId}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("strong", { className: "text-[13px] text-ink", children: t('pages.activation.ownerDecisionTitle') }), _jsx("span", { className: "font-mono text-[11px] text-gov", children: review.readiness.status }), review.controlState?.enforcement === 'safety_isolated' && _jsx("span", { className: "text-danger text-[11px]", children: t('pages.activation.safetyIsolation') })] }), _jsxs("div", { className: "mt-2 grid grid-cols-2 gap-2 text-[12px] text-ink-3 md:grid-cols-4", children: [_jsxs("span", { children: [t('pages.activation.eligibleEvaluations'), ": ", value(summary.observed)] }), _jsxs("span", { children: [t('pages.activation.matched'), ": ", value(summary.matched)] }), _jsxs("span", { children: [t('pages.activation.wouldBlock'), ": ", value(summary.wouldBlock)] }), _jsxs("span", { children: [t('pages.activation.wouldAllow'), ": ", value(summary.wouldAllow)] }), _jsxs("span", { children: [t('pages.activation.requireApproval'), ": ", value(summary.requireApproval)] }), _jsxs("span", { children: [t('pages.activation.autoCorrect'), ": ", value(summary.autoCorrect)] }), _jsxs("span", { children: [t('pages.activation.neutralControl'), ": ", value(summary.neutralControl)] }), _jsxs("span", { children: [t('pages.activation.evaluationErrors'), ": ", value(summary.errors)] })] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.shadowWindow', { first: summary.firstObservedAt ?? t('pages.activation.notCollected'), last: summary.lastObservedAt ?? t('pages.activation.notCollected') }) }), _jsx("div", { className: "mt-2 text-[12px] text-ink-3", children: t('pages.activation.runtimeCapability', { version: review.runtimeCapability.hostRuntimeVersion, supported: t(review.runtimeCapability.shadowEvidence ? 'pages.activation.supported' : 'pages.activation.unsupported') }) }), _jsx("div", { className: "mt-2 text-[12px] text-amber", children: t('pages.activation.worstImpact', { tools: affectedTools.join(', ') || t('pages.activation.notCollected') }) }), _jsxs("div", { className: "mt-3 text-[12px] text-ink-3", children: [_jsx("div", { className: "mb-1 font-medium text-ink", children: t('pages.activation.hardSafetyChecks') }), review.readiness.evidenceSnapshot.safetyGateResults.map(check => _jsxs("div", { className: check.status === 'passed' ? 'text-green' : 'text-danger', children: [check.checkId, ": ", check.status, check.reasonCode ? ` · ${check.reasonCode}` : ''] }, check.checkId))] }), review.readiness.failedChecks.length > 0 && _jsx("ul", { className: "mt-2 text-[12px] text-danger", children: review.readiness.failedChecks.map(check => _jsxs("li", { children: [check.checkId, ": ", check.reasonCode] }, check.checkId)) }), _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.scopeArtifactCode') }), _jsx("pre", { className: "mt-2 max-h-64 overflow-auto whitespace-pre-wrap text-ink-3", children: JSON.stringify(review.artifact.content, null, 2) })] }), _jsxs("div", { className: "mt-3 grid gap-2 text-[11px] text-ink-3 md:grid-cols-2", children: [renderWindow(t('pages.activation.live24Hours'), review.liveMetrics.last24Hours), renderWindow(t('pages.activation.live7Days'), review.liveMetrics.last7Days)] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-3", children: t('pages.activation.behaviorDrift', { approved: value(review.behaviorDrift.approvedBlockRate), live: value(review.behaviorDrift.liveBlockRate), delta: value(review.behaviorDrift.delta) }) }), review.liveMetrics.representativeSamples.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.representativeSamples') }), review.liveMetrics.representativeSamples.map((sample, index) => _jsxs("div", { className: "font-mono text-ink-3", children: [sample.toolName, " \u00B7 ", sample.decision, " \u00B7 ", sample.pathCategory] }, `${sample.toolName}-${index}`))] }), review.decisions.length > 0 && _jsxs("details", { className: "mt-2 text-[12px]", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('pages.activation.ownerDecisionHistory') }), review.decisions.map(decision => _jsxs("div", { className: "mt-1 border-l border-line pl-2 text-ink-3", children: [_jsx("span", { className: "font-mono", children: decision.decidedAt }), " \u00B7 ", decision.decision, " \u00B7 ", decision.principalKind, ":", decision.actorId, " \u00B7 ", decision.authenticationMethod, " \u00B7 ", decision.reasonCode, decision.note ? ` · ${decision.note}` : ''] }, decision.decisionId))] }), _jsx("div", { className: "mt-2 text-[11px] text-ink-4", children: t('pages.activation.circuitConditions') }), review.readiness.status === 'evidence_insufficient' && _jsxs("label", { className: "mt-3 block text-[12px] text-ink-3", children: [t('pages.activation.overrideReason'), _jsxs("select", { value: overrideReason, onChange: event => setOverrideReason(event.target.value), className: "mt-1 w-full rounded border border-line bg-panel px-3 py-2 text-ink", children: [_jsx("option", { value: "rare_behavior", children: t('pages.activation.overrideRareBehavior') }), _jsx("option", { value: "controlled_rollout", children: t('pages.activation.overrideControlledRollout') }), _jsx("option", { value: "owner_accepts_limited_evidence", children: t('pages.activation.overrideOwnerAccepts') })] })] }), _jsx("input", { value: note, onChange: event => setNote(event.target.value), placeholder: t('pages.activation.ownerReviewNote'), className: "mt-3 w-full rounded border border-line bg-panel px-3 py-2 text-[12px] text-ink" }), _jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [review.globalPause?.status === 'paused' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void releasePause(), children: t('pages.activation.releaseGlobalPause') }), record.action === 'code_tool_hook_shadow_activate' && _jsxs(_Fragment, { children: [_jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, onClick: () => void decide('continue-observing'), children: t('pages.activation.continueObserving') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('reject-after-shadow'), children: t('pages.activation.rejectAfterShadow') }), _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled || (review.readiness.status !== 'ready' && review.readiness.status !== 'evidence_insufficient') || (review.readiness.status === 'evidence_insufficient' && note.trim().length === 0), onClick: () => void decide('promote'), children: t('pages.activation.confirmLive') })] }), (record.action === 'code_tool_hook_live_activate' || review.controlState?.enforcement === 'safety_isolated') && _jsx(Button, { disabled: busy, variant: "destructive", onClick: () => void decide('emergency-deactivate'), children: t('pages.activation.emergencyDeactivate') }), review.controlState?.enforcement === 'safety_isolated' && _jsx(Button, { disabled: busy || !review.ownerDecisionEnabled, variant: "outline", onClick: () => void decide('recover-to-shadow'), children: t('pages.activation.recoverToShadow') })] }), !review.ownerDecisionEnabled && _jsx("div", { className: "mt-2 text-[11px] text-amber", children: t('pages.activation.rolloutDisabled') })] });
79
90
  }
80
91
  function ActivationFactCard({ record, lifecycleData, receiptCount, onDisable, disabling, onChanged, }) {
81
- const { t } = useTranslation();
92
+ const { t, i18n } = useTranslation();
82
93
  const navigate = useNavigate();
83
94
  const [showConfirm, setShowConfirm] = useState(false);
84
95
  const neverActivated = record.activatedAt === null;
@@ -95,12 +106,12 @@ function ActivationFactCard({ record, lifecycleData, receiptCount, onDisable, di
95
106
  params.set("principleId", record.principleId);
96
107
  }
97
108
  navigate(`/report-problem?${params.toString()}`);
98
- }, children: t("pages.reportProblem.entryFeedback") }), _jsxs("div", { className: "mt-[14px] mb-2", children: [_jsx("span", { className: "text-ink-2 text-[13px]", children: t("pages.activation.principleLabel") }), " ", _jsx(Link, { to: `/principles/${record.principleId}`, className: "text-gov text-[13px] hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `principle-link-${record.principleId}`, title: record.principleId, children: record.action }), _jsx("span", { className: "ml-2 font-mono text-[11px] text-ink-4", children: record.principleId })] }), record.legacyDecisionUnknown && _jsx("div", { className: "mt-3 border-l-2 border-amber px-3 text-[12px] text-amber", children: t('pages.activation.legacyUnknown', { due: record.ownerReviewDueAt ? new Date(record.ownerReviewDueAt).toLocaleString() : t('pages.activation.sevenDays') }) }), _jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.actionLabel") }), " ", _jsx("span", { className: "text-ink-2", children: record.action })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.targetLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px]", children: record.targetRef })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.activatedAtLabel") }), " ", neverActivated ? (_jsx("span", { className: "text-amber", children: t("pages.activation.neverActivated") })) : (_jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: new Date(record.activatedAt).toLocaleString() }))] }), receiptCount !== undefined && (_jsxs("div", { "data-testid": `activation-receipts-${record.activationId}`, children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.receiptsLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: t("pages.activation.receiptsValue", {
109
+ }, children: t("pages.reportProblem.entryFeedback") }), _jsxs("div", { className: "mt-[14px] mb-2", children: [_jsx("span", { className: "text-ink-2 text-[13px]", children: t("pages.activation.principleLabel") }), " ", _jsx(Link, { to: `/principles/${record.principleId}`, className: "text-gov text-[13px] hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `principle-link-${record.principleId}`, title: record.principleId, children: record.action }), _jsx("span", { className: "ml-2 font-mono text-[11px] text-ink-4", children: record.principleId })] }), record.legacyDecisionUnknown && _jsx("div", { className: "mt-3 border-l-2 border-amber px-3 text-[12px] text-amber", children: t('pages.activation.legacyUnknown', { due: record.ownerReviewDueAt ? formatDate(record.ownerReviewDueAt, i18n.language, DATETIME_OPTS) : t('pages.activation.sevenDays') }) }), _jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.actionLabel") }), " ", _jsx("span", { className: "text-ink-2", children: record.action })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.targetLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px]", children: record.targetRef })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.activatedAtLabel") }), " ", neverActivated ? (_jsx("span", { className: "text-amber", children: t("pages.activation.neverActivated") })) : (_jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: formatDate(record.activatedAt, i18n.language, DATETIME_OPTS) }))] }), receiptCount !== undefined && (_jsxs("div", { "data-testid": `activation-receipts-${record.activationId}`, children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.receiptsLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: t("pages.activation.receiptsValue", {
99
110
  effect: receiptCount?.effectCount ?? 0,
100
111
  presence: receiptCount?.presenceCount ?? 0,
101
112
  }) })] }))] }), lifecycleData !== undefined && (_jsxs("details", { className: "mt-4 group", children: [_jsx("summary", { className: "text-gov text-[13px] cursor-pointer hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.activation.lifecycleToggle") }), _jsx("div", { className: "mt-2 pl-4 border-l-2 border-line", children: lifecycleData === null ? (_jsx("div", { className: "text-ink-4 text-[13px] py-2", children: t("pages.activation.lifecycleUnavailable") })) : lifecycleData.adherence.insufficientData ? (_jsx("div", { className: "text-ink-4 text-[13px] py-2", children: t("pages.activation.insufficientData") })) : (_jsxs("div", { className: "py-2 space-y-2", children: [_jsxs("div", { className: "text-ink-2 text-[13px]", children: [_jsx("span", { className: "font-mono font-semibold text-ink tabular-nums", children: lifecycleData.adherence.rate !== null
102
113
  ? `${(lifecycleData.adherence.rate * 100).toFixed(0)}%`
103
- : "—" }), " ", _jsx("span", { className: "text-ink-3", children: t("pages.activation.adherenceLabel") }), _jsx("div", { className: "text-ink-4 text-[11px] mt-1 font-mono", children: t("pages.activation.lifecycleNote") })] }), lifecycleData.ruleMetrics.length > 0 && (_jsx("div", { className: "space-y-1", children: lifecycleData.ruleMetrics.map((rm) => (_jsxs("div", { className: "text-ink-3 text-[12px] font-mono", children: [_jsx("span", { className: "text-ink-2", children: rm.ruleId }), ":", " ", rm.triggered, " ", t("pages.activation.triggered"), " ", rm.lastTriggeredAt && (_jsxs("span", { className: "text-ink-4", children: ["(", new Date(rm.lastTriggeredAt).toLocaleString(), ")"] }))] }, rm.ruleId))) }))] })) })] })), record.status === "active" && reversible && (_jsx("div", { className: "mt-4", children: !showConfirm ? (_jsx("button", { onClick: () => setShowConfirm(true), disabled: disabling, className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `disable-btn-${record.activationId}`, children: t("pages.activation.disableAction") })) : (_jsxs("div", { className: "flex items-center gap-3 px-[14px] py-[10px] bg-surface border border-amber/25 rounded-[4px]", role: "alert", "data-testid": `confirm-bar-${record.activationId}`, children: [_jsx("span", { className: "text-ink-2 text-[13px] flex-1", children: t("pages.activation.confirmDisable") }), _jsx("button", { onClick: () => {
114
+ : "—" }), " ", _jsx("span", { className: "text-ink-3", children: t("pages.activation.adherenceLabel") }), _jsx("div", { className: "text-ink-4 text-[11px] mt-1 font-mono", children: t("pages.activation.lifecycleNote") })] }), lifecycleData.ruleMetrics.length > 0 && (_jsx("div", { className: "space-y-1", children: lifecycleData.ruleMetrics.map((rm) => (_jsxs("div", { className: "text-ink-3 text-[12px] font-mono", children: [_jsx("span", { className: "text-ink-2", children: rm.ruleId }), ":", " ", rm.triggered, " ", t("pages.activation.triggered"), " ", rm.lastTriggeredAt && (_jsxs("span", { className: "text-ink-4", children: ["(", formatDate(rm.lastTriggeredAt, i18n.language, DATETIME_OPTS), ")"] }))] }, rm.ruleId))) }))] })) })] })), record.status === "active" && reversible && (_jsx("div", { className: "mt-4", children: !showConfirm ? (_jsx("button", { onClick: () => setShowConfirm(true), disabled: disabling, className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `disable-btn-${record.activationId}`, children: t("pages.activation.disableAction") })) : (_jsxs("div", { className: "flex items-center gap-3 px-[14px] py-[10px] bg-surface border border-amber/25 rounded-[4px]", role: "alert", "data-testid": `confirm-bar-${record.activationId}`, children: [_jsx("span", { className: "text-ink-2 text-[13px] flex-1", children: t("pages.activation.confirmDisable") }), _jsx("button", { onClick: () => {
104
115
  onDisable(record);
105
116
  setShowConfirm(false);
106
117
  }, disabled: disabling, className: "inline-flex items-center border border-danger/40 text-danger bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-danger/5 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `confirm-disable-${record.activationId}`, children: disabling ? t("pages.activation.disabling") : t("common.confirm") }), _jsx("button", { onClick: () => setShowConfirm(false), className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("common.cancel") })] })) })), record.channel === 'code_tool_hook' && record.status === 'active' && _jsx(RuleCodeOwnerPanel, { record: record, onChanged: onChanged })] }));
@@ -8,6 +8,7 @@ import { SectionTitle } from "../../components/layout/section-title.js";
8
8
  import { fetchIntentSummary, fetchIntentDecisionSummary, patchFeatureFlag, fetchIntentContent, fetchIntentVersions } from "../../api.js";
9
9
  import { IntentEditor, CreateIntentButton, EditButton } from "./IntentOnboarding.js";
10
10
  import { computeVersionDiff } from "@principles/core/runtime-v2/intent-browser";
11
+ import { formatDate } from "../../utils/format-date.js";
11
12
  // ── Sub-components ────────────────────────────────────────────────────────────
12
13
  function FlagStatusBadge({ enabled }) {
13
14
  const { t } = useTranslation();
@@ -80,24 +81,17 @@ function MetaRow({ label, value }) {
80
81
  return (_jsxs("div", { className: "flex items-baseline gap-3", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 w-32 shrink-0", children: label }), _jsx("span", { className: "text-ink-2 text-[12px] font-mono break-all", children: value })] }));
81
82
  }
82
83
  function VersionEntry({ version, previousContent, reasonLabel, defaultExpanded = false }) {
83
- const { t } = useTranslation();
84
+ const { t, i18n } = useTranslation();
84
85
  const [expanded, setExpanded] = useState(defaultExpanded);
85
86
  // Compute section-level diff between previous and this version.
86
87
  // For the oldest version (no previous), diff against "" so all non-empty
87
88
  // sections show as "changed" — gives the user a visual of what was filled.
88
89
  const diff = computeVersionDiff(previousContent, version.contentSnapshot);
89
90
  // Localize the timestamp for display (avoid showing raw UTC to end users).
90
- const localizedTime = (() => {
91
- try {
92
- return new Date(version.createdAt).toLocaleString(undefined, {
93
- year: 'numeric', month: '2-digit', day: '2-digit',
94
- hour: '2-digit', minute: '2-digit',
95
- });
96
- }
97
- catch {
98
- return version.createdAt.slice(0, 19).replace('T', ' ');
99
- }
100
- })();
91
+ const localizedTime = formatDate(version.createdAt, i18n.language, {
92
+ year: 'numeric', month: '2-digit', day: '2-digit',
93
+ hour: '2-digit', minute: '2-digit',
94
+ });
101
95
  return (_jsxs("div", { className: "border-b border-line last:border-b-0", children: [_jsxs("button", { type: "button", onClick: () => setExpanded((prev) => !prev), className: "w-full flex items-center gap-3 py-1.5 text-[12px] font-mono text-ink-2 hover:bg-surface-2/50 transition-colors text-left", "aria-expanded": expanded, "aria-label": expanded ? t("pages.intent.versionDiff.collapseHint") : t("pages.intent.versionDiff.expandHint"), title: version.contentHash, children: [_jsx("span", { className: "text-ink-3 shrink-0 w-4 inline-block", children: expanded ? "▾" : "▸" }), _jsx("span", { className: "text-ink-3 shrink-0", children: localizedTime }), _jsx("span", { className: "px-1.5 py-0.5 rounded-[2px] bg-surface-2 text-ink-3 shrink-0", children: reasonLabel }), _jsx("span", { className: "text-ink-4 text-[11px] ml-auto shrink-0 hidden sm:inline", children: expanded ? t("pages.intent.versionDiff.collapseHint") : t("pages.intent.versionDiff.expandHint") })] }), expanded && (_jsx("div", { className: "pb-2.5 pl-7 pr-2", children: _jsx("div", { className: "flex flex-wrap gap-1.5 mb-2", children: diff.map((d) => {
102
96
  // d.section is one of the 5 section keys from computeVersionDiff's
103
97
  // SECTION_KEYS — safe to construct the i18n key dynamically.
@@ -8,6 +8,7 @@ import { PageLoading } from "../../components/layout/page-loading.js";
8
8
  import { SectionTitle } from "../../components/layout/section-title.js";
9
9
  import { Button } from "../../components/ui/button.js";
10
10
  import { fetchPrincipleDetail, fetchApprovalsGrouped, fetchLifecycleMetrics, fetchPrincipleTrajectory, fetchPrincipleGovernance, fetchPrincipleReceipts, approveApproval, rejectApproval, editApproval, } from "../../api.js";
11
+ import { formatDate } from "../../utils/format-date.js";
11
12
  // ── Runtime validation (H section) ──────────────────────────────────────────
12
13
  function isString(v) {
13
14
  return typeof v === "string";
@@ -108,7 +109,7 @@ const STAGE_LABEL_KEYS = {
108
109
  };
109
110
  // ── Component ───────────────────────────────────────────────────────────────
110
111
  export function PrincipleDetailPage() {
111
- const { t } = useTranslation("pages");
112
+ const { t, i18n } = useTranslation("pages");
112
113
  const { id } = useParams();
113
114
  const navigate = useNavigate();
114
115
  const [principle, setPrinciple] = useState(null);
@@ -414,7 +415,7 @@ export function PrincipleDetailPage() {
414
415
  }, children: t("reportProblem.entryFeedback") })] })] }), (governance !== null || governanceUnavailable !== null) && (_jsxs("section", { className: "mb-8", "aria-labelledby": "governance-summary-title", children: [_jsx(SectionTitle, { children: t('principles.detail.governance.title') }), governance !== null ? (_jsxs("div", { "data-testid": "governance-summary", className: `rounded-[var(--radius-md)] border p-4 ${governance.dataQuality.degraded ? 'border-amber/30 bg-amber/5' : 'border-gov/25 bg-gov/5'}`, children: [_jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between", children: [_jsxs("div", { children: [_jsx("h2", { id: "governance-summary-title", className: "text-[18px] font-semibold text-ink", children: t(`principles.detail.${governance.summary.headlineCode}`, { defaultValue: governance.summary.headlineCode }) }), _jsx("p", { className: "mt-1 text-[13px] leading-relaxed text-ink-2", children: t(`principles.detail.${governance.summary.reasonCode}`, { defaultValue: governance.summary.reasonCode }) })] }), _jsx("span", { className: "w-fit rounded-full border border-line px-2 py-1 font-mono text-[11px] text-ink-3", children: t(`principles.detail.governance.confidence.${governance.dataQuality.degraded ? 'degraded' : 'strong'}`) })] }), _jsxs("dl", { className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.principleState') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.state.${governance.principleState.value}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.process') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: governance.process.stage === undefined ? t('principles.detail.governance.none') : t(`principles.detail.governance.stage.${governance.process.stage}`) })] }), _jsxs("div", { children: [_jsx("dt", { className: "font-mono text-[11px] text-ink-4", children: t('principles.detail.governance.automation') }), _jsx("dd", { className: "text-[13px] text-ink-2", children: t(`principles.detail.governance.automationState.${governance.automation.state}`) })] })] }), _jsxs("div", { "data-testid": "governance-next-action", className: "mt-4 border-l-2 border-l-gov pl-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.nextAction') }), _jsx("p", { className: "text-[13px] text-ink-2", children: t(`principles.detail.${governance.summary.nextActionCode}`, { defaultValue: governance.summary.nextActionCode }) })] }), governance.attention.items.length > 0 && (_jsxs("div", { className: "mt-4", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.governance.blockers') }), _jsx("ul", { className: "mt-1 space-y-1 text-[13px] text-ink-2", children: governance.attention.items.map(item => _jsx("li", { children: t(`principles.detail.governance.attention.${item.reasonCode}`, { defaultValue: item.reasonCode }) }, `${item.kind}-${item.sourceRef.type}-${item.sourceRef.id}`)) })] })), governance.dataQuality.degraded && (_jsxs("div", { "data-testid": "governance-data-quality", className: "mt-4 rounded-[var(--radius-sm)] border border-amber/20 p-3", children: [_jsx("p", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-amber", children: t('principles.detail.governance.uncertainty') }), _jsx("ul", { className: "mt-1 space-y-1 text-[12px] text-ink-3", children: governance.dataQuality.issues.map((item, index) => _jsx("li", { children: t(`principles.detail.governance.issue.${item.reasonCode}`, { defaultValue: item.reasonCode }) }, `${item.source}-${item.reasonCode}-${index}`)) })] }))] })) : (_jsxs("div", { "data-testid": "governance-data-quality", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h2", { id: "governance-summary-title", className: "text-[16px] font-semibold text-ink", children: t('principles.detail.governance.unavailable') }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: t('principles.detail.governance.unavailableReason') }), _jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: t('principles.detail.governance.unavailableNextAction') })] }))] })), receipts !== null && (_jsxs("section", { className: "mb-8", "aria-labelledby": "receipt-history-title", children: [_jsx(SectionTitle, { children: t('principles.detail.receipts.title') }), receipts.status === 'ok' ? (_jsxs("div", { "data-testid": "receipt-history", className: "rounded-[var(--radius-md)] border border-line p-4", children: [_jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-2", children: [_jsx("h2", { id: "receipt-history-title", className: "text-[15px] font-semibold text-ink", children: t('principles.detail.receipts.headline', {
415
416
  defaultValue: '',
416
417
  effectCount: receipts.effectCount,
417
- lastEffectAt: receipts.lastEffectAt ? new Date(receipts.lastEffectAt).toLocaleDateString() : '',
418
+ lastEffectAt: receipts.lastEffectAt ? formatDate(receipts.lastEffectAt, i18n.language) : '',
418
419
  }) }), _jsx("span", { "data-testid": "receipt-history-counts", className: "font-mono text-[11px] text-ink-3", children: t('principles.detail.receipts.counts', { effectCount: receipts.effectCount, presenceCount: receipts.presenceCount }) })] }), receipts.events.length > 0 ? (_jsx("ul", { className: "mt-3 space-y-1.5", children: receipts.events.map((event, index) => (_jsxs("li", { className: `flex flex-wrap items-baseline gap-2 text-[13px] ${event.level === 'presence' ? 'text-ink-4' : 'text-ink-2'}`, children: [_jsx("span", { className: "font-mono text-[11px] text-ink-4", children: event.createdAt.slice(0, 16).replace('T', ' ') }), _jsx("span", { className: "font-mono text-[11px]", children: t(`principles.detail.receipts.kind.${event.kind}`, { defaultValue: event.kind }) }), event.digest && _jsx("span", { className: "min-w-0 flex-1 truncate", title: event.digest, children: event.digest })] }, `${event.createdAt}-${index}`))) })) : (_jsx("p", { className: "mt-2 text-[13px] text-ink-3", children: t('principles.detail.receipts.empty') })), _jsx("p", { className: "mt-3 text-[11px] leading-relaxed text-ink-4", children: t('principles.detail.receipts.note') })] })) : (_jsxs("div", { "data-testid": "receipt-history-degraded", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("p", { className: "text-[13px] text-ink-2", children: receipts.reason ?? t('principles.detail.receipts.unavailableReason') }), receipts.nextAction && _jsx("p", { className: "mt-1 text-[12px] text-ink-3", children: receipts.nextAction })] }))] })), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.channel") }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: t("principles.detail.channelPromptReversible") })] }), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.whyExists") }), _jsxs("div", { className: "space-y-3 mb-6", children: [_jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.applicable", { defaultValue: "适用场景" }) }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: principle.triggerPattern || t("principles.detail.notSpecified", { defaultValue: "未指定" }) })] }), _jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.expectedBehavior", { defaultValue: "预期行为" }) }), _jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: principle.action || t("principles.detail.notSpecified", { defaultValue: "未指定" }) })] })] }), principle.derivedFromPainIds.length > 0 && (_jsxs("div", { children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3", children: t("principles.detail.evidence") }), _jsxs("div", { className: "mt-2 flex items-center gap-3 flex-wrap", children: [_jsx("p", { className: "text-ink-2 text-[14px] leading-relaxed", children: t("principles.detail.evidenceCount", {
419
420
  defaultValue: "已关联 {{count}} 条证据记录",
420
421
  count: principle.derivedFromPainIds.length,
@@ -9,6 +9,7 @@ import { Skeleton } from "../../components/ui/skeleton.js";
9
9
  import { ShinyText } from "../../components/ui/shiny-text.js";
10
10
  import { fetchPrinciples, fetchApprovalsGrouped, } from "../../api.js";
11
11
  import { enumLabel } from "../../utils/enum-labels.js";
12
+ import { formatDate } from "../../utils/format-date.js";
12
13
  // ── Map principle status to review status ───────────────────────────────────
13
14
  function toReviewStatus(principleStatus, approvalStatus) {
14
15
  if (approvalStatus === "approved")
@@ -43,6 +44,35 @@ const CHANNEL_LABELS = {
43
44
  defer_archive: "channelDeferArchive",
44
45
  code_tool_hook: "channelCodeToolHook",
45
46
  };
47
+ // ── PRI-558: timeline node fill + status-driven copy (theme tokens, dark-mode safe) ──
48
+ const STATUS_DOT_BG = {
49
+ pending: "bg-gov",
50
+ approved: "bg-green",
51
+ rejected: "bg-danger",
52
+ parked: "bg-ink-3",
53
+ };
54
+ // Governance decision row mirrors the true review status — rejected/parked must
55
+ // not read as "awaiting review" (labels reuse the existing status* keys).
56
+ const GOV_DECISION = {
57
+ approved: { glyph: "✓", labelKey: "principles.govApproved" },
58
+ pending: { glyph: "⏳", labelKey: "principles.govPending" },
59
+ rejected: { glyph: "✗", labelKey: "principles.statusRejected" },
60
+ parked: { glyph: "⏸", labelKey: "principles.statusParked" },
61
+ };
62
+ const BLOCK_IMPACT_KEY = {
63
+ approved: "principles.blockImpactActive",
64
+ pending: "principles.blockImpactPending",
65
+ rejected: "principles.blockImpactRejected",
66
+ parked: "principles.blockImpactParked",
67
+ };
68
+ // Summary metric tile (one of the three numbers in the metrics bar)
69
+ function Metric({ num, label }) {
70
+ return (_jsxs("div", { className: "flex-1 min-w-[120px] px-4 py-3 border-r border-line last:border-r-0", children: [_jsx("div", { className: "font-mono text-[22px] font-semibold text-gov leading-none", children: num }), _jsx("div", { className: "font-mono text-[11px] uppercase tracking-[0.02em] text-ink-3 mt-1.5", children: label })] }));
71
+ }
72
+ // Evolution block rendered under each principle card
73
+ function EvolutionBlock({ label, children }) {
74
+ return (_jsxs("div", { className: "mt-3 pt-3 border-t border-line/60", children: [_jsx("div", { className: "font-mono text-[11px] uppercase tracking-[0.02em] text-ink-4 mb-1", children: label }), _jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed", children: children })] }));
75
+ }
46
76
  // ── Runtime validation helpers (H section) ──────────────────────────────────
47
77
  function isString(v) {
48
78
  return typeof v === "string";
@@ -109,6 +139,8 @@ export function PrinciplesPage() {
109
139
  const [approvalGroups, setApprovalGroups] = useState([]);
110
140
  const [loading, setLoading] = useState(true);
111
141
  const [error, setError] = useState(null);
142
+ // PRI-558: summary powers the metrics bar (already returned by /api/principles)
143
+ const [summary, setSummary] = useState(null);
112
144
  // Search / filter / sort
113
145
  const [search, setSearch] = useState("");
114
146
  const [statusFilter, setStatusFilter] = useState("all");
@@ -137,6 +169,8 @@ export function PrinciplesPage() {
137
169
  setPrinciples(pData.principles);
138
170
  // PRI-330: Store categories for UI display
139
171
  setCategories(pData.categories);
172
+ // PRI-558: expose summary for the metrics bar
173
+ setSummary(pData.summary);
140
174
  const aData = aResult.success ? validateApprovalsGrouped(aResult.data) : null;
141
175
  setApprovalGroups(aData?.groups ?? []);
142
176
  }
@@ -183,6 +217,7 @@ export function PrinciplesPage() {
183
217
  originalTitle: rawTitle,
184
218
  titleUsedFallback: usedFallback,
185
219
  text: p.text,
220
+ action: p.action,
186
221
  status: toReviewStatus(p.status, ag?.status),
187
222
  channels: uniqueChannels,
188
223
  confidence,
@@ -216,6 +251,8 @@ export function PrinciplesPage() {
216
251
  { value: "rejected", label: t("principles.statusRejected") },
217
252
  { value: "parked", label: t("principles.statusParked") },
218
253
  ];
254
+ // PRI-558: most recent update across loaded principles (drives "Last Updated" metric)
255
+ const latestUpdatedAt = principles.reduce((max, p) => (new Date(p.updatedAt).getTime() > new Date(max).getTime() ? p.updatedAt : max), principles[0]?.updatedAt ?? "");
219
256
  return (_jsxs(PageShell, { children: [_jsx(SectionTitle, { children: t("principles.reviewTitle") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mb-2", duration: 4.5, brightness: 0.5, disabled: !(filterMode === 'actionable' && (categories?.owner_actionable ?? 0) > 0), children: t("principles.reviewSubtitle") }), _jsx("p", { className: "text-ink-3 text-[14px] leading-relaxed mb-6", children: t("principles.reviewDescription") }), _jsxs("div", { className: "flex flex-wrap items-center gap-3 mb-6", children: [_jsxs("div", { className: "flex gap-1", role: "group", "aria-label": "Filter mode", children: [_jsxs("button", { onClick: () => setFilterMode('actionable'), className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-1 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov " +
220
257
  (filterMode === 'actionable'
221
258
  ? "bg-gov text-paper border-gov"
@@ -225,21 +262,30 @@ export function PrinciplesPage() {
225
262
  : "bg-surface text-ink-3 border-line hover:border-line-2"), children: [t("principles.filterAll", { defaultValue: "Show All" }), categories ? ` (${Object.values(categories).reduce((a, b) => a + b, 0)})` : ''] })] }), _jsx("input", { type: "text", value: search, onChange: (e) => setSearch(e.target.value), placeholder: t("principles.searchPlaceholder"), className: "flex-1 min-w-[200px] border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.searchPlaceholder") }), _jsx("div", { className: "flex gap-1", role: "group", "aria-label": "Status filter", children: statusFilters.map((sf) => (_jsx("button", { onClick: () => setStatusFilter(sf.value), className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-1 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov " +
226
263
  (statusFilter === sf.value
227
264
  ? "bg-gov text-paper border-gov"
228
- : "bg-surface text-ink-3 border-line hover:border-line-2"), children: sf.label }, sf.value))) }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "border border-line rounded-[var(--radius-md)] bg-surface text-ink-3 px-2 py-1 text-[12px] font-mono focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": "Sort order", children: [_jsx("option", { value: "updatedAt", children: t("principles.sortByUpdated") }), _jsx("option", { value: "createdAt", children: t("principles.sortByCreated") })] })] }), loading && (_jsx("div", { className: "grid gap-3 animate-[pdFadeIn_200ms_ease-out]", role: "status", "aria-live": "polite", "aria-label": t("principles.loading", { defaultValue: "Loading…" }), children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "bg-panel border border-line rounded-[var(--radius-md)] p-4 border-l-[3px] border-l-transparent", children: [_jsxs("div", { className: "flex items-start justify-between gap-3 mb-3", children: [_jsx(Skeleton, { className: "h-5 w-[55%] rounded-sm" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-[2px]" })] }), _jsx(Skeleton, { className: "h-4 w-full rounded-sm mb-2" }), _jsx(Skeleton, { className: "h-4 w-[80%] rounded-sm mb-4" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-20 rounded-sm" }), _jsx(Skeleton, { className: "h-3.5 w-24 rounded-sm" })] })] }, i))) })), error && (_jsxs("div", { className: "border border-danger/30 rounded-[var(--radius-md)] p-4 mb-4", children: [_jsx("p", { className: "text-danger text-sm", children: error }), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, className: "mt-2", children: t("principles.retry", { defaultValue: "Retry" }) })] })), !loading && !error && filtered.length === 0 && principles.length === 0 && (_jsxs("div", { className: "text-center py-12", children: [_jsx("h2", { className: "text-ink-2 text-lg font-semibold mb-2", children: t("principles.emptyTitle") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[480px] mx-auto leading-relaxed", children: filterMode === 'actionable'
265
+ : "bg-surface text-ink-3 border-line hover:border-line-2"), children: sf.label }, sf.value))) }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "border border-line rounded-[var(--radius-md)] bg-surface text-ink-3 px-2 py-1 text-[12px] font-mono focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": "Sort order", children: [_jsx("option", { value: "updatedAt", children: t("principles.sortByUpdated") }), _jsx("option", { value: "createdAt", children: t("principles.sortByCreated") })] })] }), !loading && !error && summary && (_jsxs("div", { className: "flex flex-wrap border border-line rounded-[var(--radius-md)] bg-panel overflow-hidden mb-6", children: [_jsx(Metric, { num: String(summary.total), label: t("principles.metricDeposited") }), _jsx(Metric, { num: String(summary.active), label: t("principles.metricActive") }), _jsx(Metric, { num: latestUpdatedAt ? formatDate(latestUpdatedAt, i18n.language) : "—", label: t("principles.metricLatest") })] })), loading && (_jsx("div", { className: "grid gap-3 animate-[pdFadeIn_200ms_ease-out]", role: "status", "aria-live": "polite", "aria-label": t("principles.loading", { defaultValue: "Loading…" }), children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "bg-panel border border-line rounded-[var(--radius-md)] p-4 border-l-[3px] border-l-transparent", children: [_jsxs("div", { className: "flex items-start justify-between gap-3 mb-3", children: [_jsx(Skeleton, { className: "h-5 w-[55%] rounded-sm" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-[2px]" })] }), _jsx(Skeleton, { className: "h-4 w-full rounded-sm mb-2" }), _jsx(Skeleton, { className: "h-4 w-[80%] rounded-sm mb-4" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-20 rounded-sm" }), _jsx(Skeleton, { className: "h-3.5 w-24 rounded-sm" })] })] }, i))) })), error && (_jsxs("div", { className: "border border-danger/30 rounded-[var(--radius-md)] p-4 mb-4", children: [_jsx("p", { className: "text-danger text-sm", children: error }), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, className: "mt-2", children: t("principles.retry", { defaultValue: "Retry" }) })] })), !loading && !error && filtered.length === 0 && principles.length === 0 && (_jsxs("div", { className: "text-center py-12", children: [_jsx("h2", { className: "text-ink-2 text-lg font-semibold mb-2", children: t("principles.emptyTitle") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[480px] mx-auto leading-relaxed", children: filterMode === 'actionable'
229
266
  ? t("principles.emptyActionable", { defaultValue: "No owner-actionable principles at this time. When PD captures behavior deviation signals, principle candidates will appear here for your review." })
230
- : t("principles.emptyDescription") }), categories && filterMode === 'actionable' && (_jsx("div", { className: "flex justify-center gap-2 flex-wrap mt-4", children: Object.entries(categories).map(([cat, count]) => (_jsxs("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: [cat, ": ", count] }, cat))) }))] })), !loading && !error && filtered.length === 0 && principles.length > 0 && (_jsx("p", { className: "text-ink-3 text-sm", children: t("principles.noResults") })), !loading && !error && filtered.length > 0 && (_jsx("div", { className: "grid gap-3 animate-[pdFadeIn_400ms_ease-out]", children: filtered.map((card) => (_jsxs("article", { onClick: () => navigate(`/principles/${card.principleId}`), className: "bg-panel border border-line rounded-[var(--radius-md)] p-4 cursor-pointer " +
231
- "border-l-[3px] " +
232
- STATUS_BORDER[card.status] + " " +
233
- "transition-[border-color,background,transform] duration-150 " +
234
- "hover:border-line-2 hover:bg-surface hover:-translate-y-px " +
235
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", tabIndex: 0, role: "link", "aria-label": card.title, onKeyDown: (e) => {
236
- if (e.key === "Enter" || e.key === " ") {
237
- e.preventDefault();
238
- navigate(`/principles/${card.principleId}`);
239
- }
240
- }, children: [_jsxs("div", { className: "flex items-center gap-2 mb-2 flex-wrap", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-0.5 " +
241
- STATUS_TEXT[card.status] +
242
- " border-current/20", children: t("principles.status" + card.status.charAt(0).toUpperCase() + card.status.slice(1)) }), card.channels.map((ch) => (_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-3", children: enumLabel('channel', ch, t) }, ch))), card.detectedLanguage && card.detectedLanguage !== 'unknown' && (_jsx("span", { className: "font-mono text-[11px] tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: card.detectedLanguage === 'zh'
243
- ? t("principles.languageHintZh")
244
- : t("principles.languageHintEn") })), _jsxs("span", { className: "font-mono text-[11px] text-ink-4", children: [t("principles.confidence"), ": ", enumLabel('confidence', card.confidence, t)] })] }), card.readabilityWarningCode && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-amber/5 border border-amber/20 rounded-[3px] text-ink-3 text-[12px] leading-snug", children: t("principles.readabilityWarning." + card.readabilityWarningCode, { defaultValue: t("principles.readabilityFallbackTitle") }) })), card.detectedLanguage && card.detectedLanguage !== 'unknown' && i18n.language && !i18n.language.startsWith(card.detectedLanguage) && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-surface/60 border-l-2 border-line rounded-[3px] text-ink-4 text-[12px] leading-snug", children: t("principles.languageMismatchHint", { lang: card.detectedLanguage === 'en' ? 'English' : '中文' }) })), _jsx("h3", { className: "font-semibold text-ink mb-1", children: card.title }), card.titleUsedFallback && card.originalTitle && (_jsxs("details", { className: "mb-1", children: [_jsx("summary", { className: "text-ink-4 text-[11px] font-mono cursor-pointer hover:underline", children: t("principles.readabilityOriginalLabel") }), _jsx("div", { className: "text-ink-4 text-[12px] leading-snug mt-1 font-mono bg-surface/40 px-2 py-1 rounded-[3px] break-all", children: card.originalTitle })] })), _jsx("p", { className: "text-ink-3 text-[13px] line-clamp-3 leading-relaxed", children: card.text }), _jsxs("p", { className: "font-mono text-[11px] text-ink-4 mt-2", children: [t("principles.updatedAt"), ": ", new Date(card.updatedAt).toLocaleDateString()] })] }, card.principleId))) }))] }));
267
+ : t("principles.emptyDescription") }), categories && filterMode === 'actionable' && (_jsx("div", { className: "flex justify-center gap-2 flex-wrap mt-4", children: Object.entries(categories).map(([cat, count]) => (_jsxs("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: [cat, ": ", count] }, cat))) }))] })), !loading && !error && filtered.length === 0 && principles.length > 0 && (_jsx("p", { className: "text-ink-3 text-sm", children: t("principles.noResults") })), !loading && !error && filtered.length > 0 && (_jsxs("div", { className: "relative pl-6 grid gap-3 animate-[pdFadeIn_400ms_ease-out]", children: [_jsx("span", { className: "absolute left-[12.5px] top-3 bottom-3 w-px bg-line", "aria-hidden": "true" }), filtered.map((card) => {
268
+ const ag = approvalByPrinciple.get(card.principleId);
269
+ const decision = GOV_DECISION[card.status];
270
+ const decisionRecord = ag?.records?.find((r) => r.status === ag.status) ?? ag?.records?.[0];
271
+ const decisionDate = decisionRecord?.createdAt;
272
+ return (_jsxs("article", { onClick: () => navigate(`/principles/${card.principleId}`), className: "relative bg-panel border border-line rounded-[var(--radius-md)] p-4 cursor-pointer " +
273
+ "border-l-[3px] " +
274
+ STATUS_BORDER[card.status] + " " +
275
+ "transition-[border-color,background,transform] duration-150 " +
276
+ "hover:border-line-2 hover:bg-surface hover:-translate-y-px " +
277
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", tabIndex: 0, role: "link", "aria-label": card.title, onKeyDown: (e) => {
278
+ if (e.key === "Enter" || e.key === " ") {
279
+ e.preventDefault();
280
+ navigate(`/principles/${card.principleId}`);
281
+ }
282
+ }, children: [_jsx("span", { className: "absolute -left-[19px] top-4 h-2.5 w-2.5 rounded-full border-2 border-paper " +
283
+ STATUS_DOT_BG[card.status], "aria-hidden": "true" }), _jsxs("div", { className: "flex items-center gap-2 mb-2 flex-wrap", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-0.5 " +
284
+ STATUS_TEXT[card.status] +
285
+ " border-current/20", children: t("principles.status" + card.status.charAt(0).toUpperCase() + card.status.slice(1)) }), card.channels.map((ch) => (_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-3", children: enumLabel('channel', ch, t) }, ch))), card.detectedLanguage && card.detectedLanguage !== 'unknown' && (_jsx("span", { className: "font-mono text-[11px] tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: card.detectedLanguage === 'zh'
286
+ ? t("principles.languageHintZh")
287
+ : t("principles.languageHintEn") })), _jsxs("span", { className: "font-mono text-[11px] text-ink-4", children: [t("principles.confidence"), ": ", enumLabel('confidence', card.confidence, t)] })] }), card.readabilityWarningCode && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-amber/5 border border-amber/20 rounded-[3px] text-ink-3 text-[12px] leading-snug", children: t("principles.readabilityWarning." + card.readabilityWarningCode, { defaultValue: t("principles.readabilityFallbackTitle") }) })), card.detectedLanguage && card.detectedLanguage !== 'unknown' && i18n.language && !i18n.language.startsWith(card.detectedLanguage) && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-surface/60 border-l-2 border-line rounded-[3px] text-ink-4 text-[12px] leading-snug", children: t("principles.languageMismatchHint", { lang: card.detectedLanguage === 'en' ? 'English' : '中文' }) })), _jsx("h3", { className: "font-semibold text-ink mb-1", children: card.title }), card.titleUsedFallback && card.originalTitle && (_jsxs("details", { className: "mb-1", children: [_jsx("summary", { className: "text-ink-4 text-[11px] font-mono cursor-pointer hover:underline", children: t("principles.readabilityOriginalLabel") }), _jsx("div", { className: "text-ink-4 text-[12px] leading-snug mt-1 font-mono bg-surface/40 px-2 py-1 rounded-[3px] break-all", children: card.originalTitle })] })), _jsx("p", { className: "text-ink-3 text-[13px] line-clamp-3 leading-relaxed", children: card.text }), _jsxs("p", { className: "font-mono text-[11px] text-ink-4 mt-2", children: [t("principles.updatedAt"), ": ", formatDate(card.updatedAt, i18n.language)] }), _jsx(EvolutionBlock, { label: t("principles.blockBasis"), children: ag?.candidateDescription ?? t("principles.blockBasisFallback") }), _jsx(EvolutionBlock, { label: t("principles.blockImpact"), children: t(BLOCK_IMPACT_KEY[card.status]) }), _jsx(EvolutionBlock, { label: t("principles.blockBehavior"), children: card.action ? card.action : t("principles.blockBehaviorFallback") }), _jsx("div", { className: "mt-3 pt-3 border-t border-line/60", children: _jsxs("div", { className: "flex items-center justify-between gap-3 bg-gov/5 border border-line rounded-[3px] px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] text-ink-4", children: t("principles.govDecision") }), _jsxs("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-0.5 " +
288
+ STATUS_TEXT[card.status] +
289
+ " border-current/20", children: [decision.glyph, " ", t(decision.labelKey)] })] }), decisionDate && (_jsx("span", { className: "font-mono text-[11px] text-ink-4 whitespace-nowrap", children: formatDate(decisionDate, i18n.language) }))] }) })] }, card.principleId));
290
+ })] }))] }));
245
291
  }
@@ -18,24 +18,7 @@ import { fetchUpdateStatus, fetchUpdateHistory, applyUpdate, applyFullUpdate, ro
18
18
  import { validateUpdateStatus, validateUpdateHistory } from "../../utils/validators.js";
19
19
  import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, } from "../../components/ui/alert-dialog.js";
20
20
  import { Loader2, RotateCcw, CheckCircle2, XCircle, ArrowRight } from "lucide-react";
21
- // ── Helper: format ISO date string to locale-aware display ────────────────────
22
- function formatDate(isoString, locale) {
23
- try {
24
- const date = new Date(isoString);
25
- if (Number.isNaN(date.getTime()))
26
- return isoString;
27
- return date.toLocaleDateString(locale, {
28
- year: "numeric",
29
- month: "short",
30
- day: "numeric",
31
- hour: "2-digit",
32
- minute: "2-digit",
33
- });
34
- }
35
- catch {
36
- return isoString;
37
- }
38
- }
21
+ import { formatDate } from "../../utils/format-date.js";
39
22
  export function UpdatePage() {
40
23
  const { t, i18n } = useTranslation();
41
24
  const [statusData, setStatusData] = useState(null);
@@ -52,7 +35,6 @@ export function UpdatePage() {
52
35
  const [showRollbackDialog, setShowRollbackDialog] = useState(null);
53
36
  const [rollingBack, setRollingBack] = useState(false);
54
37
  const [rollbackResult, setRollbackResult] = useState(null);
55
- const locale = i18n.language === "zh-CN" ? "zh-CN" : "en-US";
56
38
  const loadData = useCallback(async () => {
57
39
  setLoadingState("loading");
58
40
  setErrorMessage(null);
@@ -197,7 +179,13 @@ export function UpdatePage() {
197
179
  }
198
180
  // ── Loaded state ─────────────────────────────────────────────────────────
199
181
  const isUpToDate = statusData ? !statusData.hasUpdate : true;
200
- return (_jsxs(PageShell, { children: [_jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsx("div", { className: "font-mono text-[12px] tracking-[0.14em] text-ink-3 uppercase mb-3", children: t("pages.update.eyebrow") }), _jsx("h1", { className: "text-[29px] font-semibold tracking-tight text-ink mb-2", children: t("pages.update.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.update.subtitle") }), _jsxs("section", { className: "mb-8", "aria-labelledby": "section-update-status", children: [_jsx(SectionTitle, { id: "section-update-status", children: t("pages.update.currentVersion") }), _jsxs("div", { className: "bg-panel border border-line rounded-[6px] p-5", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: t("pages.update.currentVersion") }), _jsx("span", { className: "font-mono text-[13px] text-ink", children: statusData?.currentVersion ?? "—" })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: t("pages.update.latestVersion") }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-mono text-[13px] text-ink", children: statusData?.latestVersion ?? "—" }), isUpToDate ? (_jsx("span", { className: "inline-flex items-center border border-green/35 text-green rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.upToDate") })) : (_jsx("span", { className: "inline-flex items-center border border-amber/35 text-amber rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.updateAvailable") }))] })] })] }), statusData?.error && (_jsxs("div", { className: "mt-4 pt-3 border-t border-line text-[12px] text-ink-4 font-mono", children: [t("pages.update.checkError"), ": ", statusData.error] })), statusData?.syncPending && (_jsx("div", { className: "mt-4 p-3 rounded-[4px] border border-amber/30 bg-amber/5", children: _jsx("p", { className: "text-[12px] text-amber leading-relaxed", children: t("pages.update.syncPending", { pluginVersion: statusData.pluginLatestVersion ?? "" }) }) })), statusData?.codexInstalled && (_jsx("div", { className: "mt-4 p-3 rounded-[4px] border border-amber/30 bg-amber/5", children: _jsx("p", { className: "text-[12px] text-amber leading-relaxed", children: t("pages.update.codexWarning") }) })), statusData?.changelog && statusData.hasUpdate && (_jsxs("div", { className: "mt-4 p-4 rounded-[4px] border border-line bg-surface/50", children: [_jsx("p", { className: "text-[12px] font-mono text-ink-3 uppercase tracking-wide mb-2", children: t("pages.update.whatsNew") }), _jsx("div", { className: "text-[13px] text-ink-2 leading-relaxed max-h-[300px] overflow-y-auto whitespace-pre-wrap", children: statusData.changelog })] })), _jsxs("div", { className: "mt-5 pt-4 border-t border-line flex items-center gap-3 flex-wrap", children: [_jsx("button", { type: "button", onClick: handleCheckForUpdates, disabled: checking || fullUpdating, className: "border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: checking ? t("pages.update.checking") : t("pages.update.checkForUpdates") }), !isUpToDate && (_jsx("button", { type: "button", onClick: () => setShowFullUpdateDialog(true), disabled: checking || fullUpdating, className: "bg-gov text-white rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-gov/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: fullUpdating ? (_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }), t("pages.update.updating")] })) : (t("pages.update.applyUpdate")) }))] }), updateResult && (_jsx("div", { className: `mt-4 p-4 rounded-[6px] border animate-[pdFadeIn_400ms_ease-out] ${updateResult.success ? 'bg-green/5 border-green/20' : 'bg-red/5 border-red/20'}`, children: _jsxs("div", { className: "flex items-start gap-3", children: [updateResult.success ? (_jsx(CheckCircle2, { className: "h-6 w-6 text-green shrink-0 mt-0.5 animate-[pdFadeIn_600ms_ease-out]", style: { transform: 'scale(1)', animationFillMode: 'both' } })) : (_jsx(XCircle, { className: "h-6 w-6 text-red shrink-0 mt-0.5" })), _jsxs("div", { className: "flex-1 min-w-0", children: [updateResult.success && updateResult.fromVersion && updateResult.newVersion && (_jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [_jsx("span", { className: "font-mono text-[13px] text-ink-3 line-through", children: updateResult.fromVersion }), _jsx(ArrowRight, { className: "h-3.5 w-3.5 text-green" }), _jsx("span", { className: "font-mono text-[13px] text-green font-medium", children: updateResult.newVersion })] })), _jsx("p", { className: `text-[13px] ${updateResult.success ? 'text-green' : 'text-red'}`, children: updateResult.message }), updateResult.success && updateResult.updatedFiles && updateResult.updatedFiles.length > 0 && (_jsx("p", { className: "mt-1 text-[12px] text-ink-4 font-mono", children: t("pages.update.filesUpdated", { count: updateResult.updatedFiles.length }) })), updateResult.success && !updateResult.requiresRestart && (_jsx("p", { className: "mt-2 text-[12px] font-mono text-ink-4", children: t("pages.update.restartHint") })), updateResult.success && updateResult.requiresRestart && (_jsx("p", { className: "mt-2 p-2 rounded-[3px] bg-amber/5 border border-amber/20 text-[12px] text-amber leading-relaxed", children: t("pages.update.fullUpdateRestartPrompt") })), updateResult.success && updateResult.partialUpdate && (_jsx("p", { className: "mt-1 text-[12px] text-amber leading-relaxed", children: t("pages.update.partialUpdateHint") })), !updateResult.success && updateResult.nextAction && (_jsx("p", { className: "mt-1 text-[12px] text-ink-4 font-mono leading-relaxed", children: updateResult.nextAction })), !updateResult.success && (_jsxs("div", { className: "mt-2 flex items-center gap-2", children: [/network|fetch|timeout|ECONNREFUSED|ENOTFOUND/i.test(updateResult.message) && (_jsx("p", { className: "text-[12px] text-ink-4 font-mono", children: t("pages.update.networkErrorHint") })), _jsx("button", { type: "button", onClick: handleApplyUpdate, disabled: updating, className: "text-[12px] text-red underline hover:text-red/80 disabled:opacity-50", children: t("pages.update.retry") })] }))] })] }) }))] })] }), _jsxs("section", { "aria-labelledby": "section-update-history", children: [_jsx(SectionTitle, { id: "section-update-history", children: t("pages.update.history") }), historyErrorReason ? (_jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: [t("pages.update.loadError"), " (", historyErrorReason, ")"] })) : historyData && historyData.updates.length > 0 ? (_jsx("div", { className: "space-y-[10px]", children: historyData.updates.map((entry) => (_jsx("article", { className: "bg-panel border border-line rounded-[6px] px-5 py-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "inline-flex items-center border border-line rounded-[2px] px-[7px] py-1 font-mono text-[11px] text-ink-3 bg-surface/80 uppercase", children: t("pages.update.version") }), _jsxs("span", { className: "font-mono text-[13px] text-ink", children: [entry.fromVersion, " \u2192 ", entry.toVersion] }), !entry.success && (_jsx("span", { className: "inline-flex items-center border border-red/35 text-red rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.failed") }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: formatDate(entry.timestamp, locale) }), entry.backupPath && (_jsxs("button", { type: "button", onClick: () => setShowRollbackDialog(entry.backupPath), disabled: rollingBack || updating, className: "flex items-center gap-1 border border-line bg-surface text-ink-3 rounded-[3px] px-[8px] py-[4px] text-[11px] hover:text-ink hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", title: t("pages.update.rollback"), children: [_jsx(RotateCcw, { className: "h-3 w-3" }), t("pages.update.rollback")] }))] })] }) }, entry.id))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.update.noHistory") }))] })] }), _jsx(AlertDialog, { open: showFullUpdateDialog, onOpenChange: setShowFullUpdateDialog, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: t("pages.update.confirmFullUpdateTitle") }), _jsx(AlertDialogDescription, { children: t("pages.update.confirmFullUpdateDesc") })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t("common.cancel") }), _jsx(AlertDialogAction, { onClick: handleApplyFullUpdate, children: t("pages.update.applyFullUpdate") })] })] }) }), _jsx(AlertDialog, { open: showRollbackDialog !== null, onOpenChange: (open) => { if (!open)
182
+ return (_jsxs(PageShell, { children: [_jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsx("div", { className: "font-mono text-[12px] tracking-[0.14em] text-ink-3 uppercase mb-3", children: t("pages.update.eyebrow") }), _jsx("h1", { className: "text-[29px] font-semibold tracking-tight text-ink mb-2", children: t("pages.update.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.update.subtitle") }), _jsxs("section", { className: "mb-8", "aria-labelledby": "section-update-status", children: [_jsx(SectionTitle, { id: "section-update-status", children: t("pages.update.currentVersion") }), _jsxs("div", { className: "bg-panel border border-line rounded-[6px] p-5", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: t("pages.update.currentVersion") }), _jsx("span", { className: "font-mono text-[13px] text-ink", children: statusData?.currentVersion ?? "—" })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: t("pages.update.latestVersion") }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "font-mono text-[13px] text-ink", children: statusData?.latestVersion ?? "—" }), isUpToDate ? (_jsx("span", { className: "inline-flex items-center border border-green/35 text-green rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.upToDate") })) : (_jsx("span", { className: "inline-flex items-center border border-amber/35 text-amber rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.updateAvailable") }))] })] })] }), statusData?.error && (_jsxs("div", { className: "mt-4 pt-3 border-t border-line text-[12px] text-ink-4 font-mono", children: [t("pages.update.checkError"), ": ", statusData.error] })), statusData?.syncPending && (_jsx("div", { className: "mt-4 p-3 rounded-[4px] border border-amber/30 bg-amber/5", children: _jsx("p", { className: "text-[12px] text-amber leading-relaxed", children: t("pages.update.syncPending", { pluginVersion: statusData.pluginLatestVersion ?? "" }) }) })), statusData?.codexInstalled && (_jsx("div", { className: "mt-4 p-3 rounded-[4px] border border-amber/30 bg-amber/5", children: _jsx("p", { className: "text-[12px] text-amber leading-relaxed", children: t("pages.update.codexWarning") }) })), statusData?.changelog && statusData.hasUpdate && (_jsxs("div", { className: "mt-4 p-4 rounded-[4px] border border-line bg-surface/50", children: [_jsx("p", { className: "text-[12px] font-mono text-ink-3 uppercase tracking-wide mb-2", children: t("pages.update.whatsNew") }), _jsx("div", { className: "text-[13px] text-ink-2 leading-relaxed max-h-[300px] overflow-y-auto whitespace-pre-wrap", children: statusData.changelog })] })), _jsxs("div", { className: "mt-5 pt-4 border-t border-line flex items-center gap-3 flex-wrap", children: [_jsx("button", { type: "button", onClick: handleCheckForUpdates, disabled: checking || fullUpdating, className: "border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: checking ? t("pages.update.checking") : t("pages.update.checkForUpdates") }), !isUpToDate && (_jsx("button", { type: "button", onClick: () => setShowFullUpdateDialog(true), disabled: checking || fullUpdating, className: "bg-gov text-white rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-gov/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: fullUpdating ? (_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }), t("pages.update.updating")] })) : (t("pages.update.applyUpdate")) }))] }), updateResult && (_jsx("div", { className: `mt-4 p-4 rounded-[6px] border animate-[pdFadeIn_400ms_ease-out] ${updateResult.success ? 'bg-green/5 border-green/20' : 'bg-red/5 border-red/20'}`, children: _jsxs("div", { className: "flex items-start gap-3", children: [updateResult.success ? (_jsx(CheckCircle2, { className: "h-6 w-6 text-green shrink-0 mt-0.5 animate-[pdFadeIn_600ms_ease-out]", style: { transform: 'scale(1)', animationFillMode: 'both' } })) : (_jsx(XCircle, { className: "h-6 w-6 text-red shrink-0 mt-0.5" })), _jsxs("div", { className: "flex-1 min-w-0", children: [updateResult.success && updateResult.fromVersion && updateResult.newVersion && (_jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [_jsx("span", { className: "font-mono text-[13px] text-ink-3 line-through", children: updateResult.fromVersion }), _jsx(ArrowRight, { className: "h-3.5 w-3.5 text-green" }), _jsx("span", { className: "font-mono text-[13px] text-green font-medium", children: updateResult.newVersion })] })), _jsx("p", { className: `text-[13px] ${updateResult.success ? 'text-green' : 'text-red'}`, children: updateResult.message }), updateResult.success && updateResult.updatedFiles && updateResult.updatedFiles.length > 0 && (_jsx("p", { className: "mt-1 text-[12px] text-ink-4 font-mono", children: t("pages.update.filesUpdated", { count: updateResult.updatedFiles.length }) })), updateResult.success && !updateResult.requiresRestart && (_jsx("p", { className: "mt-2 text-[12px] font-mono text-ink-4", children: t("pages.update.restartHint") })), updateResult.success && updateResult.requiresRestart && (_jsx("p", { className: "mt-2 p-2 rounded-[3px] bg-amber/5 border border-amber/20 text-[12px] text-amber leading-relaxed", children: t("pages.update.fullUpdateRestartPrompt") })), updateResult.success && updateResult.partialUpdate && (_jsx("p", { className: "mt-1 text-[12px] text-amber leading-relaxed", children: t("pages.update.partialUpdateHint") })), !updateResult.success && updateResult.nextAction && (_jsx("p", { className: "mt-1 text-[12px] text-ink-4 font-mono leading-relaxed", children: updateResult.nextAction })), !updateResult.success && (_jsxs("div", { className: "mt-2 flex items-center gap-2", children: [/network|fetch|timeout|ECONNREFUSED|ENOTFOUND/i.test(updateResult.message) && (_jsx("p", { className: "text-[12px] text-ink-4 font-mono", children: t("pages.update.networkErrorHint") })), _jsx("button", { type: "button", onClick: handleApplyUpdate, disabled: updating, className: "text-[12px] text-red underline hover:text-red/80 disabled:opacity-50", children: t("pages.update.retry") })] }))] })] }) }))] })] }), _jsxs("section", { "aria-labelledby": "section-update-history", children: [_jsx(SectionTitle, { id: "section-update-history", children: t("pages.update.history") }), historyErrorReason ? (_jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: [t("pages.update.loadError"), " (", historyErrorReason, ")"] })) : historyData && historyData.updates.length > 0 ? (_jsx("div", { className: "space-y-[10px]", children: historyData.updates.map((entry) => (_jsx("article", { className: "bg-panel border border-line rounded-[6px] px-5 py-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "inline-flex items-center border border-line rounded-[2px] px-[7px] py-1 font-mono text-[11px] text-ink-3 bg-surface/80 uppercase", children: t("pages.update.version") }), _jsxs("span", { className: "font-mono text-[13px] text-ink", children: [entry.fromVersion, " \u2192 ", entry.toVersion] }), !entry.success && (_jsx("span", { className: "inline-flex items-center border border-red/35 text-red rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.failed") }))] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-ink-3 text-[13px]", children: formatDate(entry.timestamp, i18n.language, {
183
+ year: "numeric",
184
+ month: "short",
185
+ day: "numeric",
186
+ hour: "2-digit",
187
+ minute: "2-digit",
188
+ }) }), entry.backupPath && (_jsxs("button", { type: "button", onClick: () => setShowRollbackDialog(entry.backupPath), disabled: rollingBack || updating, className: "flex items-center gap-1 border border-line bg-surface text-ink-3 rounded-[3px] px-[8px] py-[4px] text-[11px] hover:text-ink hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", title: t("pages.update.rollback"), children: [_jsx(RotateCcw, { className: "h-3 w-3" }), t("pages.update.rollback")] }))] })] }) }, entry.id))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.update.noHistory") }))] })] }), _jsx(AlertDialog, { open: showFullUpdateDialog, onOpenChange: setShowFullUpdateDialog, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: t("pages.update.confirmFullUpdateTitle") }), _jsx(AlertDialogDescription, { children: t("pages.update.confirmFullUpdateDesc") })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t("common.cancel") }), _jsx(AlertDialogAction, { onClick: handleApplyFullUpdate, children: t("pages.update.applyFullUpdate") })] })] }) }), _jsx(AlertDialog, { open: showRollbackDialog !== null, onOpenChange: (open) => { if (!open)
201
189
  setShowRollbackDialog(null); }, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: t("pages.update.confirmRollbackTitle") }), _jsx(AlertDialogDescription, { children: t("pages.update.confirmRollbackDesc") })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: t("common.cancel") }), _jsx(AlertDialogAction, { onClick: () => { if (showRollbackDialog)
202
190
  handleRollback(showRollbackDialog); }, children: t("pages.update.rollback") })] })] }) }), rollbackResult && (_jsxs("div", { className: `fixed bottom-4 right-4 max-w-md p-3 rounded-[4px] text-[13px] z-50 ${rollbackResult.success ? 'bg-green/10 border border-green/20 text-green' : 'bg-red/10 border border-red/20 text-red'}`, children: [_jsx("p", { children: rollbackResult.message }), rollbackResult.success && (_jsx("p", { className: "mt-1 text-[12px] font-mono opacity-80", children: t("pages.update.restartHintAfterRollback") }))] }))] }));
203
191
  }