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.
Files changed (37) hide show
  1. package/_release/product-identity.json +1 -1
  2. package/codex-adapter/package.json +1 -1
  3. package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +4 -19
  4. package/console/dist/server/models/OwnerDecisionConsoleModel.js +17 -0
  5. package/console/dist/server/models/artifact-principle-resolver.d.ts +29 -0
  6. package/console/dist/server/models/artifact-principle-resolver.js +53 -0
  7. package/console/dist/ui/pages/activation/ActivationPage.js +27 -1
  8. package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +29 -6
  9. package/console/dist/ui/pages/focus/OwnerDecisionCard.js +4 -2
  10. package/console/dist/web/assets/app.css +18 -0
  11. package/console/dist/web/assets/app.js +5294 -2773
  12. package/console/package.json +8 -8
  13. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +15 -4
  14. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  15. package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js +0 -10
  16. package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js.map +1 -1
  17. package/core/dist/runtime-v2/index.d.ts +1 -1
  18. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  19. package/core/dist/runtime-v2/index.js +1 -1
  20. package/core/dist/runtime-v2/index.js.map +1 -1
  21. package/core/package.json +2 -2
  22. package/dist/index.js +0 -0
  23. package/package.json +2 -2
  24. package/pd-cli/dist/commands/candidate.d.ts +3 -3
  25. package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
  26. package/pd-cli/dist/commands/candidate.js +9 -21
  27. package/pd-cli/dist/commands/candidate.js.map +1 -1
  28. package/pd-cli/dist/services/rulehost-pipeline-runner.d.ts.map +1 -1
  29. package/pd-cli/dist/services/rulehost-pipeline-runner.js +4 -17
  30. package/pd-cli/dist/services/rulehost-pipeline-runner.js.map +1 -1
  31. package/plugin/dist/bundle.js +126 -126
  32. package/plugin/dist/governance-audit.js +120 -120
  33. package/plugin/dist/openclaw.plugin.json +1 -1
  34. package/plugin/dist/rulehost-evidence.js +125 -125
  35. package/plugin/openclaw.plugin.json +1 -1
  36. package/plugin/package.json +1 -1
  37. package/release-manager/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "productVersion": "2.1.0",
4
- "sourceCommit": "2d4605a339688f0fedfe94d486b106a0d02e354e"
4
+ "sourceCommit": "8e62cd7369cac812d921464152728dd6d91f7c25"
5
5
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/codex-adapter",
3
- "version": "0.4.5",
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, PrincipleTreeLedgerAdapter, } from '@principles/core/runtime-v2';
1
+ import { SqliteConnection, SqliteApprovalQueueStore, SqlitePIArtifactStore, ApprovalQueue, } from '@principles/core/runtime-v2';
2
2
  import { loadLedger } from '@principles/core/principle-tree-ledger';
3
- import { resolveLedgerPrincipleId } from './principle-id-resolution.js';
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 resolutionLedger = new PrincipleTreeLedgerAdapter({ stateDir });
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
- let mappedId = artifact?.sourcePrincipleId ?? null;
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
- const [expandedId, setExpandedId] = useState(null);
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: "/failed", "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: () => {
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: "/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", "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") }))] }))] }));
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;