create-principles-disciple 1.144.25 → 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/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/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 +38 -9
- package/core/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/core/dist/runtime-v2/internalization/evaluator-runner.js +170 -46
- 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/package.json +1 -1
- package/host-runtime/package.json +1 -1
- package/package.json +1 -1
- package/plugin/dist/bundle.js +447 -438
- package/release-manager/package.json +1 -1
|
@@ -22,6 +22,98 @@ export declare function summarizeDecisionResults(results: readonly DecisionResul
|
|
|
22
22
|
failureReason?: string;
|
|
23
23
|
successWarnings: string[];
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* SPEC-PD-UIUX-001 R2: how the owner-inbox display items map onto the Focus
|
|
27
|
+
* page after the reading-order rework. Pure display partitioning — no
|
|
28
|
+
* permission or lifecycle decisions happen here.
|
|
29
|
+
*
|
|
30
|
+
* - decision items whose principleId matches an on-screen action card are NOT
|
|
31
|
+
* duplicated as full cards (one primary action surface per object); the
|
|
32
|
+
* match uses the verified principleId association field, never titles;
|
|
33
|
+
* - unmatched decision items are preserved (with their own entry) — objects
|
|
34
|
+
* are never hidden for visual tidiness;
|
|
35
|
+
* - two different decisions of the same principle are never merged: each
|
|
36
|
+
* action card keeps its own object; this partition only affects the
|
|
37
|
+
* display-only inbox projection;
|
|
38
|
+
* - blocked items follow the inbox.attention semantics: 'individual' items
|
|
39
|
+
* render one card each, everything else gets one aggregate count;
|
|
40
|
+
* - historical unknowns stay an explanatory notice (not part of the todo
|
|
41
|
+
* count).
|
|
42
|
+
*/
|
|
43
|
+
export declare function partitionInboxForDisplay<T extends {
|
|
44
|
+
principleId: string;
|
|
45
|
+
inbox: {
|
|
46
|
+
group: string;
|
|
47
|
+
attention: string;
|
|
48
|
+
};
|
|
49
|
+
}>(inbox: {
|
|
50
|
+
groups: {
|
|
51
|
+
decision: T[];
|
|
52
|
+
blocked: T[];
|
|
53
|
+
recovery: T[];
|
|
54
|
+
};
|
|
55
|
+
historicalUnknown: {
|
|
56
|
+
count: number;
|
|
57
|
+
};
|
|
58
|
+
}, actionCardPrincipleIds: ReadonlySet<string>): {
|
|
59
|
+
unmatchedDecisions: T[];
|
|
60
|
+
blockedIndividual: T[];
|
|
61
|
+
blockedAggregate: T[];
|
|
62
|
+
recovery: T[];
|
|
63
|
+
pendingTotal: number;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* SPEC-PD-UIUX-001 R2 (Owner review round 2): the conclusion strip's headline
|
|
67
|
+
* and total must agree with the objects actually on screen and with the inbox
|
|
68
|
+
* response contract.
|
|
69
|
+
*
|
|
70
|
+
* Modes (display-only; no permission derivation):
|
|
71
|
+
* - 'unavailable' — inbox projection off (feature_disabled surfaces as
|
|
72
|
+
* ownerInbox===null with NO error) or the request failed: the pending count
|
|
73
|
+
* CANNOT be confirmed; never allClear.
|
|
74
|
+
* - 'degraded' — the inbox answered HTTP-success but carries
|
|
75
|
+
* `degraded: { reason, nextAction }` (e.g. principle ledger unreadable, the
|
|
76
|
+
* server then returns EMPTY groups): counts cannot be trusted; the strip
|
|
77
|
+
* shows the server's reason + nextAction and never allClear.
|
|
78
|
+
* - 'ok' — counts are trusted.
|
|
79
|
+
*
|
|
80
|
+
* Counting (S2): displayTotal = actionableCount + inbox pending items NOT
|
|
81
|
+
* already represented by an on-screen action card. The association is the
|
|
82
|
+
* verified principleId field (partition.unmatchedDecisions are inbox items
|
|
83
|
+
* WITHOUT a card). One inbox item matching N cards of the same principle
|
|
84
|
+
* counts the N cards (each is a distinct decision); disjoint sets sum.
|
|
85
|
+
*/
|
|
86
|
+
export declare function deriveConclusionDisplay(input: {
|
|
87
|
+
ownerInbox: {
|
|
88
|
+
groups: {
|
|
89
|
+
decision: unknown[];
|
|
90
|
+
};
|
|
91
|
+
degraded?: {
|
|
92
|
+
reason: string;
|
|
93
|
+
nextAction: string;
|
|
94
|
+
};
|
|
95
|
+
} | null;
|
|
96
|
+
ownerInboxError: string | null;
|
|
97
|
+
partition: {
|
|
98
|
+
pendingTotal: number;
|
|
99
|
+
unmatchedDecisions: unknown[];
|
|
100
|
+
} | null;
|
|
101
|
+
actionableCount: number;
|
|
102
|
+
}): {
|
|
103
|
+
mode: 'ok' | 'degraded' | 'unavailable';
|
|
104
|
+
displayTotal: number;
|
|
105
|
+
actionableCount: number;
|
|
106
|
+
degraded?: {
|
|
107
|
+
reason: string;
|
|
108
|
+
nextAction: string;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* SPEC-PD-UIUX-001 R2 (Owner review S3): EVERY conclusion branch must express
|
|
113
|
+
* governance readiness the same way — "actionable now" is only claimable when
|
|
114
|
+
* the actions are not locked. One matrix, one source.
|
|
115
|
+
*/
|
|
116
|
+
export declare function resolveConclusionActionableKey(mode: 'ok' | 'degraded' | 'unavailable', locked: boolean): string;
|
|
25
117
|
export interface OwnerConfigureCommand {
|
|
26
118
|
/** i18n key for the shell label, e.g. "pages.focus.experience.ownerGuide.cmdPowerShell". */
|
|
27
119
|
labelKey: string;
|
|
@@ -42,6 +42,93 @@ export function summarizeDecisionResults(results) {
|
|
|
42
42
|
successWarnings,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* SPEC-PD-UIUX-001 R2: how the owner-inbox display items map onto the Focus
|
|
47
|
+
* page after the reading-order rework. Pure display partitioning — no
|
|
48
|
+
* permission or lifecycle decisions happen here.
|
|
49
|
+
*
|
|
50
|
+
* - decision items whose principleId matches an on-screen action card are NOT
|
|
51
|
+
* duplicated as full cards (one primary action surface per object); the
|
|
52
|
+
* match uses the verified principleId association field, never titles;
|
|
53
|
+
* - unmatched decision items are preserved (with their own entry) — objects
|
|
54
|
+
* are never hidden for visual tidiness;
|
|
55
|
+
* - two different decisions of the same principle are never merged: each
|
|
56
|
+
* action card keeps its own object; this partition only affects the
|
|
57
|
+
* display-only inbox projection;
|
|
58
|
+
* - blocked items follow the inbox.attention semantics: 'individual' items
|
|
59
|
+
* render one card each, everything else gets one aggregate count;
|
|
60
|
+
* - historical unknowns stay an explanatory notice (not part of the todo
|
|
61
|
+
* count).
|
|
62
|
+
*/
|
|
63
|
+
export function partitionInboxForDisplay(inbox, actionCardPrincipleIds) {
|
|
64
|
+
const blockedIndividual = inbox.groups.blocked.filter((item) => item.inbox.attention === "individual");
|
|
65
|
+
const blockedAggregate = inbox.groups.blocked.filter((item) => item.inbox.attention !== "individual");
|
|
66
|
+
const unmatchedDecisions = inbox.groups.decision.filter((item) => !actionCardPrincipleIds.has(item.principleId));
|
|
67
|
+
return {
|
|
68
|
+
unmatchedDecisions,
|
|
69
|
+
blockedIndividual,
|
|
70
|
+
blockedAggregate,
|
|
71
|
+
recovery: inbox.groups.recovery,
|
|
72
|
+
// 待处理口径: decision + blocked-individual + recovery (INV-01 lineage).
|
|
73
|
+
pendingTotal: inbox.groups.decision.length + blockedIndividual.length + inbox.groups.recovery.length,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* SPEC-PD-UIUX-001 R2 (Owner review round 2): the conclusion strip's headline
|
|
78
|
+
* and total must agree with the objects actually on screen and with the inbox
|
|
79
|
+
* response contract.
|
|
80
|
+
*
|
|
81
|
+
* Modes (display-only; no permission derivation):
|
|
82
|
+
* - 'unavailable' — inbox projection off (feature_disabled surfaces as
|
|
83
|
+
* ownerInbox===null with NO error) or the request failed: the pending count
|
|
84
|
+
* CANNOT be confirmed; never allClear.
|
|
85
|
+
* - 'degraded' — the inbox answered HTTP-success but carries
|
|
86
|
+
* `degraded: { reason, nextAction }` (e.g. principle ledger unreadable, the
|
|
87
|
+
* server then returns EMPTY groups): counts cannot be trusted; the strip
|
|
88
|
+
* shows the server's reason + nextAction and never allClear.
|
|
89
|
+
* - 'ok' — counts are trusted.
|
|
90
|
+
*
|
|
91
|
+
* Counting (S2): displayTotal = actionableCount + inbox pending items NOT
|
|
92
|
+
* already represented by an on-screen action card. The association is the
|
|
93
|
+
* verified principleId field (partition.unmatchedDecisions are inbox items
|
|
94
|
+
* WITHOUT a card). One inbox item matching N cards of the same principle
|
|
95
|
+
* counts the N cards (each is a distinct decision); disjoint sets sum.
|
|
96
|
+
*/
|
|
97
|
+
export function deriveConclusionDisplay(input) {
|
|
98
|
+
if (input.ownerInbox === null || input.ownerInboxError !== null || input.partition === null) {
|
|
99
|
+
return { mode: 'unavailable', displayTotal: input.actionableCount, actionableCount: input.actionableCount };
|
|
100
|
+
}
|
|
101
|
+
const matchedDecisions = input.ownerInbox.groups.decision.length - input.partition.unmatchedDecisions.length;
|
|
102
|
+
const uncoveredInboxPending = Math.max(0, input.partition.pendingTotal - matchedDecisions);
|
|
103
|
+
const base = {
|
|
104
|
+
actionableCount: input.actionableCount,
|
|
105
|
+
};
|
|
106
|
+
if (input.ownerInbox.degraded !== undefined) {
|
|
107
|
+
return {
|
|
108
|
+
mode: 'degraded',
|
|
109
|
+
displayTotal: input.actionableCount + uncoveredInboxPending,
|
|
110
|
+
...base,
|
|
111
|
+
degraded: input.ownerInbox.degraded,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
mode: 'ok',
|
|
116
|
+
displayTotal: input.actionableCount + uncoveredInboxPending,
|
|
117
|
+
...base,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* SPEC-PD-UIUX-001 R2 (Owner review S3): EVERY conclusion branch must express
|
|
122
|
+
* governance readiness the same way — "actionable now" is only claimable when
|
|
123
|
+
* the actions are not locked. One matrix, one source.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveConclusionActionableKey(mode, locked) {
|
|
126
|
+
if (locked)
|
|
127
|
+
return "pages.focus.conclusion.actionableLocked";
|
|
128
|
+
if (mode === 'unavailable')
|
|
129
|
+
return "pages.focus.conclusion.actionableWithoutTotal";
|
|
130
|
+
return "pages.focus.conclusion.actionableUnlocked";
|
|
131
|
+
}
|
|
45
132
|
// ── i18n code mapping helpers ─────────────────────────────────────────────
|
|
46
133
|
/** Map a stateReasonCode to an i18n key, with optional interpolation params. */
|
|
47
134
|
function getStateReasonText(code, t, pendingReviewCount) {
|
|
@@ -600,6 +687,40 @@ export function FocusPage({ featureFlags }) {
|
|
|
600
687
|
: t(experienceData.readiness.authenticationMode !== "authenticated"
|
|
601
688
|
? "pages.focus.ownerDecision.actionsLockedAuth"
|
|
602
689
|
: "pages.focus.ownerDecision.actionsLockedIdentity");
|
|
690
|
+
// ── SPEC-PD-UIUX-001 R2: one primary action surface per decision object ──
|
|
691
|
+
// Matching uses the verified principleId association field (never titles).
|
|
692
|
+
// See partitionInboxForDisplay for the semantics.
|
|
693
|
+
const actionCardPrincipleIds = new Set();
|
|
694
|
+
for (const item of decisionItems) {
|
|
695
|
+
if (item.principleId !== undefined)
|
|
696
|
+
actionCardPrincipleIds.add(item.principleId);
|
|
697
|
+
}
|
|
698
|
+
for (const group of pendingGroups)
|
|
699
|
+
actionCardPrincipleIds.add(group.principleId);
|
|
700
|
+
const inboxPartition = ownerInbox !== null && ownerInboxError === null
|
|
701
|
+
? partitionInboxForDisplay(ownerInbox, actionCardPrincipleIds)
|
|
702
|
+
: null;
|
|
703
|
+
const inboxBlockedIndividual = inboxPartition?.blockedIndividual ?? [];
|
|
704
|
+
const inboxBlockedAggregate = inboxPartition?.blockedAggregate ?? [];
|
|
705
|
+
const inboxRecovery = inboxPartition?.recovery ?? [];
|
|
706
|
+
const unmatchedInboxDecisions = inboxPartition?.unmatchedDecisions ?? [];
|
|
707
|
+
// 待处理口径 (INV-01 lineage): decision + blocked-individual + recovery —
|
|
708
|
+
// same three groups the inbox renders; historical unknowns stay explanatory.
|
|
709
|
+
const inboxPendingTotal = inboxPartition?.pendingTotal ?? 0;
|
|
710
|
+
// 可操作口径: the action cards actually on screen (decision cards + approval
|
|
711
|
+
// cards) — the same number the「需要你决定」section header shows.
|
|
712
|
+
const actionableCount = (ownerDecisionError === null ? decisionItems.length : 0)
|
|
713
|
+
+ (showApprovalCards ? pendingGroups.length : 0);
|
|
714
|
+
// Review round 2 (P2): the headline never claims allClear from the inbox
|
|
715
|
+
// alone, degraded payloads surface their reason, and the total is the
|
|
716
|
+
// association-deduped union of on-screen cards and inbox items — see
|
|
717
|
+
// deriveConclusionDisplay.
|
|
718
|
+
const conclusionDisplay = deriveConclusionDisplay({
|
|
719
|
+
ownerInbox,
|
|
720
|
+
ownerInboxError,
|
|
721
|
+
partition: inboxPartition,
|
|
722
|
+
actionableCount,
|
|
723
|
+
});
|
|
603
724
|
// Map codes to i18n text
|
|
604
725
|
// Governance Recovery Actions v1: tasks_need_human_review interpolates the
|
|
605
726
|
// needs_human_review task count, not the approvals count.
|
|
@@ -607,9 +728,17 @@ export function FocusPage({ featureFlags }) {
|
|
|
607
728
|
const stateReasonCount = stateReasonCode === "tasks_need_human_review" ? humanReviewCount : pendingCount;
|
|
608
729
|
const stateReason = getStateReasonText(stateReasonCode, t, stateReasonCount);
|
|
609
730
|
const nextAction = getNextActionText(nextActionCode, t);
|
|
610
|
-
return (_jsx(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.focus.eyebrow") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mb-2", duration: 4.5, brightness: 0.5, disabled: pendingCount === 0, children: t("pages.focus.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.focus.subtitle") }), _jsxs("section", { "aria-label": t("pages.focus.
|
|
611
|
-
|
|
612
|
-
|
|
731
|
+
return (_jsx(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.focus.eyebrow") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mb-2", duration: 4.5, brightness: 0.5, disabled: pendingCount === 0, children: t("pages.focus.title") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[760px] leading-relaxed mb-7", children: t("pages.focus.subtitle") }), _jsxs("section", { "aria-label": t("pages.focus.conclusion.sectionTitle"), "data-testid": "focus-conclusion", className: "mb-7 px-[18px] py-[14px] bg-panel border border-line rounded-[6px]", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx("span", { className: `inline-flex items-center rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase ${experienceMode && experienceData !== null
|
|
732
|
+
? EXPERIENCE_ATTENTION[experienceData.summary.primaryAttention].badgeClass
|
|
733
|
+
: governanceState === "owner_review_ready"
|
|
734
|
+
? "bg-gov/10 text-gov border border-gov/20"
|
|
735
|
+
: governanceState === "degraded"
|
|
736
|
+
? "bg-amber/10 text-amber border border-amber/20"
|
|
737
|
+
: governanceState === "in_progress"
|
|
738
|
+
? "bg-green/10 text-green border border-green/20"
|
|
739
|
+
: "text-ink-4 border border-line bg-surface/80"}`, role: "status", "data-testid": "focus-conclusion-badge", children: experienceMode && experienceData !== null
|
|
740
|
+
? t(EXPERIENCE_ATTENTION[experienceData.summary.primaryAttention].labelKey)
|
|
741
|
+
: t(`pages.focus.stateLabel.${governanceState}`) }), _jsx("div", { className: "text-ink-2 text-[13px] leading-relaxed", "data-testid": "focus-conclusion-counts", children: conclusionDisplay.mode === 'ok' && conclusionDisplay.displayTotal === 0 ? (_jsx("span", { children: t("pages.focus.conclusion.allClear") })) : conclusionDisplay.mode === 'unavailable' || conclusionDisplay.mode === 'degraded' ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium", "data-testid": "focus-conclusion-uncertain", children: t("pages.focus.conclusion.pendingUncertain") }), conclusionDisplay.actionableCount > 0 && (_jsxs("span", { className: "text-ink-4", children: [" · ", t(resolveConclusionActionableKey(conclusionDisplay.mode, ownerActionsLockedReason !== null), { count: conclusionDisplay.actionableCount })] }))] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium", children: t("pages.focus.conclusion.pendingTotal", { count: conclusionDisplay.displayTotal }) }), _jsxs("span", { className: "text-ink-4", children: [" · ", t(resolveConclusionActionableKey(conclusionDisplay.mode, ownerActionsLockedReason !== null), { count: conclusionDisplay.actionableCount })] })] })) })] }), conclusionDisplay.mode === 'degraded' && conclusionDisplay.degraded && (_jsxs("div", { className: "mt-3 border border-amber/30 rounded-[6px] bg-surface px-3 py-2.5", "data-testid": "focus-conclusion-degraded", children: [_jsxs("p", { className: "text-[12px] text-ink-3 leading-relaxed", children: [t("pages.focus.conclusion.degradedLabel"), " ", conclusionDisplay.degraded.reason] }), _jsx("p", { className: "mt-1 text-[12px] text-ink-3 leading-relaxed", children: conclusionDisplay.degraded.nextAction })] })), ownerActionsLockedReason !== null && (_jsxs("div", { id: "focus-global-lock-notice", className: "mt-3 flex items-start justify-between gap-3 border border-amber/30 rounded-[6px] bg-surface px-3 py-2.5", "data-testid": "focus-conclusion-lock", children: [_jsx("p", { className: "text-[12px] text-ink-3 leading-relaxed", children: ownerActionsLockedReason }), _jsx(Link, { to: "/settings", className: "shrink-0 text-[12px] text-gov underline hover:text-gov-2", children: t("pages.focus.ownerDecision.goSettingsCta") })] })), _jsx("p", { className: "mt-2 text-ink-4 text-[11.5px] leading-relaxed", "data-testid": "focus-conclusion-scope-note", children: t("pages.focus.conclusion.countsNote") })] }), _jsxs("section", { "aria-label": t("pages.focus.ownerDecision.sectionTitle"), className: "mb-7", "data-testid": "owner-decisions-section", children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-3", children: [_jsx(SectionTitle, { children: t("pages.focus.ownerDecision.sectionTitle") }), _jsx("span", { className: "font-mono text-ink-4 text-[12px]", children: ownerDecisionError !== null
|
|
613
742
|
? t("pages.focus.ownerDecision.unavailable")
|
|
614
743
|
: `· ${decisionItems.length + (showApprovalCards ? pendingGroups.length : 0)}` })] }), ownerDecisionError !== null && (_jsx("p", { className: "text-ink-4 text-[12.5px]", children: t("pages.focus.ownerDecision.loadError") })), ownerDecisionError === null && decisionItems.length === 0 && !showApprovalCards && (_jsx("p", { className: "text-ink-4 text-[13px]", children: t("pages.focus.ownerDecision.empty") })), decisionItems.map((item) => (_jsx(OwnerDecisionCard, { item: item, governanceReady: experienceData === null || (experienceData.readiness.authenticationMode === "authenticated"
|
|
615
744
|
&& experienceData.readiness.ownerIdentityConfiguration === "configured"), actionsLockedReason: ownerActionsLockedReason ?? undefined, onResolved: () => { void loadData(); } }, item.reviewKey))), (() => {
|
|
@@ -627,16 +756,18 @@ export function FocusPage({ featureFlags }) {
|
|
|
627
756
|
budget: unconfirmed.budget,
|
|
628
757
|
full: entry.truncated ? t("pages.focus.promptCapacityPerHostFull") : "",
|
|
629
758
|
}) }, entry.hostKind))) }), unconfirmed.nextAction !== undefined && (_jsx("p", { className: "mt-1 text-ink-2", children: unconfirmed.nextAction })), _jsxs("div", { className: "mt-2 flex gap-2", children: [["openclaw", "codex"].map((host) => (_jsx("button", { type: "button", onClick: () => setPromptTargetHost(host), "data-testid": `prompt-capacity-pick-${host}`, className: `inline-flex items-center border rounded-[3px] px-[10px] py-[4px] transition-colors ${promptTargetHost === host ? "border-amber bg-amber/10" : "border-line hover:border-line-2"}`, children: t("pages.focus.promptCapacityViewAsHost", { host: host === "openclaw" ? "OpenClaw" : "Codex" }) }, host))), promptTargetHost !== null && (_jsx("button", { type: "button", onClick: () => setPromptTargetHost(null), "data-testid": "prompt-capacity-pick-clear", className: "inline-flex items-center border border-line rounded-[3px] px-[10px] py-[4px] hover:border-line-2", children: t("pages.focus.promptCapacityClearHost", { defaultValue: "清除选择" }) }))] })] }));
|
|
630
|
-
})(), groupedAvailable && pendingGroups.map((group) => (_jsx(PendingReviewCard, { group: group, actionsLockedReason: ownerActionsLockedReason ?? undefined, promptInjection: groupedData.promptInjection, promptTargetHost: promptTargetHost ?? undefined, onDecisionApplied: () => { void loadData(); } }, group.principleId)))] }),
|
|
759
|
+
})(), groupedAvailable && pendingGroups.map((group) => (_jsx(PendingReviewCard, { group: group, actionsLockedReason: ownerActionsLockedReason ?? undefined, promptInjection: groupedData.promptInjection, promptTargetHost: promptTargetHost ?? undefined, onDecisionApplied: () => { void loadData(); } }, group.principleId)))] }), _jsxs("section", { "aria-label": t("pages.focus.ownerAttention.sectionTitle"), className: "mb-7", "data-testid": "owner-attention-section", children: [_jsxs("div", { className: "flex items-baseline gap-2 mb-3", children: [_jsx(SectionTitle, { children: t("pages.focus.ownerAttention.sectionTitle") }), ownerInbox !== null && ownerInboxError === null && (_jsx("span", { className: "font-mono text-ink-4 text-[12px]", "data-testid": "owner-attention-counts", children: `· ${t("pages.focus.ownerAttention.blockedCount", { count: ownerInbox.groups.blocked.length })} · ${t("pages.focus.ownerAttention.recoveryCount", { count: inboxRecovery.length })}` }))] }), ownerInboxError !== null && (_jsx("p", { className: "text-ink-4 text-[12.5px]", children: t("pages.focus.ownerInbox.loadError") })), ownerInbox !== null && ownerInboxError === null && (_jsxs(_Fragment, { children: [ownerInbox.groups.blocked.length === 0
|
|
760
|
+
&& inboxRecovery.length === 0
|
|
761
|
+
&& unmatchedInboxDecisions.length === 0 && (_jsx("p", { className: "text-ink-4 text-[13px]", children: t("pages.focus.ownerAttention.empty") })), unmatchedInboxDecisions.length > 0 && (_jsx("p", { className: "mb-2 text-ink-4 text-[12px] leading-relaxed", "data-testid": "owner-attention-unmatched-note", children: t("pages.focus.ownerAttention.unmatchedTitle") })), unmatchedInboxDecisions.map((item) => (_jsx(InboxItem, { item: item, t: t, note: t("pages.focus.ownerAttention.unmatchedItemNote") }, item.principleId))), inboxBlockedIndividual.map((item) => (_jsx(InboxItem, { item: item, t: t }, item.principleId))), inboxRecovery.map((item) => (_jsx(InboxItem, { item: item, t: t }, item.principleId))), inboxBlockedAggregate.length > 0 && (_jsxs("p", { className: "mt-2 text-ink-4 text-[12.5px] leading-relaxed", "data-testid": "owner-attention-aggregate-notice", children: [t("pages.focus.ownerAttention.blockedAggregate", { count: inboxBlockedAggregate.length }), " ", _jsx(Link, { to: "/principles", className: "text-gov hover:underline", children: t("pages.focus.ownerInbox.viewLibrary") })] })), ownerInbox.historicalUnknown.count > 0 && (_jsxs("p", { className: "text-ink-4 text-[12.5px] leading-relaxed mt-2", "data-testid": "owner-inbox-aggregate-notice", children: [ownerInbox.historicalUnknown.reasonText, " ", _jsx(Link, { to: "/principles", className: "text-gov hover:underline", children: t("pages.focus.ownerInbox.viewLibrary") })] }))] }))] }), experienceMode && experienceData !== null ? (_jsx(ExperienceSummaryCard, { snapshot: experienceData })) : !experienceMode ? (_jsxs("div", { className: "mb-7 px-[18px] py-[14px] bg-panel border border-line rounded-[6px]", children: [_jsx("div", { className: "flex items-center gap-2 mb-1", children: _jsx("span", { className: `inline-flex items-center rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase ${governanceState === "owner_review_ready" ? "bg-gov/10 text-gov border border-gov/20"
|
|
631
762
|
: governanceState === "degraded" ? "bg-amber/10 text-amber border border-amber/20"
|
|
632
763
|
: governanceState === "in_progress" ? "bg-green/10 text-green border border-green/20"
|
|
633
|
-
: "text-ink-4 border border-line bg-surface/80"}`, role: "status", children: t(`pages.focus.stateLabel.${governanceState}`) }) }), _jsx("div", { className: "text-ink-2 text-[13px] leading-relaxed mt-1", children: stateReason }), _jsxs("div", { className: "text-ink-4 text-[13px] leading-relaxed mt-1", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.nextActionLabel") }), " ", nextAction] })] })) : null, !experienceMode && (_jsxs(_Fragment, { children: [_jsx(ProseSummary, { pendingCount: pendingCount, deviationCount: deviationCount, stagnationCount: stagnationCount, evidenceCount: evidenceCount }), _jsx(FeedbackStratification, { gateBlocksToday: gateBlocksToday, inProgressCount: evidenceCount, pendingCount: pendingCount }), !experienceMode && governanceState === "none" && pendingCount === 0 && deviationCount === 0 && stagnationCount === 0 && (stateReasonCode === "state_db_missing" ? (_jsx(ZeroStateDbMissing, {})) : (_jsxs(_Fragment, { children: [_jsx(ZeroStateHealthy, {}), _jsx("div", { className: "mt-4", children: _jsx(OnboardingGuide, {}) })] }))), governanceState === "in_progress" && inProgressSummary && (_jsx(InProgressGuide, { summary: inProgressSummary })), degradedSignals && degradedSignals.length > 0 && (_jsx(DegradedSummary, { signals: degradedSignals }))] })),
|
|
764
|
+
: "text-ink-4 border border-line bg-surface/80"}`, role: "status", children: t(`pages.focus.stateLabel.${governanceState}`) }) }), _jsx("div", { className: "text-ink-2 text-[13px] leading-relaxed mt-1", children: stateReason }), _jsxs("div", { className: "text-ink-4 text-[13px] leading-relaxed mt-1", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.nextActionLabel") }), " ", nextAction] })] })) : null, !experienceMode && (_jsxs(_Fragment, { children: [_jsx(ProseSummary, { pendingCount: pendingCount, deviationCount: deviationCount, stagnationCount: stagnationCount, evidenceCount: evidenceCount }), _jsx(FeedbackStratification, { gateBlocksToday: gateBlocksToday, inProgressCount: evidenceCount, pendingCount: pendingCount }), !experienceMode && governanceState === "none" && pendingCount === 0 && deviationCount === 0 && stagnationCount === 0 && (stateReasonCode === "state_db_missing" ? (_jsx(ZeroStateDbMissing, {})) : (_jsxs(_Fragment, { children: [_jsx(ZeroStateHealthy, {}), _jsx("div", { className: "mt-4", children: _jsx(OnboardingGuide, {}) })] }))), governanceState === "in_progress" && inProgressSummary && (_jsx(InProgressGuide, { summary: inProgressSummary })), degradedSignals && degradedSignals.length > 0 && (_jsx(DegradedSummary, { signals: degradedSignals }))] })), experienceMode && experienceData !== null && experienceData.summary.primaryAttention === "all_clear" && pendingGroups.length === 0 && (_jsx("div", { className: "mt-4", children: _jsx(OnboardingGuide, {}) })), !experienceMode && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-deviation", children: [_jsx(SectionTitle, { id: "section-deviation", children: evidenceCount > 0
|
|
634
765
|
? t("pages.focus.sectionEvidenceInProgress")
|
|
635
|
-
: t("pages.focus.sectionDeviation") }), deviationCount > 0 ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "text-ink-2 text-sm mb-3", children: [_jsx("span", { className: "font-mono font-semibold text-ink", children: deviationCount }), " ", t("pages.focus.deviationCount", { count: deviationCount })] }), _jsx("div", { className: "text-ink-4 text-[13px] bg-surface/60 border-l-2 border-amber px-3 py-2 mb-4", children: t("pages.focus.deviationDisclaimer") }), _jsxs("details", { className: "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.focus.viewFullChain") }), _jsx("div", { className: "mt-2 pl-4 border-l-2 border-line text-ink-3 text-[13px]", children: t("pages.focus.deviationDetailPending") })] })] })) : evidenceCount > 0 ? (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.focus.evidenceInProgress", { count: evidenceCount }) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.focus.emptyDeviation") }))] })), !experienceMode && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-signals", children: [_jsx(SectionTitle, { id: "section-signals", children: t("pages.focus.sectionSignals") }), stagnationSignals.length > 0 ? (_jsx("div", { className: "space-y-[10px]", children: stagnationSignals.map((signal) => (_jsx(StagnationSignalCard, { signal: signal }, signal.principleId))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.focus.emptySignals") }))] })), _jsx("footer", { className: "mt-12 pt-6 border-t border-line text-ink-3 text-[13px]", children: t("pages.focus.footer") })] }) }));
|
|
766
|
+
: t("pages.focus.sectionDeviation") }), deviationCount > 0 ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "text-ink-2 text-sm mb-3", children: [_jsx("span", { className: "font-mono font-semibold text-ink", children: deviationCount }), " ", t("pages.focus.deviationCount", { count: deviationCount })] }), _jsx("div", { className: "text-ink-4 text-[13px] bg-surface/60 border-l-2 border-amber px-3 py-2 mb-4", children: t("pages.focus.deviationDisclaimer") }), _jsxs("details", { className: "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.focus.viewFullChain") }), _jsx("div", { className: "mt-2 pl-4 border-l-2 border-line text-ink-3 text-[13px]", children: t("pages.focus.deviationDetailPending") })] })] })) : evidenceCount > 0 ? (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.focus.evidenceInProgress", { count: evidenceCount }) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.focus.emptyDeviation") }))] })), !experienceMode && (_jsxs("section", { className: "mt-8", "aria-labelledby": "section-signals", children: [_jsx(SectionTitle, { id: "section-signals", children: t("pages.focus.sectionSignals") }), stagnationSignals.length > 0 ? (_jsx("div", { className: "space-y-[10px]", children: stagnationSignals.map((signal) => (_jsx(StagnationSignalCard, { signal: signal }, signal.principleId))) })) : (_jsx("div", { className: "text-ink-3 text-[13px] leading-relaxed py-3", children: t("pages.focus.emptySignals") }))] })), _jsx(DailyThoughtCard, {}), _jsx("footer", { className: "mt-12 pt-6 border-t border-line text-ink-3 text-[13px]", children: t("pages.focus.footer") })] }) }));
|
|
636
767
|
}
|
|
637
768
|
/** Owner Decision Experience v1 inbox item — display-only (SPEC §11.1). */
|
|
638
|
-
function InboxItem({ item, t }) {
|
|
769
|
+
function InboxItem({ item, t, note }) {
|
|
639
770
|
return (_jsxs("div", { className: "mb-2 rounded-[var(--radius-md)] border border-line p-3", "data-testid": "owner-inbox-item", children: [_jsxs("div", { className: "flex flex-wrap items-baseline gap-2", children: [_jsx(Link, { to: `/principles/${encodeURIComponent(item.principleId)}`, className: "text-ink text-[13.5px] font-medium hover:underline", children: item.learnedPrinciple.status === 'known'
|
|
640
771
|
? item.learnedPrinciple.text
|
|
641
|
-
: item.learnedPrinciple.reasonText }), _jsx("span", { className: "font-mono text-[11px] text-ink-4 border border-line rounded-[2px] px-1.5 py-0.5", children: t(`pages.focus.ownerInbox.group.${item.inbox.group}`) })] }), _jsx("p", { className: "mt-1 text-ink-3 text-[12.5px] leading-relaxed", children: item.nextAction.ownerText })] }));
|
|
772
|
+
: item.learnedPrinciple.reasonText }), _jsx("span", { className: "font-mono text-[11px] text-ink-4 border border-line rounded-[2px] px-1.5 py-0.5", children: t(`pages.focus.ownerInbox.group.${item.inbox.group}`) })] }), _jsx("p", { className: "mt-1 text-ink-3 text-[12.5px] leading-relaxed", children: note ?? item.nextAction.ownerText })] }));
|
|
642
773
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export declare function getReceiptPresentation(effectCount: number): {
|
|
2
|
-
headlineKey: 'principles.detail.receipts.headline' | 'principles.detail.receipts.headlinePresence';
|
|
1
|
+
export declare function getReceiptPresentation(effectCount: number, presenceCount: number): {
|
|
2
|
+
headlineKey: 'principles.detail.receipts.headline' | 'principles.detail.receipts.headlinePresence' | 'principles.detail.receipts.headlineNone';
|
|
3
|
+
/** effect>0 且 presence=0 — effects exist without any presence records; note it, never fabricate presence. */
|
|
4
|
+
presenceMissing: boolean;
|
|
3
5
|
showZeroEffectExplanation: boolean;
|
|
4
6
|
};
|
|
5
7
|
export type GovernanceControlBlock = {
|
|
@@ -105,10 +105,33 @@ const STAGE_LABEL_KEYS = {
|
|
|
105
105
|
behavior: "principles.detail.stageBehavior",
|
|
106
106
|
};
|
|
107
107
|
// ── Component ───────────────────────────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
// SPEC-PD-UIUX-001 R1: the headline must agree with BOTH verified counts.
|
|
109
|
+
// effect>0 → effect headline (records exist; no permanence claim)
|
|
110
|
+
// effect=0,presence>0 → "injections recorded, no effect records yet"
|
|
111
|
+
// effect=0,presence=0 → "nothing observed within the current retention window"
|
|
112
|
+
// Presence is never inferred from approval/activation, and effect is never
|
|
113
|
+
// inferred from presence — the degraded branch (status≠ok) handles
|
|
114
|
+
// "cannot be confirmed" separately.
|
|
115
|
+
export function getReceiptPresentation(effectCount, presenceCount) {
|
|
116
|
+
if (effectCount > 0) {
|
|
117
|
+
return {
|
|
118
|
+
headlineKey: 'principles.detail.receipts.headline',
|
|
119
|
+
presenceMissing: presenceCount === 0,
|
|
120
|
+
showZeroEffectExplanation: false,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (presenceCount > 0) {
|
|
124
|
+
return {
|
|
125
|
+
headlineKey: 'principles.detail.receipts.headlinePresence',
|
|
126
|
+
presenceMissing: false,
|
|
127
|
+
showZeroEffectExplanation: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
headlineKey: 'principles.detail.receipts.headlineNone',
|
|
132
|
+
presenceMissing: false,
|
|
133
|
+
showZeroEffectExplanation: true,
|
|
134
|
+
};
|
|
112
135
|
}
|
|
113
136
|
export const GOVERNANCE_BLOCK_I18N_KEYS = {
|
|
114
137
|
recoveryReason: 'principles.detail.governance.reason.recovery_required',
|
|
@@ -434,7 +457,7 @@ export function PrincipleDetailPage() {
|
|
|
434
457
|
}
|
|
435
458
|
const hasRules = principle.rules.length > 0;
|
|
436
459
|
const receiptPresentation = receipts?.status === 'ok'
|
|
437
|
-
? getReceiptPresentation(receipts.effectCount)
|
|
460
|
+
? getReceiptPresentation(receipts.effectCount, receipts.presenceCount)
|
|
438
461
|
: null;
|
|
439
462
|
return (_jsx(PageShell, { children: _jsxs("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: [_jsxs(Button, { variant: "ghost", onClick: () => navigate("/principles"), className: "mb-4 -ml-2", children: ["\u2190 ", t("principles.detail.backToList")] }), _jsxs("section", { className: "mb-8", children: [_jsx(SectionTitle, { children: t("principles.detail.conclusion") }), _jsx("h1", { className: "text-[22px] font-semibold text-ink leading-snug mb-3", children: ownerDecision?.learnedPrinciple.status === 'known'
|
|
440
463
|
? ownerDecision.learnedPrinciple.value.text
|
|
@@ -476,25 +499,40 @@ export function PrincipleDetailPage() {
|
|
|
476
499
|
effectCount: receipts.effectCount,
|
|
477
500
|
lastEffectAt: receipts.lastEffectAt ? formatDate(receipts.lastEffectAt, i18n.language) : '',
|
|
478
501
|
})
|
|
479
|
-
: t('principles.detail.receipts.headlinePresence') }), _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 }) })] }), receiptPresentation?.showZeroEffectExplanation && (_jsx("p", { "data-testid": "receipt-history-zero-effect", className: "mt-2 text-[12px] leading-relaxed text-ink-3", children: t(
|
|
480
|
-
|
|
502
|
+
: t(receiptPresentation?.headlineKey ?? 'principles.detail.receipts.headlinePresence') }), _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 }) })] }), receiptPresentation?.showZeroEffectExplanation && (_jsx("p", { "data-testid": "receipt-history-zero-effect", className: "mt-2 text-[12px] leading-relaxed text-ink-3", children: t(receipts.presenceCount > 0
|
|
503
|
+
? 'principles.detail.receipts.zeroEffectPresence'
|
|
504
|
+
: 'principles.detail.receipts.zeroEffectNone') })), receiptPresentation?.presenceMissing && (_jsx("p", { "data-testid": "receipt-history-presence-missing", className: "mt-2 text-[12px] leading-relaxed text-ink-3", children: t('principles.detail.receipts.presenceMissingNote') })), 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') }), _jsx("div", { className: "mt-3 border-t border-line pt-3", children: _jsx(ReceiptCoverageDisclosure, { coverage: receipts.coverage }) })] })) : (_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("h3", { className: "text-[13px] font-semibold text-ink", children: i18n.t(getReceiptSourceStatusLabelKey(receipts.coverage.sourceStatus)) }), _jsx("p", { className: "mt-1 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 })] }))] })), evidenceAudit !== null && (_jsxs("section", { className: "mb-8", "aria-labelledby": "evidence-audit-title", children: [_jsx(SectionTitle, { children: t('principles.detail.evidenceAudit.title') }), evidenceAudit.status === 'ok' ? (_jsxs("div", { "data-testid": "evidence-audit", className: "rounded-[var(--radius-md)] border border-line p-4", children: [_jsx("h2", { id: "evidence-audit-title", className: "text-[15px] font-semibold text-ink", children: t('principles.detail.evidenceAudit.headline', {
|
|
481
505
|
d: evidenceAudit.deliveries.length,
|
|
482
506
|
a: evidenceAudit.applications.length,
|
|
483
507
|
e: evidenceAudit.episodes.length,
|
|
484
508
|
f: evidenceAudit.effects.length,
|
|
485
509
|
o: evidenceAudit.outcomes.length,
|
|
486
510
|
}) }), evidenceAudit.unresolvedReferences.length > 0 && (_jsx("p", { "data-testid": "evidence-audit-unresolved", className: "mt-2 text-[12px] text-amber", children: t('principles.detail.evidenceAudit.unresolved', {
|
|
487
|
-
defaultValue: '{{count}} 条记录的关联对象尚未到达(pending association,非"未发生")',
|
|
488
511
|
count: evidenceAudit.unresolvedReferences.length,
|
|
489
512
|
}) })), _jsx("dl", { className: "mt-3 space-y-2 text-[13px]", children: [
|
|
490
|
-
['delivery', '
|
|
491
|
-
['application', '
|
|
492
|
-
['episode', '
|
|
493
|
-
['effect', '
|
|
494
|
-
['outcome', '
|
|
495
|
-
].map(([key,
|
|
496
|
-
|
|
497
|
-
|
|
513
|
+
['delivery', 'principles.detail.evidenceAudit.sectionDelivery', evidenceAudit.deliveries, 'delivery'],
|
|
514
|
+
['application', 'principles.detail.evidenceAudit.sectionApplication', evidenceAudit.applications, 'application'],
|
|
515
|
+
['episode', 'principles.detail.evidenceAudit.sectionEpisode', evidenceAudit.episodes, 'behavior_episode'],
|
|
516
|
+
['effect', 'principles.detail.evidenceAudit.sectionEffect', evidenceAudit.effects, 'effect'],
|
|
517
|
+
['outcome', 'principles.detail.evidenceAudit.sectionOutcome', evidenceAudit.outcomes, 'outcome'],
|
|
518
|
+
].map(([key, labelKey, list, recordKind]) => (_jsxs("div", { className: "border-t border-line pt-2", children: [_jsx("dt", { className: "font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t(labelKey) }), _jsx("dd", { className: "mt-1", children: list.length === 0 ? (_jsx("span", { className: "text-[13px] text-ink-3", children: t('principles.detail.evidenceAudit.emptySection') })) : (_jsx("ul", { className: "space-y-1", children: list.map((record) => (_jsxs("li", { className: "flex flex-wrap items-baseline gap-2 text-[13px] text-ink-2", children: [_jsx("span", { className: "font-mono text-[11px] text-ink-4", children: record.recordedAt.slice(0, 16).replace('T', ' ') }), _jsx("span", { className: "font-mono text-[11px]", children: record.summary }), _jsx("span", { className: "min-w-0 flex-1 break-words", title: `${record.observationKey}\n${record.detail}`, children: record.detail }), record.associationStatus === 'pending_association' && (_jsx("span", { className: "font-mono text-[10px] rounded bg-amber/15 px-1.5 py-0.5 text-amber", children: t('principles.detail.evidenceAudit.pending') }))] }, record.evidenceId))) })) }), evidenceAudit.pages[recordKind].hasMore && (_jsx(Button, { variant: "ghost", size: "sm", disabled: loadingEvidenceKind !== null, onClick: () => void loadOlderEvidence(recordKind), children: loadingEvidenceKind === recordKind ? t('principles.detail.evidenceAudit.loading') : t('principles.detail.evidenceAudit.loadOlder') }))] }, key))) }), evidenceAudit.capabilityDeclarations.length > 0 && (_jsxs("details", { className: "mt-3 border-t border-line pt-3", children: [_jsx("summary", { className: "cursor-pointer font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.evidenceAudit.capabilities') }), _jsx("ul", { className: "mt-2 space-y-1", children: evidenceAudit.capabilityDeclarations.map((cap) => (_jsxs("li", { className: "text-[12px] text-ink-3", children: [_jsxs("span", { className: "font-mono", children: [cap.hostKind, " \u00B7 ", cap.capability, " \u00B7 ", cap.status] }), cap.note && _jsxs("span", { children: [" \u2014 ", cap.note] })] }, `${cap.hostKind}-${cap.capability}`))) })] })), _jsxs("details", { className: "mt-3 border-t border-line pt-3", "data-testid": "evidence-audit-technical", children: [_jsx("summary", { className: "cursor-pointer font-mono text-[11px] uppercase tracking-[0.08em] text-ink-4", children: t('principles.detail.evidenceAudit.technicalSummary') }), _jsx("p", { className: "mt-2 text-[11px] leading-relaxed text-ink-4", children: t('principles.detail.evidenceAudit.technicalNote') }), _jsx("ul", { className: "mt-2 space-y-1", children: [
|
|
519
|
+
['principles.detail.evidenceAudit.sectionDelivery', evidenceAudit.deliveries],
|
|
520
|
+
['principles.detail.evidenceAudit.sectionApplication', evidenceAudit.applications],
|
|
521
|
+
['principles.detail.evidenceAudit.sectionEpisode', evidenceAudit.episodes],
|
|
522
|
+
['principles.detail.evidenceAudit.sectionEffect', evidenceAudit.effects],
|
|
523
|
+
['principles.detail.evidenceAudit.sectionOutcome', evidenceAudit.outcomes],
|
|
524
|
+
].flatMap(([labelKey, list]) => list.map((record) => (_jsxs("li", { className: "text-[11px] leading-relaxed text-ink-4", children: [_jsx("span", { className: "font-mono", children: record.evidenceId }), record.sourceStatus === 'unknown' && _jsxs(_Fragment, { children: [" \u00B7 ", t('principles.detail.evidenceAudit.sourceUnknown')] }), record.contentRedactedAt && _jsxs(_Fragment, { children: [" \u00B7 ", t('principles.detail.evidenceAudit.contentRedacted', { date: record.contentRedactedAt.slice(0, 10) })] }), record.contentReference && (_jsxs(_Fragment, { children: [" \u00B7 ", t('principles.detail.evidenceAudit.contentReference', {
|
|
525
|
+
resolution: record.contentReference.resolution === 'resolved' ? t('principles.detail.evidenceAudit.referenceResolved') : t('principles.detail.evidenceAudit.referenceUnresolved'),
|
|
526
|
+
principleId: record.contentReference.principleId,
|
|
527
|
+
artifactId: record.contentReference.artifactId ? ` · ${record.contentReference.artifactId}` : '',
|
|
528
|
+
// Review round (P2): keep the payload digest traceable —
|
|
529
|
+
// the technical note promises it.
|
|
530
|
+
payloadDigest: ` · ${t('principles.detail.evidenceAudit.payloadDigestLabel')}: ${record.contentReference.payloadDigest ?? t('principles.detail.evidenceAudit.noPayloadDigest')}`,
|
|
531
|
+
})] })), record.activationReference && (_jsxs(_Fragment, { children: [" \u00B7 ", t('principles.detail.evidenceAudit.activationReference', {
|
|
532
|
+
activationId: record.activationReference.activationId,
|
|
533
|
+
activatedAt: record.activationReference.activatedAt ? ` · ${record.activationReference.activatedAt}` : '',
|
|
534
|
+
snapshotDigest: ` · ${t('principles.detail.evidenceAudit.snapshotDigestLabel')}: ${record.activationReference.sourceSnapshotDigest}`,
|
|
535
|
+
})] })), record.nativeLineage.length > 0 && _jsxs(_Fragment, { children: [" \u00B7 ", t('principles.detail.evidenceAudit.nativeLineage', { lineage: record.nativeLineage.join(' · ') })] })] }, record.evidenceId)))) })] }), _jsx("p", { className: "mt-3 text-[11px] leading-relaxed text-ink-4", children: t('principles.detail.evidenceAudit.note') })] })) : (_jsxs("div", { "data-testid": "evidence-audit-degraded", className: "rounded-[var(--radius-md)] border border-amber/30 bg-amber/5 p-4", role: "status", children: [_jsx("h3", { className: "text-[13px] font-semibold text-ink", children: t('principles.detail.evidenceAudit.unavailable') }), _jsx("p", { className: "mt-1 text-[13px] text-ink-2", children: evidenceAudit.reason ?? '—' }), evidenceAudit.nextAction && _jsx("p", { className: "mt-1 text-[12px] text-ink-3", children: evidenceAudit.nextAction })] }))] })), _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 font-mono break-all", 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", {
|
|
498
536
|
defaultValue: "已关联 {{count}} 条证据记录",
|
|
499
537
|
count: principle.derivedFromPainIds.length,
|
|
500
538
|
}) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: async () => {
|
|
@@ -205,7 +205,7 @@ export function UpdatePage() {
|
|
|
205
205
|
}) }, item.transactionId))), recoveryData.nextAction && (_jsx("p", { className: "mt-2 text-[12px] text-ink-3", children: recoveryData.nextAction }))] })), _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 ?? "—" })] }), statusData?.identityDivergence && (_jsx("div", { className: "border border-amber/35 rounded-[4px] px-3 py-2 text-[12px] leading-relaxed text-ink-2", children: t("pages.update.identityDivergence", {
|
|
206
206
|
active: statusData.identityDivergence.activeVersion,
|
|
207
207
|
plugin: statusData.identityDivergence.pluginVersion,
|
|
208
|
-
}) })), _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: updateState === 'check_failed' ? "—" : (statusData?.latestVersion || "—") }), updateState === 'check_failed' && (_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.checkFailed") })), updateState === 'update_blocked' && (_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.blockedBadge") })), updateState === 'up_to_date' && (_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") })), updateState === 'update_available' && (_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") }))] })] })] }), (updateState === 'update_blocked' || updateState === 'check_failed') && (_jsxs("div", { className: "mt-4 pt-3 border-t border-line text-[12px] leading-relaxed", children: [blockedReason && (_jsxs("p", { className: "text-ink-2", children: [t("pages.update.reasonLabel"), blockedReason] })), statusData?.message && updateState === 'update_blocked' && (_jsx("p", { className: "mt-1 text-ink-3", children: statusData.message })), statusData?.nextAction && (_jsx("p", { className: "mt-1 text-ink-3 font-mono", children: statusData.nextAction }))] })), 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] })), inProgress && (_jsxs("div", { className: "mt-4 p-4 rounded-[6px] border border-gov/30 bg-surface text-[13px] leading-relaxed", role: "status", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin text-gov" }), _jsx("span", { className: "font-medium text-ink", children: t("pages.update.inProgressPanel") })] }), _jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-3", children: t("pages.update.inProgressPhase", { phase: inProgress.phase ?? "…" }) }), _jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-4", children: inProgress.transactionId })] })), _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 || inProgress !== null, 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 && updateState === 'update_available' && (_jsx("button", { type: "button", onClick: () => setShowFullUpdateDialog(true), disabled: checking || fullUpdating || inProgress !== null, 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' : updateResult.refusal ? 'bg-amber/5 border-amber/25' : 'bg-
|
|
208
|
+
}) })), _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: updateState === 'check_failed' ? "—" : (statusData?.latestVersion || "—") }), updateState === 'check_failed' && (_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.checkFailed") })), updateState === 'update_blocked' && (_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.blockedBadge") })), updateState === 'up_to_date' && (_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") })), updateState === 'update_available' && (_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") }))] })] })] }), (updateState === 'update_blocked' || updateState === 'check_failed') && (_jsxs("div", { className: "mt-4 pt-3 border-t border-line text-[12px] leading-relaxed", children: [blockedReason && (_jsxs("p", { className: "text-ink-2", children: [t("pages.update.reasonLabel"), blockedReason] })), statusData?.message && updateState === 'update_blocked' && (_jsx("p", { className: "mt-1 text-ink-3", children: statusData.message })), statusData?.nextAction && (_jsx("p", { className: "mt-1 text-ink-3 font-mono", children: statusData.nextAction }))] })), 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] })), inProgress && (_jsxs("div", { className: "mt-4 p-4 rounded-[6px] border border-gov/30 bg-surface text-[13px] leading-relaxed", role: "status", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin text-gov" }), _jsx("span", { className: "font-medium text-ink", children: t("pages.update.inProgressPanel") })] }), _jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-3", children: t("pages.update.inProgressPhase", { phase: inProgress.phase ?? "…" }) }), _jsx("p", { className: "mt-1 font-mono text-[12px] text-ink-4", children: inProgress.transactionId })] })), _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 || inProgress !== null, 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 && updateState === 'update_available' && (_jsx("button", { type: "button", onClick: () => setShowFullUpdateDialog(true), disabled: checking || fullUpdating || inProgress !== null, 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' : updateResult.refusal ? 'bg-amber/5 border-amber/25' : 'bg-danger/5 border-danger/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' } })) : updateResult.refusal ? (_jsx(AlertTriangle, { className: "h-6 w-6 text-amber shrink-0 mt-0.5" })) : (_jsx(XCircle, { className: "h-6 w-6 text-danger shrink-0 mt-0.5" })), _jsxs("div", { className: "flex-1 min-w-0", children: [updateResult.refusal && (_jsx("p", { className: "text-[12px] font-mono uppercase text-amber mb-1", children: t("pages.update.blockedBadge") })), 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' : updateResult.refusal ? 'text-ink-2' : 'text-danger'}`, children: updateResult.message }), 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.gatewayNotice && (_jsx("p", { className: "mt-2 p-2 rounded-[3px] bg-amber/5 border border-amber/20 text-[12px] text-amber leading-relaxed font-mono", children: updateResult.gatewayNotice })), !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: () => setShowFullUpdateDialog(true), disabled: fullUpdating, className: "text-[12px] text-danger underline hover:text-danger/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) => (_jsxs("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 === "failed" ? "—" : entry.toVersion] }), entry.kind !== "failure" && (_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.historyKind.${entry.kind}`) })), !entry.success && entry.kind !== 'refusal' && (_jsx("span", { className: "inline-flex items-center border border-danger/35 text-danger rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: t("pages.update.failed") }))] }), _jsx("div", { className: "flex items-center gap-3", children: _jsx("span", { className: "text-ink-3 text-[13px]", children: formatDate(entry.timestamp, i18n.language, {
|
|
209
209
|
year: "numeric",
|
|
210
210
|
month: "short",
|
|
211
211
|
day: "numeric",
|