create-principles-disciple 1.143.5 → 1.144.1
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 +1 -1
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +4 -19
- package/console/dist/server/models/OwnerDecisionConsoleModel.js +17 -0
- package/console/dist/server/models/artifact-principle-resolver.d.ts +29 -0
- package/console/dist/server/models/artifact-principle-resolver.js +53 -0
- package/console/dist/ui/pages/activation/ActivationPage.js +27 -1
- package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +29 -6
- package/console/dist/ui/pages/focus/OwnerDecisionCard.js +4 -2
- package/console/dist/web/assets/app.css +18 -0
- package/console/dist/web/assets/app.js +5294 -2773
- package/console/package.json +8 -8
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +15 -4
- package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
- package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js +0 -10
- package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +1 -1
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +1 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/package.json +2 -2
- package/dist/index.js +0 -0
- package/package.json +2 -2
- package/pd-cli/dist/commands/candidate.d.ts +3 -3
- package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
- package/pd-cli/dist/commands/candidate.js +9 -21
- package/pd-cli/dist/commands/candidate.js.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
- package/pd-cli/dist/services/rulehost-pipeline-runner.js +4 -17
- package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
- package/plugin/dist/bundle.js +126 -126
- package/plugin/dist/governance-audit.js +120 -120
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/dist/rulehost-evidence.js +125 -125
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { SqliteConnection, SqliteApprovalQueueStore, SqlitePIArtifactStore, ApprovalQueue,
|
|
1
|
+
import { SqliteConnection, SqliteApprovalQueueStore, SqlitePIArtifactStore, ApprovalQueue, } from '@principles/core/runtime-v2';
|
|
2
2
|
import { loadLedger } from '@principles/core/principle-tree-ledger';
|
|
3
|
-
import {
|
|
3
|
+
import { createArtifactPrincipleResolutionDeps, resolveArtifactPrincipleId, } from './artifact-principle-resolver.js';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
/** Type guard: non-null object (not array). Replaces `as Record<string, unknown>` assertions. */
|
|
@@ -124,27 +124,12 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
124
124
|
const artifactPrincipleMap = new Map();
|
|
125
125
|
const artifactDescriptionMap = new Map();
|
|
126
126
|
const stateDir = path.join(this.workspaceDir, '.state');
|
|
127
|
-
const
|
|
127
|
+
const resolutionDeps = createArtifactPrincipleResolutionDeps(conn, artifactStore, this.workspaceDir);
|
|
128
128
|
for (const approval of allApprovals) {
|
|
129
129
|
if (!artifactPrincipleMap.has(approval.artifactId)) {
|
|
130
130
|
try {
|
|
131
131
|
const artifact = await artifactStore.getArtifactById(approval.artifactId);
|
|
132
|
-
|
|
133
|
-
if (artifact && !mappedId) {
|
|
134
|
-
const resolution = await resolveLedgerPrincipleId(artifact, {
|
|
135
|
-
ledger: resolutionLedger,
|
|
136
|
-
getArtifactById: (id) => artifactStore.getArtifactById(id),
|
|
137
|
-
getTaskDiagnosticJson: (taskId) => {
|
|
138
|
-
const row = conn
|
|
139
|
-
.getDb()
|
|
140
|
-
.prepare('SELECT diagnostic_json FROM tasks WHERE task_id = ?')
|
|
141
|
-
.get(taskId);
|
|
142
|
-
return row?.diagnostic_json ?? null;
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
if (resolution.status === 'resolved')
|
|
146
|
-
mappedId = resolution.principleId;
|
|
147
|
-
}
|
|
132
|
+
const mappedId = artifact ? await resolveArtifactPrincipleId(artifact, resolutionDeps) : null;
|
|
148
133
|
artifactPrincipleMap.set(approval.artifactId, mappedId);
|
|
149
134
|
if (artifact?.contentJson) {
|
|
150
135
|
artifactDescriptionMap.set(approval.artifactId, extractCandidateDescription(artifact.contentJson));
|
|
@@ -13,6 +13,7 @@ import * as fs from 'node:fs';
|
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import { SqliteConnection, SqlitePIArtifactStore, SqliteApprovalQueueStore, collectOwnerDecisionFacts, deriveOwnerDecisionCapability, buildOwnerDecisionReview, } from '@principles/core/runtime-v2';
|
|
15
15
|
import { ActivationsConsoleModel } from './ActivationsConsoleModel.js';
|
|
16
|
+
import { createArtifactPrincipleResolutionDeps, resolveArtifactPrincipleId, } from './artifact-principle-resolver.js';
|
|
16
17
|
const GOVERNANCE_TASK_KINDS = new Set(['evaluator', 'rollout_reviewer']);
|
|
17
18
|
/** tasks.status 合法值(PDTaskStatus)运行时守卫。 */
|
|
18
19
|
const TASK_STATUSES = new Set(['pending', 'leased', 'succeeded', 'retry_wait', 'failed', 'needs_human_review']);
|
|
@@ -219,11 +220,27 @@ export class OwnerDecisionConsoleModel {
|
|
|
219
220
|
// ── 2. 既有 approvals pending(高风险部署审批 — 独立门,动作走既有 API)──
|
|
220
221
|
try {
|
|
221
222
|
const approvals = await new SqliteApprovalQueueStore(conn).listPending();
|
|
223
|
+
// principleId 深链定位:approval.artifactId → ledger principle id,
|
|
224
|
+
// 与 approvals/grouped 视图共用同一套解析装配。解析失败时该条目不带
|
|
225
|
+
// principleId → UI 回退到审查列表链接(即无深链的既有行为),不阻断
|
|
226
|
+
// 收件箱其余条目。
|
|
227
|
+
const resolutionDeps = createArtifactPrincipleResolutionDeps(conn, artifactStore, this.workspaceDir);
|
|
222
228
|
for (const approval of approvals) {
|
|
229
|
+
let principleId;
|
|
230
|
+
try {
|
|
231
|
+
const artifact = await artifactStore.getArtifactById(approval.artifactId);
|
|
232
|
+
if (artifact) {
|
|
233
|
+
principleId = (await resolveArtifactPrincipleId(artifact, resolutionDeps)) ?? undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// 解析失败 → 不带 principleId(rc-9:回退目标=现状基线,非静默行为变更)
|
|
238
|
+
}
|
|
223
239
|
items.push({
|
|
224
240
|
reviewKey: `apr:${approval.approvalId}`,
|
|
225
241
|
kind: 'activation_approval',
|
|
226
242
|
taskId: approval.artifactId,
|
|
243
|
+
...(principleId !== undefined ? { principleId } : {}),
|
|
227
244
|
title: `部署审批 · ${approval.channel}`,
|
|
228
245
|
summary: `${approval.riskLevel} 风险通道 ${approval.channel} 的原则部署等待批准。`,
|
|
229
246
|
reasonCode: 'activation_approval_pending',
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
|
|
2
|
+
import type { PIArtifactRecord, SqliteConnection } from '@principles/core/runtime-v2';
|
|
3
|
+
export interface ArtifactPrincipleResolutionDeps {
|
|
4
|
+
readonly ledger: PrincipleTreeLedgerAdapter;
|
|
5
|
+
readonly getArtifactById: (artifactId: string) => Promise<PIArtifactRecord | null>;
|
|
6
|
+
/** Read one column from the tasks table (same workspace as the caller). */
|
|
7
|
+
readonly getTaskDiagnosticJson: (taskId: string) => string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Assemble the resolution deps from an open readonly connection — the shape
|
|
11
|
+
* both approval read models already hold. The ledger adapter is built once
|
|
12
|
+
* per model call, not per artifact.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createArtifactPrincipleResolutionDeps(conn: SqliteConnection, artifactStore: {
|
|
15
|
+
getArtifactById(artifactId: string): Promise<PIArtifactRecord | null>;
|
|
16
|
+
}, workspaceDir: string): ArtifactPrincipleResolutionDeps;
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the ledger principle id behind one approval artifact — ALWAYS via
|
|
19
|
+
* resolveLedgerPrincipleId, so a direct `sourcePrincipleId` column hit is
|
|
20
|
+
* validated against the ledger (`hasPrinciple`) before it is trusted and the
|
|
21
|
+
* candidate lineage is walked otherwise. Returns null when unresolvable —
|
|
22
|
+
* the caller degrades to unlinked / bare-link and never guesses.
|
|
23
|
+
*
|
|
24
|
+
* Contract note (PR #1789 review): this deliberately tightens the previous
|
|
25
|
+
* grouped-view behavior, which trusted an unvalidated column hit. A stale
|
|
26
|
+
* column id must not surface as a deep-link target — /principles/<stale-id>
|
|
27
|
+
* would dead-end the Owner on a not-found page.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveArtifactPrincipleId(artifact: PIArtifactRecord, deps: ArtifactPrincipleResolutionDeps): Promise<string | null>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared wiring for resolveLedgerPrincipleId over one workspace SQLite store.
|
|
3
|
+
*
|
|
4
|
+
* Two approval-facing read models need the same artifact → ledger principleId
|
|
5
|
+
* chain — ApprovalsGroupedConsoleModel (grouped approvals view) and
|
|
6
|
+
* OwnerDecisionConsoleModel (activation_approval inbox items, which carry the
|
|
7
|
+
* principleId the focus page deep-links on) — so the dependency assembly
|
|
8
|
+
* (ledger adapter + dreamer artifact re-read + tasks diagnostic read) lives
|
|
9
|
+
* here exactly once instead of being duplicated per model.
|
|
10
|
+
*/
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import { PrincipleTreeLedgerAdapter } from '@principles/core/runtime-v2';
|
|
13
|
+
import { resolveLedgerPrincipleId } from './principle-id-resolution.js';
|
|
14
|
+
function isRecord(value) {
|
|
15
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Assemble the resolution deps from an open readonly connection — the shape
|
|
19
|
+
* both approval read models already hold. The ledger adapter is built once
|
|
20
|
+
* per model call, not per artifact.
|
|
21
|
+
*/
|
|
22
|
+
export function createArtifactPrincipleResolutionDeps(conn, artifactStore, workspaceDir) {
|
|
23
|
+
return {
|
|
24
|
+
ledger: new PrincipleTreeLedgerAdapter({ stateDir: path.join(workspaceDir, '.state') }),
|
|
25
|
+
getArtifactById: (artifactId) => artifactStore.getArtifactById(artifactId),
|
|
26
|
+
getTaskDiagnosticJson: (taskId) => {
|
|
27
|
+
const row = conn
|
|
28
|
+
.getDb()
|
|
29
|
+
.prepare('SELECT diagnostic_json FROM tasks WHERE task_id = ?')
|
|
30
|
+
.get(taskId);
|
|
31
|
+
if (!isRecord(row))
|
|
32
|
+
return null;
|
|
33
|
+
const value = row.diagnostic_json;
|
|
34
|
+
return typeof value === 'string' ? value : null;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the ledger principle id behind one approval artifact — ALWAYS via
|
|
40
|
+
* resolveLedgerPrincipleId, so a direct `sourcePrincipleId` column hit is
|
|
41
|
+
* validated against the ledger (`hasPrinciple`) before it is trusted and the
|
|
42
|
+
* candidate lineage is walked otherwise. Returns null when unresolvable —
|
|
43
|
+
* the caller degrades to unlinked / bare-link and never guesses.
|
|
44
|
+
*
|
|
45
|
+
* Contract note (PR #1789 review): this deliberately tightens the previous
|
|
46
|
+
* grouped-view behavior, which trusted an unvalidated column hit. A stale
|
|
47
|
+
* column id must not surface as a deep-link target — /principles/<stale-id>
|
|
48
|
+
* would dead-end the Owner on a not-found page.
|
|
49
|
+
*/
|
|
50
|
+
export async function resolveArtifactPrincipleId(artifact, deps) {
|
|
51
|
+
const resolution = await resolveLedgerPrincipleId(artifact, deps);
|
|
52
|
+
return resolution.status === 'resolved' ? resolution.principleId : null;
|
|
53
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback } from "react";
|
|
3
|
-
import { Link, useNavigate } from "react-router-dom";
|
|
3
|
+
import { Link, useNavigate, useSearchParams } from "react-router-dom";
|
|
4
4
|
import { useTranslation } from "react-i18next";
|
|
5
5
|
import { toast } from "sonner";
|
|
6
6
|
import { PageShell } from "../../components/layout/page-shell.js";
|
|
@@ -119,6 +119,11 @@ function ActivationFactCard({ record, lifecycleData, receiptCount, onDisable, di
|
|
|
119
119
|
}
|
|
120
120
|
export function ActivationPage() {
|
|
121
121
|
const { t } = useTranslation();
|
|
122
|
+
const [searchParams] = useSearchParams();
|
|
123
|
+
// ?activationId= deep link (治理焦点 RuleCode 决策卡直达): once the list has
|
|
124
|
+
// loaded, scroll the linked card into view and pulse a theme-token ring
|
|
125
|
+
// around it. Unknown id → plain landing (the list itself stays the truth).
|
|
126
|
+
const deepLinkActivationId = searchParams.get("activationId");
|
|
122
127
|
const [activationsData, setActivationsData] = useState(null);
|
|
123
128
|
const [lifecycleCache, setLifecycleCache] = useState({});
|
|
124
129
|
const [receiptCounts, setReceiptCounts] = useState(null);
|
|
@@ -219,6 +224,27 @@ export function ActivationPage() {
|
|
|
219
224
|
useEffect(() => {
|
|
220
225
|
loadData();
|
|
221
226
|
}, [loadData]);
|
|
227
|
+
// Deep link (?activationId=): scroll the linked activation card into view
|
|
228
|
+
// and pulse a theme-token ring for a few seconds. Runs once the list has
|
|
229
|
+
// actually rendered; a card that is not on the page degrades to a plain
|
|
230
|
+
// landing (rc-9 — no fabricated focus). The URL param is untrusted (rc-1):
|
|
231
|
+
// CSS.escape (unquoted attribute selector) keeps a crafted id from breaking
|
|
232
|
+
// the selector.
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (deepLinkActivationId === null || loadingState !== "loaded")
|
|
235
|
+
return;
|
|
236
|
+
const card = document.querySelector(`[data-testid=${CSS.escape(`activation-card-${deepLinkActivationId}`)}]`);
|
|
237
|
+
if (card === null)
|
|
238
|
+
return;
|
|
239
|
+
card.scrollIntoView({ block: "center" });
|
|
240
|
+
const HIGHLIGHT_CLASSES = ["ring-2", "ring-gov", "ring-offset-2", "ring-offset-paper"];
|
|
241
|
+
card.classList.add(...HIGHLIGHT_CLASSES);
|
|
242
|
+
const timer = window.setTimeout(() => card.classList.remove(...HIGHLIGHT_CLASSES), 3000);
|
|
243
|
+
return () => {
|
|
244
|
+
window.clearTimeout(timer);
|
|
245
|
+
card.classList.remove(...HIGHLIGHT_CLASSES);
|
|
246
|
+
};
|
|
247
|
+
}, [deepLinkActivationId, loadingState]);
|
|
222
248
|
const handleDisable = useCallback(async (record) => {
|
|
223
249
|
setDisablingIds((prev) => new Set(prev).add(record.activationId));
|
|
224
250
|
const result = await disableActivation(record.activationId);
|
|
@@ -31,7 +31,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
31
31
|
* - rc-9: every error/disabled path surfaces a reason + next action
|
|
32
32
|
*/
|
|
33
33
|
import { useState, useEffect, useCallback, useRef, Fragment } from "react";
|
|
34
|
-
import { useNavigate } from "react-router-dom";
|
|
34
|
+
import { useNavigate, useSearchParams } from "react-router-dom";
|
|
35
35
|
import { useTranslation } from "react-i18next";
|
|
36
36
|
import { toast } from "sonner";
|
|
37
37
|
import { PageShell } from "../../components/layout/page-shell.js";
|
|
@@ -174,6 +174,10 @@ function groupTasksByKind(tasks) {
|
|
|
174
174
|
export function FailedTasksPage() {
|
|
175
175
|
const { t } = useTranslation();
|
|
176
176
|
const navigate = useNavigate();
|
|
177
|
+
const [searchParams] = useSearchParams();
|
|
178
|
+
// ?taskId= deep link (治理焦点「查看恢复详情」直达): initial value only —
|
|
179
|
+
// the row auto-expands once; the Owner can still collapse/switch freely.
|
|
180
|
+
const deepLinkTaskId = searchParams.get("taskId") ?? undefined;
|
|
177
181
|
const [state, setState] = useState({ status: "loading" });
|
|
178
182
|
// Governance Recovery Actions v1 — flag-gated recovery UI (fail-closed:
|
|
179
183
|
// absent/false → no recovery entry, Console stays read-only).
|
|
@@ -267,7 +271,7 @@ export function FailedTasksPage() {
|
|
|
267
271
|
setRecoverLoading(false);
|
|
268
272
|
}
|
|
269
273
|
};
|
|
270
|
-
return (_jsxs(PageShell, { children: [_jsxs("div", { className: "mb-8", children: [_jsx("div", { className: "font-mono text-[11px] uppercase tracking-[0.14em] text-ink-3", children: t("pages.failedTasks.title") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mt-3 mb-2", duration: 4.5, brightness: 0.5, disabled: state.status !== "loaded" || state.data.tasks.length === 0, children: t("pages.failedTasks.title") }), _jsx("p", { className: "text-ink-3 text-sm leading-relaxed max-w-[712px]", children: t("pages.failedTasks.subtitle") })] }), state.status === "loading" && (_jsx(PageLoading, { cardCount: 3, label: t("common.loading") })), state.status === "disabled" && (_jsxs("div", { className: "py-8", children: [_jsx("div", { className: "p-5 bg-panel border border-amber/20 rounded-[var(--radius-md)] text-amber text-[13px] leading-relaxed", children: t("pages.failedTasks.disabled") }), _jsx("div", { className: "mt-4", children: _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, children: t("common.refresh") }) })] })), state.status === "error" && (_jsxs("div", { className: "py-8", children: [_jsxs("div", { className: "text-danger text-sm mb-2", children: [t("pages.failedTasks.error"), ": ", state.message] }), state.nextAction && (_jsx("div", { className: "text-ink-3 text-[13px] mb-3", children: state.nextAction })), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, children: t("common.refresh") })] })), state.status === "loaded" && (_jsx("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: _jsx(LoadedContent, { data: state.data, onCreateDraft: handleCreateDraft, onRecover: recoveryEnabled ? (task) => setRecoverTarget(task) : undefined, t: t }) })), _jsx(AlertDialog, { open: recoverTarget !== null, onOpenChange: (open) => {
|
|
274
|
+
return (_jsxs(PageShell, { children: [_jsxs("div", { className: "mb-8", children: [_jsx("div", { className: "font-mono text-[11px] uppercase tracking-[0.14em] text-ink-3", children: t("pages.failedTasks.title") }), _jsx(ShinyText, { as: "h1", className: "text-[29px] font-semibold tracking-tight text-ink mt-3 mb-2", duration: 4.5, brightness: 0.5, disabled: state.status !== "loaded" || state.data.tasks.length === 0, children: t("pages.failedTasks.title") }), _jsx("p", { className: "text-ink-3 text-sm leading-relaxed max-w-[712px]", children: t("pages.failedTasks.subtitle") })] }), state.status === "loading" && (_jsx(PageLoading, { cardCount: 3, label: t("common.loading") })), state.status === "disabled" && (_jsxs("div", { className: "py-8", children: [_jsx("div", { className: "p-5 bg-panel border border-amber/20 rounded-[var(--radius-md)] text-amber text-[13px] leading-relaxed", children: t("pages.failedTasks.disabled") }), _jsx("div", { className: "mt-4", children: _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, children: t("common.refresh") }) })] })), state.status === "error" && (_jsxs("div", { className: "py-8", children: [_jsxs("div", { className: "text-danger text-sm mb-2", children: [t("pages.failedTasks.error"), ": ", state.message] }), state.nextAction && (_jsx("div", { className: "text-ink-3 text-[13px] mb-3", children: state.nextAction })), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, children: t("common.refresh") })] })), state.status === "loaded" && (_jsx("div", { className: "animate-[pdFadeIn_400ms_ease-out]", children: _jsx(LoadedContent, { data: state.data, onCreateDraft: handleCreateDraft, onRecover: recoveryEnabled ? (task) => setRecoverTarget(task) : undefined, initialExpandedTaskId: deepLinkTaskId, t: t }) })), _jsx(AlertDialog, { open: recoverTarget !== null, onOpenChange: (open) => {
|
|
271
275
|
if (!open && !recoverLoading) {
|
|
272
276
|
setRecoverTarget(null);
|
|
273
277
|
}
|
|
@@ -283,7 +287,7 @@ export function FailedTasksPage() {
|
|
|
283
287
|
void handleRecoverConfirmed();
|
|
284
288
|
}, children: recoverConfirmLabel })] })] }) })] }));
|
|
285
289
|
}
|
|
286
|
-
function LoadedContent({ data, onCreateDraft, onRecover, t }) {
|
|
290
|
+
function LoadedContent({ data, onCreateDraft, onRecover, initialExpandedTaskId, t }) {
|
|
287
291
|
const hasTasks = data.tasks.length > 0;
|
|
288
292
|
// Empty state — surface nextAction from the API (rc-9: no silent fallback)
|
|
289
293
|
if (!hasTasks) {
|
|
@@ -295,18 +299,24 @@ function LoadedContent({ data, onCreateDraft, onRecover, t }) {
|
|
|
295
299
|
const tasks = grouped.get(kind);
|
|
296
300
|
if (!tasks || tasks.length === 0)
|
|
297
301
|
return null;
|
|
298
|
-
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: t("pages.failedTasks.kind") }), _jsxs(CardTitle, { className: "text-[15px] font-mono", children: [kind, " (", tasks.length, ")"] })] }) }), _jsx(CardContent, { children: _jsx(TaskTable, { tasks: tasks, onCreateDraft: onCreateDraft, onRecover: onRecover, t: t }) })] }, kind));
|
|
302
|
+
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: "outline", children: t("pages.failedTasks.kind") }), _jsxs(CardTitle, { className: "text-[15px] font-mono", children: [kind, " (", tasks.length, ")"] })] }) }), _jsx(CardContent, { children: _jsx(TaskTable, { tasks: tasks, onCreateDraft: onCreateDraft, onRecover: onRecover, initialExpandedTaskId: initialExpandedTaskId, t: t }) })] }, kind));
|
|
299
303
|
}) }), _jsxs("div", { className: "mt-8 font-mono text-[11px] text-ink-4 space-y-1", children: [_jsxs("div", { children: [t("common.total"), ": ", data.total] }), data.total > data.tasks.length && (_jsx("div", { className: "text-amber/80 leading-relaxed", children: t("pages.failedTasks.partialResults")
|
|
300
304
|
.replace("{shown}", String(data.tasks.length))
|
|
301
305
|
.replace("{total}", String(data.total)) }))] })] }));
|
|
302
306
|
}
|
|
303
|
-
function TaskTable({ tasks, onCreateDraft, onRecover, t }) {
|
|
307
|
+
function TaskTable({ tasks, onCreateDraft, onRecover, initialExpandedTaskId, t }) {
|
|
304
308
|
// PRI-747 F22: one row at a time is expanded into its detail panel.
|
|
305
|
-
|
|
309
|
+
// ?taskId= deep link seeds the initial expansion (only when this table
|
|
310
|
+
// actually contains the task); after mount it behaves like a normal toggle.
|
|
311
|
+
const deepLinkTask = initialExpandedTaskId !== undefined
|
|
312
|
+
? tasks.find((task) => task.taskId === initialExpandedTaskId)
|
|
313
|
+
: undefined;
|
|
314
|
+
const [expandedId, setExpandedId] = useState(deepLinkTask?.taskId ?? null);
|
|
306
315
|
const [detail, setDetail] = useState({ status: "idle" });
|
|
307
316
|
// Drop responses from a superseded expand/collapse (rc-7: only the newest
|
|
308
317
|
// request's result may become current state).
|
|
309
318
|
const detailRequestIdRef = useRef(0);
|
|
319
|
+
const deepLinkFetchedRef = useRef(false);
|
|
310
320
|
// Fetch (or refetch) one task's detail; a stale response from a superseded
|
|
311
321
|
// request is dropped (rc-7: only the newest request's result may land).
|
|
312
322
|
const requestDetail = useCallback(async (taskId) => {
|
|
@@ -326,6 +336,19 @@ function TaskTable({ tasks, onCreateDraft, onRecover, t }) {
|
|
|
326
336
|
setDetail({ status: "error", taskId, message: result.error, nextAction: result.nextAction });
|
|
327
337
|
}
|
|
328
338
|
}, []);
|
|
339
|
+
// Deep link (?taskId=): fetch the linked task's detail and scroll its row
|
|
340
|
+
// into view once. The table mounts only after the task list has loaded, so
|
|
341
|
+
// the row is present at first effect run. The URL param is untrusted
|
|
342
|
+
// (rc-1): CSS.escape (unquoted attribute selector) keeps a crafted id from
|
|
343
|
+
// breaking the selector.
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
if (deepLinkTask === undefined || deepLinkFetchedRef.current)
|
|
346
|
+
return;
|
|
347
|
+
deepLinkFetchedRef.current = true;
|
|
348
|
+
void requestDetail(deepLinkTask.taskId);
|
|
349
|
+
const row = document.querySelector(`[data-testid=${CSS.escape(`detail-toggle-${deepLinkTask.taskId}`)}]`);
|
|
350
|
+
row?.scrollIntoView({ block: "center" });
|
|
351
|
+
}, [deepLinkTask, requestDetail]);
|
|
329
352
|
const toggleDetail = useCallback((taskId) => {
|
|
330
353
|
if (expandedId === taskId) {
|
|
331
354
|
// Invalidate any in-flight response, then collapse.
|
|
@@ -95,7 +95,7 @@ export function OwnerDecisionCard({ item, onResolved, governanceReady = true, ac
|
|
|
95
95
|
: item.kind === "rulecode_decision"
|
|
96
96
|
? t("pages.focus.ownerDecision.kindRulecode")
|
|
97
97
|
: t("pages.focus.ownerDecision.kindEvaluator");
|
|
98
|
-
return (_jsxs("article", { className: "bg-panel border border-line rounded-[6px] p-4 mb-3", "data-testid": `owner-decision-${item.taskId}`, children: [_jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-ink text-[14px] font-medium leading-snug", children: item.title }), _jsx("div", { className: "mt-1 text-ink-4 text-[11px] font-mono uppercase tracking-[0.08em]", children: kindLabel })] }), item.legacy && (_jsx("span", { className: "shrink-0 text-ink-4 border border-line rounded-[2px] px-[7px] py-[3px] text-[10.5px] font-mono", children: t("pages.focus.ownerDecision.legacyBadge") }))] }), _jsx("p", { className: "text-ink-2 text-[13px] leading-relaxed mb-2", children: item.summary }), item.review?.brief.kind === "evaluator" && (_jsxs("div", { className: "mb-3 grid gap-2 text-[12.5px] leading-relaxed", children: [item.review.brief.principle?.statement && (_jsxs("p", { children: [_jsx("span", { className: "font-medium text-ink", children: t("pages.focus.ownerDecision.principleLabel") }), " ", item.review.brief.principle.statement] })), item.review.brief.implementation?.summary && (_jsxs("p", { children: [_jsx("span", { className: "font-medium text-ink", children: t("pages.focus.ownerDecision.implementationLabel") }), " ", item.review.brief.implementation.summary] })), (item.review.brief.concerns?.length ?? 0) > 0 && (_jsxs("p", { className: "text-amber", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.concernLabel") }), " ", item.review.brief.concerns?.[0]] })), (item.review.brief.qualityChecklist?.items?.length ?? 0) > 0 && (_jsxs("div", { className: "mt-1 rounded-[3px] border border-line bg-surface-2 p-2", "data-testid": `owner-quality-checklist-${item.taskId}`, children: [_jsx("div", { className: "font-mono text-[10.5px] uppercase tracking-[0.06em] text-ink-3 mb-1", children: t("pages.focus.ownerDecision.qualityChecklistLabel") }), _jsx("ul", { className: "grid gap-0.5", children: item.review.brief.qualityChecklist?.items.map((qItem) => (_jsxs("li", { className: "flex items-start gap-1.5 text-[12px]", children: [_jsx("span", { className: qItem.pass ? "text-emerald" : "text-amber", "aria-hidden": "true", children: qItem.pass ? "✓" : "!" }), _jsx("span", { className: "sr-only", children: qItem.pass ? t("pages.focus.ownerDecision.qualityCheckStatus.passed") : t("pages.focus.ownerDecision.qualityCheckStatus.failed") }), _jsxs("span", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t(`pages.focus.ownerDecision.qualityCheck.${qItem.id}`) }), " ", qItem.note] })] }, qItem.id))) })] })), item.review.brief.formationEvidence && (_jsxs("div", { className: "mt-1 rounded-[3px] border border-line bg-surface-2 p-2", "data-testid": `owner-formation-evidence-${item.taskId}`, children: [_jsx("div", { className: "font-mono text-[10.5px] uppercase tracking-[0.06em] text-ink-3 mb-1", children: t("pages.focus.ownerDecision.formationLabel") }), item.review.brief.formationEvidence.sourcePainId ? (_jsxs("p", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.formationPainLabel") }), " ", _jsx("span", { className: "font-mono text-[11.5px]", children: item.review.brief.formationEvidence.sourcePainId })] })) : null, item.review.brief.formationEvidence.diagnosis?.summary && (_jsxs("p", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.formationDiagnosisLabel") }), " ", item.review.brief.formationEvidence.diagnosis.summary] })), item.review.brief.formationEvidence.diagnosis?.rootCause && (_jsxs("p", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.formationRootCauseLabel") }), " ", item.review.brief.formationEvidence.diagnosis.rootCause] })), (item.review.brief.formationEvidence.diagnosis?.evidence.length ?? 0) > 0 && (_jsxs("div", { children: [_jsx("span", { className: "font-medium text-ink-2", children: t("pages.focus.ownerDecision.formationEvidenceLabel") }), _jsx("ul", { className: "mt-0.5 grid gap-0.5 text-[12px] text-ink-3", children: item.review.brief.formationEvidence.diagnosis?.evidence.slice(0, 3).map((row, index) => (_jsxs("li", { children: [_jsx("span", { className: "font-mono text-[11px]", children: row.sourceRef }), " ", row.note] }, `${row.sourceRef}-${index}`))) })] })), !item.review.brief.formationEvidence.diagnosis && (_jsx("p", { className: "text-ink-3", children: t("pages.focus.ownerDecision.formationUnavailableNote") }))] }))] })), item.review && (_jsxs("div", { className: "mb-2 flex flex-wrap gap-2 text-[10.5px] font-mono uppercase tracking-[0.06em]", children: [_jsx("span", { className: "rounded-[2px] border border-line px-2 py-1 text-ink-3", children: t(`pages.focus.ownerDecision.evidence.${item.review.evidence.completeness}`) }), item.review.evidence.deterministicChecks.map(check => (_jsxs("span", { className: "rounded-[2px] border border-line px-2 py-1 text-ink-3", children: [check.check, ": ", check.status] }, check.check)))] })), item.machineRecommendation && (_jsxs("p", { className: "text-ink-3 text-[12.5px] mb-1", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.machineRecommendationLabel") }), " ", item.machineRecommendation, item.score !== undefined && _jsx("span", { className: "font-mono ml-1", children: item.score.toFixed(2) })] })), item.kind === "rollout_review" && (_jsx("p", { className: "text-ink-4 text-[12px] mb-1", children: t("pages.focus.ownerDecision.rolloutSafetyNote") })), evidenceUnavailable ? (_jsxs("div", { className: "mt-3 rounded-[3px] border border-amber/40 bg-amber/5 p-3", children: [_jsx("p", { className: "text-ink-2 text-[12.5px] leading-relaxed", children: t("pages.focus.ownerDecision.evidenceUnavailable") }), _jsx(Link, { to:
|
|
98
|
+
return (_jsxs("article", { className: "bg-panel border border-line rounded-[6px] p-4 mb-3", "data-testid": `owner-decision-${item.taskId}`, children: [_jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-ink text-[14px] font-medium leading-snug", children: item.title }), _jsx("div", { className: "mt-1 text-ink-4 text-[11px] font-mono uppercase tracking-[0.08em]", children: kindLabel })] }), item.legacy && (_jsx("span", { className: "shrink-0 text-ink-4 border border-line rounded-[2px] px-[7px] py-[3px] text-[10.5px] font-mono", children: t("pages.focus.ownerDecision.legacyBadge") }))] }), _jsx("p", { className: "text-ink-2 text-[13px] leading-relaxed mb-2", children: item.summary }), item.review?.brief.kind === "evaluator" && (_jsxs("div", { className: "mb-3 grid gap-2 text-[12.5px] leading-relaxed", children: [item.review.brief.principle?.statement && (_jsxs("p", { children: [_jsx("span", { className: "font-medium text-ink", children: t("pages.focus.ownerDecision.principleLabel") }), " ", item.review.brief.principle.statement] })), item.review.brief.implementation?.summary && (_jsxs("p", { children: [_jsx("span", { className: "font-medium text-ink", children: t("pages.focus.ownerDecision.implementationLabel") }), " ", item.review.brief.implementation.summary] })), (item.review.brief.concerns?.length ?? 0) > 0 && (_jsxs("p", { className: "text-amber", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.concernLabel") }), " ", item.review.brief.concerns?.[0]] })), (item.review.brief.qualityChecklist?.items?.length ?? 0) > 0 && (_jsxs("div", { className: "mt-1 rounded-[3px] border border-line bg-surface-2 p-2", "data-testid": `owner-quality-checklist-${item.taskId}`, children: [_jsx("div", { className: "font-mono text-[10.5px] uppercase tracking-[0.06em] text-ink-3 mb-1", children: t("pages.focus.ownerDecision.qualityChecklistLabel") }), _jsx("ul", { className: "grid gap-0.5", children: item.review.brief.qualityChecklist?.items.map((qItem) => (_jsxs("li", { className: "flex items-start gap-1.5 text-[12px]", children: [_jsx("span", { className: qItem.pass ? "text-emerald" : "text-amber", "aria-hidden": "true", children: qItem.pass ? "✓" : "!" }), _jsx("span", { className: "sr-only", children: qItem.pass ? t("pages.focus.ownerDecision.qualityCheckStatus.passed") : t("pages.focus.ownerDecision.qualityCheckStatus.failed") }), _jsxs("span", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t(`pages.focus.ownerDecision.qualityCheck.${qItem.id}`) }), " ", qItem.note] })] }, qItem.id))) })] })), item.review.brief.formationEvidence && (_jsxs("div", { className: "mt-1 rounded-[3px] border border-line bg-surface-2 p-2", "data-testid": `owner-formation-evidence-${item.taskId}`, children: [_jsx("div", { className: "font-mono text-[10.5px] uppercase tracking-[0.06em] text-ink-3 mb-1", children: t("pages.focus.ownerDecision.formationLabel") }), item.review.brief.formationEvidence.sourcePainId ? (_jsxs("p", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.formationPainLabel") }), " ", _jsx("span", { className: "font-mono text-[11.5px]", children: item.review.brief.formationEvidence.sourcePainId })] })) : null, item.review.brief.formationEvidence.diagnosis?.summary && (_jsxs("p", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.formationDiagnosisLabel") }), " ", item.review.brief.formationEvidence.diagnosis.summary] })), item.review.brief.formationEvidence.diagnosis?.rootCause && (_jsxs("p", { className: "text-ink-2", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.formationRootCauseLabel") }), " ", item.review.brief.formationEvidence.diagnosis.rootCause] })), (item.review.brief.formationEvidence.diagnosis?.evidence.length ?? 0) > 0 && (_jsxs("div", { children: [_jsx("span", { className: "font-medium text-ink-2", children: t("pages.focus.ownerDecision.formationEvidenceLabel") }), _jsx("ul", { className: "mt-0.5 grid gap-0.5 text-[12px] text-ink-3", children: item.review.brief.formationEvidence.diagnosis?.evidence.slice(0, 3).map((row, index) => (_jsxs("li", { children: [_jsx("span", { className: "font-mono text-[11px]", children: row.sourceRef }), " ", row.note] }, `${row.sourceRef}-${index}`))) })] })), !item.review.brief.formationEvidence.diagnosis && (_jsx("p", { className: "text-ink-3", children: t("pages.focus.ownerDecision.formationUnavailableNote") }))] }))] })), item.review && (_jsxs("div", { className: "mb-2 flex flex-wrap gap-2 text-[10.5px] font-mono uppercase tracking-[0.06em]", children: [_jsx("span", { className: "rounded-[2px] border border-line px-2 py-1 text-ink-3", children: t(`pages.focus.ownerDecision.evidence.${item.review.evidence.completeness}`) }), item.review.evidence.deterministicChecks.map(check => (_jsxs("span", { className: "rounded-[2px] border border-line px-2 py-1 text-ink-3", children: [check.check, ": ", check.status] }, check.check)))] })), item.machineRecommendation && (_jsxs("p", { className: "text-ink-3 text-[12.5px] mb-1", children: [_jsx("span", { className: "font-medium", children: t("pages.focus.ownerDecision.machineRecommendationLabel") }), " ", item.machineRecommendation, item.score !== undefined && _jsx("span", { className: "font-mono ml-1", children: item.score.toFixed(2) })] })), item.kind === "rollout_review" && (_jsx("p", { className: "text-ink-4 text-[12px] mb-1", children: t("pages.focus.ownerDecision.rolloutSafetyNote") })), evidenceUnavailable ? (_jsxs("div", { className: "mt-3 rounded-[3px] border border-amber/40 bg-amber/5 p-3", children: [_jsx("p", { className: "text-ink-2 text-[12.5px] leading-relaxed", children: t("pages.focus.ownerDecision.evidenceUnavailable") }), _jsx(Link, { to: `/failed-tasks?taskId=${encodeURIComponent(item.taskId)}`, "data-testid": `owner-evidence-recover-${item.taskId}`, className: "mt-2 inline-flex items-center border border-line text-ink bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors", children: t("pages.focus.ownerDecision.goRecoverCta") })] })) : isTaskDecision ? (_jsxs(_Fragment, { children: [showInstruction && (_jsxs("div", { className: "mt-2 mb-2", children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("pages.focus.ownerDecision.instructionLabel") }), _jsx("textarea", { value: instruction, onChange: (e) => setInstruction(e.target.value.slice(0, 500)), "data-testid": `owner-instruction-${item.taskId}`, placeholder: t("pages.focus.ownerDecision.instructionPlaceholder"), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[64px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov" })] })), requiresPartialAcknowledgement && (_jsxs("label", { className: "mt-2 mb-1 flex items-start gap-2 text-[12px] text-ink-3", children: [_jsx("input", { type: "checkbox", checked: partialEvidenceAcknowledged, onChange: (event) => setPartialEvidenceAcknowledged(event.target.checked), "data-testid": `owner-partial-evidence-ack-${item.taskId}`, className: "mt-0.5" }), _jsx("span", { children: t("pages.focus.ownerDecision.partialEvidenceAcknowledgement") })] })), !governanceReady && actionsLockedReason && (_jsxs("div", { className: "mt-3 rounded-[3px] border border-amber/40 bg-amber/5 p-3", "data-testid": `owner-actions-locked-${item.taskId}`, children: [_jsx("p", { className: "text-ink-2 text-[12.5px] leading-relaxed", children: actionsLockedReason }), _jsx(Link, { to: "/settings", "data-testid": `owner-actions-locked-settings-${item.taskId}`, className: "mt-2 inline-flex items-center border border-line text-ink bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors", children: t("pages.focus.ownerDecision.goSettingsCta") })] })), _jsxs("div", { className: "flex flex-wrap gap-2 mt-3", children: [canAccept && (_jsx("button", { type: "button", onClick: () => handleAction("accept_current"), disabled: !governanceReady || actionLoading !== null || (requiresPartialAcknowledgement && !partialEvidenceAcknowledged), "data-testid": `owner-accept-${item.taskId}`, className: "inline-flex items-center border border-gov text-gov bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-gov/5 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: actionLoading === "accept_current" ? t("common.loading") : t("pages.focus.ownerDecision.acceptCurrent") })), canRevise && (_jsx("button", { type: "button", onClick: () => {
|
|
99
99
|
if (!showInstruction) {
|
|
100
100
|
setShowInstruction(true);
|
|
101
101
|
return;
|
|
@@ -105,5 +105,7 @@ export function OwnerDecisionCard({ item, onResolved, governanceReady = true, ac
|
|
|
105
105
|
? t("pages.focus.ownerDecision.actionConsequence")
|
|
106
106
|
: acceptBlockedCopy }), _jsxs("button", { type: "button", onClick: () => setShowAdvanced(!showAdvanced), className: "mt-2 text-ink-4 text-[11px] font-mono uppercase tracking-[0.08em] hover:text-ink-3", "data-testid": `owner-advanced-toggle-${item.taskId}`, children: [showAdvanced ? "▾" : "▸", " ", t("pages.focus.ownerDecision.advanced")] }), showAdvanced && (_jsxs("dl", { className: "mt-2 grid gap-1 text-[11.5px] font-mono text-ink-4", children: [_jsxs("div", { children: ["taskId: ", item.taskId] }), _jsxs("div", { children: ["reasonCode: ", item.reasonCode] }), _jsxs("div", { children: ["reviewKey: ", item.reviewKey.slice(0, 24), "\u2026"] }), _jsxs("div", { children: ["epoch: ", item.expectedRevisionEpoch] }), item.review?.brief.requiredChanges.map((change, index) => (_jsxs("div", { children: ["requiredChange: ", change] }, `${change}-${index}`))), item.review?.evidence.items.map((evidence, index) => (_jsxs("div", { children: [evidence.evidenceClass, "/", evidence.label, ": ", evidence.value] }, `${evidence.label}-${index}`))), item.review?.brief.kind === "evaluator" && item.review.brief.formationEvidence && (_jsxs(_Fragment, { children: [_jsxs("div", { children: ["dreamerArtifact: ", item.review.brief.formationEvidence.provenance.sourceDreamerArtifactId] }), item.review.brief.formationEvidence.provenance.sourceDiagnosisArtifactId && (_jsxs("div", { children: ["diagnosisArtifact: ", item.review.brief.formationEvidence.provenance.sourceDiagnosisArtifactId] })), item.review.brief.formationEvidence.notes.map((note, index) => (_jsxs("div", { children: ["formationNote: ", note] }, `${note}-${index}`)))] }))] }))] })) : (_jsxs("div", { className: "mt-2", children: [_jsx("p", { className: "text-ink-4 text-[12px] mb-2", children: item.kind === "activation_approval"
|
|
107
107
|
? t("pages.focus.ownerDecision.approvalHint")
|
|
108
|
-
: t("pages.focus.ownerDecision.rulecodeHint") }), item.kind === "activation_approval" ? (_jsx(Link, { to:
|
|
108
|
+
: t("pages.focus.ownerDecision.rulecodeHint") }), item.kind === "activation_approval" ? (_jsx(Link, { to: item.principleId !== undefined
|
|
109
|
+
? `/principles/${encodeURIComponent(item.principleId)}`
|
|
110
|
+
: "/principles", "data-testid": `go-approvals-${item.taskId}`, className: "inline-flex items-center border border-gov text-gov bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-gov/5 transition-colors", children: t("pages.focus.ownerDecision.goApprovalsCta") })) : (_jsx(Link, { to: `/activation?activationId=${encodeURIComponent(item.taskId)}`, "data-testid": `go-activation-${item.taskId}`, className: "inline-flex items-center border border-gov text-gov bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-gov/5 transition-colors", children: t("pages.focus.ownerDecision.goActivationCta") }))] }))] }));
|
|
109
111
|
}
|
|
@@ -2187,9 +2187,27 @@
|
|
|
2187
2187
|
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
|
2188
2188
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2189
2189
|
}
|
|
2190
|
+
.ring {
|
|
2191
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
2192
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2193
|
+
}
|
|
2194
|
+
.ring-2 {
|
|
2195
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
2196
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
2197
|
+
}
|
|
2198
|
+
.ring-gov {
|
|
2199
|
+
--tw-ring-color: var(--color-gov);
|
|
2200
|
+
}
|
|
2201
|
+
.ring-offset-2 {
|
|
2202
|
+
--tw-ring-offset-width: 2px;
|
|
2203
|
+
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
|
2204
|
+
}
|
|
2190
2205
|
.ring-offset-background {
|
|
2191
2206
|
--tw-ring-offset-color: var(--color-background);
|
|
2192
2207
|
}
|
|
2208
|
+
.ring-offset-paper {
|
|
2209
|
+
--tw-ring-offset-color: var(--color-paper);
|
|
2210
|
+
}
|
|
2193
2211
|
.outline {
|
|
2194
2212
|
outline-style: var(--tw-outline-style);
|
|
2195
2213
|
outline-width: 1px;
|