@agent-native/core 0.147.0 → 0.148.0
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/analytics/DEVELOPING.md +50 -3
- package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +190 -0
- package/corpus/templates/analytics/app/i18n/index.ts +14 -29
- package/corpus/templates/analytics/app/i18n-data.ts +10 -10
- package/corpus/templates/analytics/app/pages/Settings.tsx +5 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -3
- package/corpus/templates/analytics/scripts/first-party-analytics-bigquery-backfill.ts +197 -18
- package/corpus/templates/analytics/server/db/schema.ts +23 -0
- package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +3 -1
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +113 -13
- package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +140 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-volume.ts +207 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +40 -9
- package/corpus/templates/analytics/server/lib/gcloud.ts +148 -20
- package/corpus/templates/analytics/server/plugins/db.ts +42 -0
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/assets/app/i18n/index.ts +14 -29
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/brain/app/i18n/index.ts +14 -29
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/chat/app/i18n/index.ts +14 -29
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +2 -7
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +18 -13
- package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +87 -370
- package/corpus/templates/clips/app/components/settings/slack-section.tsx +59 -101
- package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +136 -213
- package/corpus/templates/clips/app/i18n/en-US.ts +11 -8
- package/corpus/templates/clips/desktop/src/app.tsx +1 -1
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/content/app/i18n/index.ts +14 -29
- package/corpus/templates/crm/app/i18n/index.ts +14 -29
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/design/actions/analyze-brand-assets.ts +15 -15
- package/corpus/templates/design/actions/import-code.ts +2 -43
- package/corpus/templates/design/app/i18n/index.ts +14 -29
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/dispatch/app/i18n-data.ts +1 -1
- package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/factory/AGENTS.md +40 -49
- package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +66 -0
- package/corpus/templates/factory/actions/evaluate-triage-item.ts +37 -0
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -0
- package/corpus/templates/factory/actions/get-triage-item.ts +25 -4
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +55 -0
- package/corpus/templates/factory/actions/ingest-github-observation.ts +21 -0
- package/corpus/templates/factory/actions/list-factory-audit.ts +118 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
- package/corpus/templates/factory/actions/list-triage-items.ts +24 -2
- package/corpus/templates/factory/actions/poll-github-sources.ts +60 -0
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +39 -0
- package/corpus/templates/factory/actions/poll-slack-channel.ts +45 -1
- package/corpus/templates/factory/actions/run-factory-automation.ts +1 -1
- package/corpus/templates/factory/actions/save-factory-automation.ts +2 -2
- package/corpus/templates/factory/actions/start-builder-for-item.ts +51 -0
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +685 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +6 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +25 -0
- package/corpus/templates/factory/app/routes/factory.tsx +95 -42
- package/corpus/templates/factory/server/db/schema.ts +37 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +5 -1
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +29 -0
- package/corpus/templates/factory/server/triage/audit.ts +79 -0
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/forms/app/i18n/index.ts +14 -29
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/macros/app/i18n/index.ts +14 -29
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/mail/app/i18n/index.ts +14 -29
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +7 -7
- package/corpus/templates/plan/app/i18n/index.ts +14 -29
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/slides/actions/analyze-brand-assets.ts +15 -15
- package/corpus/templates/slides/actions/import-code.ts +2 -43
- package/corpus/templates/slides/app/i18n/index.ts +14 -29
- package/corpus/templates/slides/server/routes/api/preview-images.get.ts +4 -1
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/tasks/app/components/layout/HeaderActions.tsx +1 -84
- package/corpus/templates/tasks/app/hooks/use-mobile.tsx +1 -21
- package/corpus/templates/tasks/app/i18n/index.ts +14 -29
- package/dist/agent/engine/ai-sdk-engine.js +2 -1
- package/dist/agent/engine/openai-compatible-endpoint.d.ts +2 -0
- package/dist/agent/engine/openai-compatible-endpoint.js +5 -1
- package/dist/agent/engine/provider-endpoint-validation.d.ts +9 -0
- package/dist/agent/engine/provider-endpoint-validation.js +26 -0
- package/dist/agent/engine/provider-env-vars.d.ts +1 -1
- package/dist/agent/engine/provider-env-vars.js +1 -1
- package/dist/agent/engine/registry.d.ts +8 -6
- package/dist/agent/engine/registry.js +37 -24
- package/dist/agent/tool-search.d.ts +1 -0
- package/dist/agent/tool-search.js +17 -1
- package/dist/automations/service.js +4 -10
- package/dist/brand-kit/brand-signals.d.ts +13 -0
- package/dist/brand-kit/brand-signals.js +15 -0
- package/dist/brand-kit/index.d.ts +2 -2
- package/dist/brand-kit/index.js +1 -1
- package/dist/cli/plan-blocks.d.ts +3 -18
- package/dist/cli/plan-blocks.js +2 -95
- package/dist/cli/plan-publish-store.d.ts +2 -61
- package/dist/cli/plan-publish-store.js +2 -127
- package/dist/cli/skills-content/connection.d.ts +1 -1
- package/dist/cli/skills-content/connection.js +1 -52
- package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-recap-skill.js +1 -553
- package/dist/client/agent-engine-key.d.ts +14 -4
- package/dist/client/agent-engine-key.js +51 -5
- package/dist/client/agent-provider-catalog.d.ts +20 -0
- package/dist/client/agent-provider-catalog.js +100 -0
- package/dist/client/blocks/index.d.ts +1 -0
- package/dist/client/blocks/index.js +1 -0
- package/dist/client/chat/run-recovery.js +4 -48
- package/dist/client/chat-first.js +4 -3
- package/dist/client/i18n.d.ts +14 -0
- package/dist/client/i18n.js +18 -0
- package/dist/client/settings/ProviderSetupForm.d.ts +21 -0
- package/dist/client/settings/ProviderSetupForm.js +145 -0
- package/dist/client/settings/SettingsPanel.js +52 -49
- package/dist/client/settings/index.d.ts +2 -0
- package/dist/client/settings/index.js +2 -0
- package/dist/coding-tools/run-code.d.ts +5 -0
- package/dist/coding-tools/run-code.js +196 -36
- package/dist/coding-tools/tool-orchestration.d.ts +10 -0
- package/dist/coding-tools/tool-orchestration.js +107 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/jobs/frontmatter.d.ts +9 -0
- package/dist/jobs/frontmatter.js +16 -0
- package/dist/jobs/scheduler.js +8 -8
- package/dist/jobs/tools.d.ts +1 -1
- package/dist/jobs/tools.js +2 -7
- package/dist/localization/default-messages.d.ts +20 -0
- package/dist/localization/default-messages.js +25 -5
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.js +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/test-agent-engine.js +34 -10
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/run-code-tools.d.ts +1 -1
- package/dist/server/agent-chat/run-code-tools.js +3 -1
- package/dist/server/agent-chat-plugin.js +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.js +43 -12
- package/dist/server/agents-bundle.js +2 -1
- package/dist/server/auth.js +21 -5
- package/dist/server/credential-provider.js +1 -0
- package/dist/server/design-token-utils.d.ts +18 -0
- package/dist/server/design-token-utils.js +31 -0
- package/dist/server/onboarding-html.js +22 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
- package/dist/templates/chat/app/i18n/index.ts +14 -29
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
- package/dist/templates/default/app/i18n/index.ts +14 -29
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
- package/dist/triggers/dispatcher.js +1 -8
- package/docs/content/environment-variables.mdx +39 -38
- package/package.json +4 -4
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
- package/src/templates/chat/app/i18n/index.ts +14 -29
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
- package/src/templates/default/app/i18n/index.ts +14 -29
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/plan/app/components/plan/wireframe/sanitize-html.ts +0 -343
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import {
|
|
4
|
+
IconAlertCircle,
|
|
5
|
+
IconBrandGithub,
|
|
6
|
+
IconBrandSlack,
|
|
7
|
+
IconChevronDown,
|
|
8
|
+
IconExternalLink,
|
|
9
|
+
IconSearch,
|
|
10
|
+
} from "@tabler/icons-react";
|
|
11
|
+
import { useEffect, useMemo } from "react";
|
|
12
|
+
import { useSearchParams } from "react-router";
|
|
13
|
+
|
|
14
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
15
|
+
|
|
16
|
+
type FactoryAuditEvent = {
|
|
17
|
+
id: string;
|
|
18
|
+
automationRunId: string | null;
|
|
19
|
+
automationThreadId: string | null;
|
|
20
|
+
automationName: string | null;
|
|
21
|
+
itemId: string | null;
|
|
22
|
+
source: string | null;
|
|
23
|
+
sourceUrl: string | null;
|
|
24
|
+
action: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
status: string;
|
|
27
|
+
summary: string;
|
|
28
|
+
details: Record<string, unknown>;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type FactoryAuditRun = {
|
|
33
|
+
id: string;
|
|
34
|
+
automation: string;
|
|
35
|
+
runId: string | null;
|
|
36
|
+
threadId: string | null;
|
|
37
|
+
status: string;
|
|
38
|
+
startedAt: number;
|
|
39
|
+
finishedAt: number | null;
|
|
40
|
+
error: string | null;
|
|
41
|
+
events: FactoryAuditEvent[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type FactoryAuditResponse = {
|
|
45
|
+
runs: FactoryAuditRun[];
|
|
46
|
+
count: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type AuditItemGroup = {
|
|
50
|
+
key: string;
|
|
51
|
+
events: FactoryAuditEvent[];
|
|
52
|
+
itemId: string | null;
|
|
53
|
+
source: string | null;
|
|
54
|
+
sourceUrl: string | null;
|
|
55
|
+
title: string;
|
|
56
|
+
outcome: string;
|
|
57
|
+
status: string;
|
|
58
|
+
latestAt: string;
|
|
59
|
+
checks: number;
|
|
60
|
+
decision: FactoryAuditEvent | null;
|
|
61
|
+
externalAction: FactoryAuditEvent | null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function FactoryAuditView({
|
|
65
|
+
factoryId,
|
|
66
|
+
refreshToken = 0,
|
|
67
|
+
}: {
|
|
68
|
+
factoryId: string;
|
|
69
|
+
refreshToken?: number;
|
|
70
|
+
}) {
|
|
71
|
+
const t = useT();
|
|
72
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
73
|
+
const selectedRunId = searchParams.get("auditRunId");
|
|
74
|
+
const auditQuery = useActionQuery<FactoryAuditResponse>(
|
|
75
|
+
"list-factory-audit",
|
|
76
|
+
{ factoryId, limit: 30 },
|
|
77
|
+
{ staleTime: 5_000 },
|
|
78
|
+
);
|
|
79
|
+
const refetchAudit = auditQuery.refetch;
|
|
80
|
+
const runs = auditQuery.data?.runs ?? [];
|
|
81
|
+
const selectedRun =
|
|
82
|
+
runs.find((run) => run.id === selectedRunId) ?? runs[0] ?? null;
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!selectedRun || selectedRun.id === selectedRunId) return;
|
|
86
|
+
setSearchParams(
|
|
87
|
+
(current) => {
|
|
88
|
+
const next = new URLSearchParams(current);
|
|
89
|
+
next.set("auditRunId", selectedRun.id);
|
|
90
|
+
return next;
|
|
91
|
+
},
|
|
92
|
+
{ replace: true },
|
|
93
|
+
);
|
|
94
|
+
}, [selectedRun, selectedRunId, setSearchParams]);
|
|
95
|
+
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (refreshToken === 0) return;
|
|
98
|
+
void refetchAudit();
|
|
99
|
+
}, [refreshToken, refetchAudit]);
|
|
100
|
+
|
|
101
|
+
function selectRun(runId: string) {
|
|
102
|
+
setSearchParams(
|
|
103
|
+
(current) => {
|
|
104
|
+
const next = new URLSearchParams(current);
|
|
105
|
+
next.set("auditRunId", runId);
|
|
106
|
+
return next;
|
|
107
|
+
},
|
|
108
|
+
{ replace: true },
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className="p-4 lg:p-6">
|
|
114
|
+
{auditQuery.isError ? (
|
|
115
|
+
<Card>
|
|
116
|
+
<CardContent className="flex items-start gap-2 p-4 text-sm text-destructive">
|
|
117
|
+
<IconAlertCircle className="mt-0.5 size-4 shrink-0" />
|
|
118
|
+
<span>{t("factoryRoute.auditLoadError")}</span>
|
|
119
|
+
</CardContent>
|
|
120
|
+
</Card>
|
|
121
|
+
) : auditQuery.isLoading ? (
|
|
122
|
+
<div className="grid gap-4 lg:grid-cols-[minmax(240px,.4fr)_minmax(0,1fr)]">
|
|
123
|
+
<AuditSkeleton rows={5} />
|
|
124
|
+
<AuditSkeleton rows={4} />
|
|
125
|
+
</div>
|
|
126
|
+
) : runs.length === 0 ? (
|
|
127
|
+
<Card>
|
|
128
|
+
<CardContent className="p-6 text-sm text-muted-foreground">
|
|
129
|
+
{t("factoryRoute.auditEmpty")}
|
|
130
|
+
</CardContent>
|
|
131
|
+
</Card>
|
|
132
|
+
) : (
|
|
133
|
+
<div className="grid gap-4 lg:grid-cols-[minmax(240px,.4fr)_minmax(0,1fr)]">
|
|
134
|
+
<Card>
|
|
135
|
+
<CardHeader className="px-4 py-3">
|
|
136
|
+
<div className="flex items-center justify-between gap-3">
|
|
137
|
+
<CardTitle className="text-sm">
|
|
138
|
+
{t("factoryRoute.auditRuns")}
|
|
139
|
+
</CardTitle>
|
|
140
|
+
<span className="text-xs text-muted-foreground">
|
|
141
|
+
{runs.length}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
</CardHeader>
|
|
145
|
+
<CardContent className="p-0">
|
|
146
|
+
<div className="divide-y">
|
|
147
|
+
{runs.map((run) => (
|
|
148
|
+
<button
|
|
149
|
+
key={run.id}
|
|
150
|
+
type="button"
|
|
151
|
+
className={`w-full cursor-pointer p-3 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring ${selectedRun?.id === run.id ? "bg-muted/60" : ""}`}
|
|
152
|
+
onClick={() => selectRun(run.id)}
|
|
153
|
+
>
|
|
154
|
+
<div className="flex items-center justify-between gap-3">
|
|
155
|
+
<span className="min-w-0 truncate text-sm font-medium">
|
|
156
|
+
{formatAutomationName(run.automation)}
|
|
157
|
+
</span>
|
|
158
|
+
<AuditStatus status={run.status} />
|
|
159
|
+
</div>
|
|
160
|
+
<div className="mt-1 flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
|
161
|
+
<span className="shrink-0">
|
|
162
|
+
{formatAuditAge(run.startedAt)}
|
|
163
|
+
</span>
|
|
164
|
+
<span className="shrink-0">
|
|
165
|
+
{run.events.length}{" "}
|
|
166
|
+
{formatAuditCountLabel(run.events.length)}
|
|
167
|
+
</span>
|
|
168
|
+
</div>
|
|
169
|
+
</button>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
</CardContent>
|
|
173
|
+
</Card>
|
|
174
|
+
|
|
175
|
+
<Card>
|
|
176
|
+
<CardHeader className="border-b px-4 py-3">
|
|
177
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
178
|
+
<div className="min-w-0">
|
|
179
|
+
<CardTitle className="truncate text-base">
|
|
180
|
+
{selectedRun &&
|
|
181
|
+
formatAutomationName(selectedRun.automation)}
|
|
182
|
+
</CardTitle>
|
|
183
|
+
{selectedRun && (
|
|
184
|
+
<p className="mt-1 truncate text-xs text-muted-foreground">
|
|
185
|
+
{formatAuditAge(selectedRun.startedAt)}
|
|
186
|
+
</p>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
{selectedRun && <AuditStatus status={selectedRun.status} />}
|
|
190
|
+
</div>
|
|
191
|
+
</CardHeader>
|
|
192
|
+
<CardContent className="pt-4">
|
|
193
|
+
{selectedRun && <AuditRunDetail run={selectedRun} />}
|
|
194
|
+
</CardContent>
|
|
195
|
+
</Card>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function AuditRunDetail({ run }: { run: FactoryAuditRun }) {
|
|
203
|
+
const t = useT();
|
|
204
|
+
const groups = useMemo(() => groupAuditEvents(run.events), [run.events]);
|
|
205
|
+
const itemCount = new Set(
|
|
206
|
+
run.events
|
|
207
|
+
.map((event) => event.itemId)
|
|
208
|
+
.filter((itemId): itemId is string => Boolean(itemId)),
|
|
209
|
+
).size;
|
|
210
|
+
const decisionCount = run.events.filter(
|
|
211
|
+
(event) => event.kind === "decision",
|
|
212
|
+
).length;
|
|
213
|
+
const actionCount = run.events.filter(
|
|
214
|
+
(event) => event.kind === "external_action",
|
|
215
|
+
).length;
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<>
|
|
219
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-muted-foreground">
|
|
220
|
+
<span>
|
|
221
|
+
{itemCount} {itemCount === 1 ? "item" : "items"}
|
|
222
|
+
</span>
|
|
223
|
+
<span>
|
|
224
|
+
{decisionCount} {decisionCount === 1 ? "decision" : "decisions"}
|
|
225
|
+
</span>
|
|
226
|
+
<span>
|
|
227
|
+
{actionCount} {actionCount === 1 ? "action" : "actions"}
|
|
228
|
+
</span>
|
|
229
|
+
{run.threadId && (
|
|
230
|
+
<a
|
|
231
|
+
href={`/chat/${encodeURIComponent(run.threadId)}`}
|
|
232
|
+
className="ml-auto inline-flex items-center gap-1 text-primary hover:underline"
|
|
233
|
+
>
|
|
234
|
+
{t("factoryRoute.auditOpenThread")}
|
|
235
|
+
<IconExternalLink className="size-3" />
|
|
236
|
+
</a>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
{run.error && (
|
|
241
|
+
<div className="mt-4 rounded-md border border-destructive/30 bg-destructive/5 p-3 text-sm">
|
|
242
|
+
<p className="font-medium text-destructive">
|
|
243
|
+
{t("factoryRoute.auditRunError")}
|
|
244
|
+
</p>
|
|
245
|
+
<p className="mt-1 break-words text-muted-foreground">{run.error}</p>
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
|
|
249
|
+
<div className="mt-5">
|
|
250
|
+
<div className="mb-2 flex items-center justify-between gap-3">
|
|
251
|
+
<h3 className="text-sm font-medium">Activity</h3>
|
|
252
|
+
<span className="text-xs text-muted-foreground">
|
|
253
|
+
{groups.length} {groups.length === 1 ? "thing" : "things"}
|
|
254
|
+
</span>
|
|
255
|
+
</div>
|
|
256
|
+
{groups.length === 0 ? (
|
|
257
|
+
<p className="rounded-md border border-dashed p-4 text-sm text-muted-foreground">
|
|
258
|
+
{t("factoryRoute.auditNoEvents")}
|
|
259
|
+
</p>
|
|
260
|
+
) : (
|
|
261
|
+
<div className="overflow-hidden rounded-md border">
|
|
262
|
+
{groups.map((group) => (
|
|
263
|
+
<AuditItemRow key={group.key} group={group} />
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
</>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function AuditItemRow({ group }: { group: AuditItemGroup }) {
|
|
273
|
+
const t = useT();
|
|
274
|
+
const decision = group.decision;
|
|
275
|
+
const rationale = decision?.summary ?? group.externalAction?.summary ?? null;
|
|
276
|
+
const sourceLink =
|
|
277
|
+
group.sourceUrl ?? group.events.find((event) => event.sourceUrl)?.sourceUrl;
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<details className="group border-b last:border-b-0">
|
|
281
|
+
<summary className="flex cursor-pointer list-none items-center gap-3 px-3 py-3 hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring [&::-webkit-details-marker]:hidden">
|
|
282
|
+
<AuditSourceIcon source={group.source} />
|
|
283
|
+
<div className="min-w-0 flex-1">
|
|
284
|
+
<p className="truncate text-sm font-medium">{group.title}</p>
|
|
285
|
+
<p className="mt-0.5 truncate text-xs text-muted-foreground">
|
|
286
|
+
{group.outcome}
|
|
287
|
+
<span aria-hidden="true"> · </span>
|
|
288
|
+
{group.checks} {formatAuditCountLabel(group.checks)}
|
|
289
|
+
</p>
|
|
290
|
+
</div>
|
|
291
|
+
<span className="hidden shrink-0 text-[11px] text-muted-foreground sm:block">
|
|
292
|
+
{formatAuditSource(group.source)}
|
|
293
|
+
</span>
|
|
294
|
+
<AuditStatus status={group.status} compact />
|
|
295
|
+
<IconChevronDown className="size-4 shrink-0 text-muted-foreground transition-transform duration-200 ease-[var(--ease-collapse)] group-open:rotate-180 motion-reduce:transition-none" />
|
|
296
|
+
</summary>
|
|
297
|
+
<div className="border-t bg-muted/20 px-4 pb-4 pt-3">
|
|
298
|
+
<div className="grid gap-3 sm:grid-cols-[minmax(0,1fr)_auto]">
|
|
299
|
+
<div>
|
|
300
|
+
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
301
|
+
{rationale ? "Why" : "What happened"}
|
|
302
|
+
</p>
|
|
303
|
+
<p className="mt-1 max-w-[72ch] text-sm leading-6">
|
|
304
|
+
{rationale ??
|
|
305
|
+
"The source was inspected, but no action was taken."}
|
|
306
|
+
</p>
|
|
307
|
+
</div>
|
|
308
|
+
<AuditDecisionFacts event={decision} />
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div className="mt-4 border-t border-border/70 pt-3">
|
|
312
|
+
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
313
|
+
Trace
|
|
314
|
+
</p>
|
|
315
|
+
<div className="mt-2 space-y-2">
|
|
316
|
+
{group.events.map((event) => (
|
|
317
|
+
<div
|
|
318
|
+
key={event.id}
|
|
319
|
+
className="flex items-center justify-between gap-3 text-xs text-muted-foreground"
|
|
320
|
+
>
|
|
321
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
322
|
+
<AuditStatus status={event.status} compact />
|
|
323
|
+
<span className="truncate">
|
|
324
|
+
{formatAuditAction(event.action)}
|
|
325
|
+
</span>
|
|
326
|
+
</span>
|
|
327
|
+
<time className="shrink-0">
|
|
328
|
+
{formatAuditAge(event.createdAt)}
|
|
329
|
+
</time>
|
|
330
|
+
</div>
|
|
331
|
+
))}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
{(group.itemId || sourceLink) && (
|
|
336
|
+
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2 border-t border-border/70 pt-3 text-xs">
|
|
337
|
+
{group.itemId && (
|
|
338
|
+
<a
|
|
339
|
+
href={`/factory?tab=inbox&itemId=${encodeURIComponent(group.itemId)}`}
|
|
340
|
+
className="text-primary hover:underline"
|
|
341
|
+
>
|
|
342
|
+
{t("factoryRoute.auditOpenItem")}
|
|
343
|
+
</a>
|
|
344
|
+
)}
|
|
345
|
+
{sourceLink && (
|
|
346
|
+
<a
|
|
347
|
+
href={sourceLink}
|
|
348
|
+
target="_blank"
|
|
349
|
+
rel="noreferrer"
|
|
350
|
+
className="inline-flex items-center gap-1 text-primary hover:underline"
|
|
351
|
+
>
|
|
352
|
+
{t("factoryRoute.auditOpenSource")}
|
|
353
|
+
<IconExternalLink className="size-3" />
|
|
354
|
+
</a>
|
|
355
|
+
)}
|
|
356
|
+
</div>
|
|
357
|
+
)}
|
|
358
|
+
</div>
|
|
359
|
+
</details>
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function AuditDecisionFacts({ event }: { event: FactoryAuditEvent | null }) {
|
|
364
|
+
const t = useT();
|
|
365
|
+
if (!event) return null;
|
|
366
|
+
const clearBug = readBooleanDetail(event.details, "clearBug");
|
|
367
|
+
const productUx = readBooleanDetail(event.details, "productUxImplications");
|
|
368
|
+
const ownerArea = readStringDetail(event.details, "ownerOwnedArea");
|
|
369
|
+
const guards = readGuardSummary(event.details.guardResults);
|
|
370
|
+
if (clearBug === null && productUx === null && !ownerArea && !guards)
|
|
371
|
+
return null;
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<div className="flex flex-wrap content-start gap-x-3 gap-y-1 text-xs text-muted-foreground sm:max-w-[260px] sm:justify-end">
|
|
375
|
+
{clearBug !== null && (
|
|
376
|
+
<AuditFact
|
|
377
|
+
label={t("factoryRoute.auditClearBug")}
|
|
378
|
+
value={yesNo(clearBug)}
|
|
379
|
+
/>
|
|
380
|
+
)}
|
|
381
|
+
{productUx !== null && (
|
|
382
|
+
<AuditFact
|
|
383
|
+
label={t("factoryRoute.auditUxImpact")}
|
|
384
|
+
value={yesNo(productUx)}
|
|
385
|
+
/>
|
|
386
|
+
)}
|
|
387
|
+
{ownerArea && (
|
|
388
|
+
<AuditFact label={t("factoryRoute.auditOwnerArea")} value={ownerArea} />
|
|
389
|
+
)}
|
|
390
|
+
{guards && <AuditFact label="Guards" value={guards} />}
|
|
391
|
+
</div>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function AuditFact({ label, value }: { label: string; value: string }) {
|
|
396
|
+
return (
|
|
397
|
+
<span>
|
|
398
|
+
<span className="font-medium text-foreground">{label}</span> {value}
|
|
399
|
+
</span>
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function AuditSourceIcon({ source }: { source: string | null }) {
|
|
404
|
+
const className = "size-4 shrink-0 text-muted-foreground";
|
|
405
|
+
const normalized = source?.toLowerCase() ?? "";
|
|
406
|
+
if (normalized.includes("slack"))
|
|
407
|
+
return <IconBrandSlack className={className} />;
|
|
408
|
+
if (normalized.includes("github"))
|
|
409
|
+
return <IconBrandGithub className={className} />;
|
|
410
|
+
if (normalized.includes("sentry"))
|
|
411
|
+
return <IconAlertCircle className={className} />;
|
|
412
|
+
return <IconSearch className={className} />;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function AuditStatus({
|
|
416
|
+
status,
|
|
417
|
+
compact = false,
|
|
418
|
+
}: {
|
|
419
|
+
status: string;
|
|
420
|
+
compact?: boolean;
|
|
421
|
+
}) {
|
|
422
|
+
const tone =
|
|
423
|
+
status === "success"
|
|
424
|
+
? "bg-emerald-500"
|
|
425
|
+
: status === "error"
|
|
426
|
+
? "bg-destructive"
|
|
427
|
+
: status === "skipped"
|
|
428
|
+
? "bg-muted-foreground"
|
|
429
|
+
: "bg-amber-500";
|
|
430
|
+
const label = formatAuditLabel(status);
|
|
431
|
+
return (
|
|
432
|
+
<span
|
|
433
|
+
className={`inline-flex shrink-0 items-center gap-1.5 text-xs text-muted-foreground ${compact ? "" : "capitalize"}`}
|
|
434
|
+
aria-label={label}
|
|
435
|
+
title={label}
|
|
436
|
+
>
|
|
437
|
+
<span className={`size-1.5 rounded-full ${tone}`} />
|
|
438
|
+
{!compact && label}
|
|
439
|
+
</span>
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function AuditSkeleton({ rows }: { rows: number }) {
|
|
444
|
+
return (
|
|
445
|
+
<Card>
|
|
446
|
+
<CardContent className="space-y-4 p-4">
|
|
447
|
+
<div className="h-5 w-32 animate-pulse rounded bg-muted" />
|
|
448
|
+
{Array.from({ length: rows }, (_, index) => (
|
|
449
|
+
<div key={index} className="space-y-2">
|
|
450
|
+
<div className="h-4 w-3/4 animate-pulse rounded bg-muted" />
|
|
451
|
+
<div className="h-3 w-1/2 rounded bg-muted/70" />
|
|
452
|
+
</div>
|
|
453
|
+
))}
|
|
454
|
+
</CardContent>
|
|
455
|
+
</Card>
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function groupAuditEvents(events: FactoryAuditEvent[]): AuditItemGroup[] {
|
|
460
|
+
const grouped = new Map<string, FactoryAuditEvent[]>();
|
|
461
|
+
for (const event of events) {
|
|
462
|
+
const key = event.itemId ?? `event:${event.id}`;
|
|
463
|
+
const current = grouped.get(key) ?? [];
|
|
464
|
+
current.push(event);
|
|
465
|
+
grouped.set(key, current);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return [...grouped.entries()]
|
|
469
|
+
.map(([key, groupEvents]) => {
|
|
470
|
+
const decision =
|
|
471
|
+
groupEvents.find((event) => event.kind === "decision") ?? null;
|
|
472
|
+
const externalAction =
|
|
473
|
+
groupEvents.find((event) => event.kind === "external_action") ?? null;
|
|
474
|
+
const titleEvent =
|
|
475
|
+
groupEvents.find((event) => event.action === "get-triage-item") ??
|
|
476
|
+
groupEvents.find(
|
|
477
|
+
(event) => event.action === "get-slack-feedback-context",
|
|
478
|
+
) ??
|
|
479
|
+
groupEvents.find((event) => event.action === "list-triage-items") ??
|
|
480
|
+
groupEvents.find((event) => event.kind === "observed") ??
|
|
481
|
+
groupEvents[0];
|
|
482
|
+
const latestAt = groupEvents.reduce(
|
|
483
|
+
(latest, event) =>
|
|
484
|
+
new Date(event.createdAt).getTime() > new Date(latest).getTime()
|
|
485
|
+
? event.createdAt
|
|
486
|
+
: latest,
|
|
487
|
+
groupEvents[0].createdAt,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
key,
|
|
492
|
+
events: groupEvents,
|
|
493
|
+
itemId: titleEvent.itemId,
|
|
494
|
+
source: titleEvent.source,
|
|
495
|
+
sourceUrl: titleEvent.sourceUrl,
|
|
496
|
+
title: formatAuditSubject(titleEvent),
|
|
497
|
+
outcome: formatAuditOutcome(groupEvents, decision, externalAction),
|
|
498
|
+
status: formatGroupStatus(groupEvents, decision, externalAction),
|
|
499
|
+
latestAt,
|
|
500
|
+
checks: groupEvents.filter(
|
|
501
|
+
(event) => event.kind === "read" || event.kind === "observed",
|
|
502
|
+
).length,
|
|
503
|
+
decision,
|
|
504
|
+
externalAction,
|
|
505
|
+
};
|
|
506
|
+
})
|
|
507
|
+
.sort(
|
|
508
|
+
(left, right) =>
|
|
509
|
+
new Date(right.latestAt).getTime() - new Date(left.latestAt).getTime(),
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function formatAuditSubject(event: FactoryAuditEvent): string {
|
|
514
|
+
if (event.action === "get-slack-feedback-context") {
|
|
515
|
+
const messageCount = readNumberDetail(event.details, "messageCount");
|
|
516
|
+
return `Slack thread${messageCount === null ? "" : ` · ${messageCount} ${messageCount === 1 ? "message" : "messages"}`}`;
|
|
517
|
+
}
|
|
518
|
+
const summary = event.summary.trim();
|
|
519
|
+
const subject = summary.replace(/^(Inspected|Read)\s+/i, "");
|
|
520
|
+
return truncateAuditText(subject || formatAuditAction(event.action), 110);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function formatAuditOutcome(
|
|
524
|
+
events: FactoryAuditEvent[],
|
|
525
|
+
decision: FactoryAuditEvent | null,
|
|
526
|
+
externalAction: FactoryAuditEvent | null,
|
|
527
|
+
): string {
|
|
528
|
+
if (externalAction) {
|
|
529
|
+
const provider = readStringDetail(externalAction.details, "provider");
|
|
530
|
+
if (provider === "bot-tag") return "Builder tagged in Slack";
|
|
531
|
+
if (provider === "builder-http") return "Builder fix submitted";
|
|
532
|
+
return "Action taken";
|
|
533
|
+
}
|
|
534
|
+
if (decision) {
|
|
535
|
+
const ownerArea = readStringDetail(decision.details, "ownerOwnedArea");
|
|
536
|
+
const productUx = readBooleanDetail(
|
|
537
|
+
decision.details,
|
|
538
|
+
"productUxImplications",
|
|
539
|
+
);
|
|
540
|
+
if (decision.status === "skipped" || ownerArea || productUx === true) {
|
|
541
|
+
return "Held for review";
|
|
542
|
+
}
|
|
543
|
+
if (readBooleanDetail(decision.details, "clearBug") === true) {
|
|
544
|
+
return "Builder fix selected";
|
|
545
|
+
}
|
|
546
|
+
return "Decision recorded";
|
|
547
|
+
}
|
|
548
|
+
if (
|
|
549
|
+
events.some(
|
|
550
|
+
(event) =>
|
|
551
|
+
event.kind === "observed" && /no new|no result/i.test(event.summary),
|
|
552
|
+
)
|
|
553
|
+
) {
|
|
554
|
+
return "No new items";
|
|
555
|
+
}
|
|
556
|
+
return "Inspected only";
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function formatGroupStatus(
|
|
560
|
+
events: FactoryAuditEvent[],
|
|
561
|
+
decision: FactoryAuditEvent | null,
|
|
562
|
+
externalAction: FactoryAuditEvent | null,
|
|
563
|
+
): string {
|
|
564
|
+
if (events.some((event) => event.status === "error")) return "error";
|
|
565
|
+
if (externalAction) return externalAction.status;
|
|
566
|
+
if (decision) return decision.status;
|
|
567
|
+
return events.some((event) => event.status === "running")
|
|
568
|
+
? "running"
|
|
569
|
+
: "success";
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function formatAuditSource(source: string | null): string {
|
|
573
|
+
const normalized = source?.toLowerCase() ?? "";
|
|
574
|
+
if (normalized.includes("slack")) return "Slack";
|
|
575
|
+
if (normalized.includes("github")) return "GitHub";
|
|
576
|
+
if (normalized.includes("sentry")) return "Sentry";
|
|
577
|
+
return source ? formatAuditLabel(source) : "Factory";
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function formatAutomationName(value: string): string {
|
|
581
|
+
const words = value
|
|
582
|
+
.replace(/^factory-/, "")
|
|
583
|
+
.replace(/[_-]+/g, " ")
|
|
584
|
+
.split(" ");
|
|
585
|
+
return words
|
|
586
|
+
.filter(Boolean)
|
|
587
|
+
.map((word) => {
|
|
588
|
+
const lower = word.toLowerCase();
|
|
589
|
+
if (lower === "github") return "GitHub";
|
|
590
|
+
if (lower === "slack") return "Slack";
|
|
591
|
+
if (lower === "sentry") return "Sentry";
|
|
592
|
+
if (lower === "pr") return "PR";
|
|
593
|
+
return `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`;
|
|
594
|
+
})
|
|
595
|
+
.join(" ");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function formatAuditAction(value: string): string {
|
|
599
|
+
return formatAuditLabel(value).replace(/^Poll /, "Check ");
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function formatAuditCountLabel(value: number): string {
|
|
603
|
+
return value === 1 ? "check" : "checks";
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function formatAuditAge(value: string | number) {
|
|
607
|
+
const date = new Date(value);
|
|
608
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
609
|
+
|
|
610
|
+
const elapsedSeconds = Math.max(
|
|
611
|
+
0,
|
|
612
|
+
Math.floor((Date.now() - date.getTime()) / 1_000),
|
|
613
|
+
);
|
|
614
|
+
if (elapsedSeconds < 60) return "now";
|
|
615
|
+
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
|
616
|
+
if (elapsedMinutes < 60) return `${elapsedMinutes}m`;
|
|
617
|
+
const elapsedHours = Math.floor(elapsedMinutes / 60);
|
|
618
|
+
if (elapsedHours < 24) return `${elapsedHours}h`;
|
|
619
|
+
const elapsedDays = Math.floor(elapsedHours / 24);
|
|
620
|
+
if (elapsedDays < 30) return `${elapsedDays}d`;
|
|
621
|
+
const elapsedMonths = Math.floor(elapsedDays / 30);
|
|
622
|
+
if (elapsedMonths < 12) return `${elapsedMonths}mo`;
|
|
623
|
+
return `${Math.floor(elapsedMonths / 12)}y`;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function formatAuditLabel(value: string): string {
|
|
627
|
+
return value
|
|
628
|
+
.replace(/[_-]+/g, " ")
|
|
629
|
+
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function formatAuditValue(value: unknown): string {
|
|
633
|
+
if (value === null || value === undefined) return "-";
|
|
634
|
+
if (typeof value === "string") return value;
|
|
635
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
636
|
+
return String(value);
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
return JSON.stringify(value);
|
|
640
|
+
} catch {
|
|
641
|
+
return "unreadable";
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function truncateAuditText(value: string, maxLength: number): string {
|
|
646
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function readBooleanDetail(
|
|
650
|
+
details: Record<string, unknown>,
|
|
651
|
+
key: string,
|
|
652
|
+
): boolean | null {
|
|
653
|
+
return typeof details[key] === "boolean" ? details[key] : null;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function readNumberDetail(
|
|
657
|
+
details: Record<string, unknown>,
|
|
658
|
+
key: string,
|
|
659
|
+
): number | null {
|
|
660
|
+
return typeof details[key] === "number" ? details[key] : null;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function readStringDetail(
|
|
664
|
+
details: Record<string, unknown>,
|
|
665
|
+
key: string,
|
|
666
|
+
): string | null {
|
|
667
|
+
return typeof details[key] === "string" && details[key] ? details[key] : null;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function readGuardSummary(value: unknown): string | null {
|
|
671
|
+
if (!Array.isArray(value) || value.length === 0) return null;
|
|
672
|
+
const passed = value.filter(
|
|
673
|
+
(guard): guard is { passed: boolean } =>
|
|
674
|
+
typeof guard === "object" &&
|
|
675
|
+
guard !== null &&
|
|
676
|
+
"passed" in guard &&
|
|
677
|
+
typeof guard.passed === "boolean" &&
|
|
678
|
+
guard.passed,
|
|
679
|
+
).length;
|
|
680
|
+
return `${passed}/${value.length} passed`;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function yesNo(value: boolean): string {
|
|
684
|
+
return value ? "yes" : "no";
|
|
685
|
+
}
|
|
@@ -10,6 +10,7 @@ export interface NavigationState {
|
|
|
10
10
|
factoryId?: string;
|
|
11
11
|
factoryTab?: string;
|
|
12
12
|
factoryAutomationId?: string;
|
|
13
|
+
factoryAuditRunId?: string;
|
|
13
14
|
factoryNodeId?: string;
|
|
14
15
|
factoryEdgeId?: string;
|
|
15
16
|
}
|
|
@@ -36,6 +37,11 @@ export function useNavigationState() {
|
|
|
36
37
|
searchParams.get("automationId") ?? undefined,
|
|
37
38
|
}
|
|
38
39
|
: {}),
|
|
40
|
+
...(pathname.startsWith("/factory") && searchParams.get("auditRunId")
|
|
41
|
+
? {
|
|
42
|
+
factoryAuditRunId: searchParams.get("auditRunId") ?? undefined,
|
|
43
|
+
}
|
|
44
|
+
: {}),
|
|
39
45
|
...(pathname.startsWith("/factory") && searchParams.get("node")
|
|
40
46
|
? { factoryNodeId: searchParams.get("node") ?? undefined }
|
|
41
47
|
: {}),
|