feat(ui): reconcile backup UI changes with current routing and interaction features
This commit is contained in:
@@ -13,13 +13,17 @@ import { companiesApi } from "../api/companies";
|
||||
import { ApiError } from "../api/client";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
|
||||
type CompanySelectionSource = "manual" | "route_sync" | "bootstrap";
|
||||
type CompanySelectionOptions = { source?: CompanySelectionSource };
|
||||
|
||||
interface CompanyContextValue {
|
||||
companies: Company[];
|
||||
selectedCompanyId: string | null;
|
||||
selectedCompany: Company | null;
|
||||
selectionSource: CompanySelectionSource;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
setSelectedCompanyId: (companyId: string) => void;
|
||||
setSelectedCompanyId: (companyId: string, options?: CompanySelectionOptions) => void;
|
||||
reloadCompanies: () => Promise<void>;
|
||||
createCompany: (data: {
|
||||
name: string;
|
||||
@@ -34,24 +38,8 @@ const CompanyContext = createContext<CompanyContextValue | null>(null);
|
||||
|
||||
export function CompanyProvider({ children }: { children: ReactNode }) {
|
||||
const queryClient = useQueryClient();
|
||||
const [selectedCompanyId, setSelectedCompanyIdState] = useState<string | null>(
|
||||
() => {
|
||||
// Check URL param first (supports "open in new tab" from company rail)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const companyParam = urlParams.get("company");
|
||||
if (companyParam) {
|
||||
localStorage.setItem(STORAGE_KEY, companyParam);
|
||||
// Clean up the URL param
|
||||
urlParams.delete("company");
|
||||
const newSearch = urlParams.toString();
|
||||
const newUrl =
|
||||
window.location.pathname + (newSearch ? `?${newSearch}` : "");
|
||||
window.history.replaceState({}, "", newUrl);
|
||||
return companyParam;
|
||||
}
|
||||
return localStorage.getItem(STORAGE_KEY);
|
||||
}
|
||||
);
|
||||
const [selectionSource, setSelectionSource] = useState<CompanySelectionSource>("bootstrap");
|
||||
const [selectedCompanyId, setSelectedCompanyIdState] = useState<string | null>(() => localStorage.getItem(STORAGE_KEY));
|
||||
|
||||
const { data: companies = [], isLoading, error } = useQuery({
|
||||
queryKey: queryKeys.companies.all,
|
||||
@@ -83,11 +71,13 @@ export function CompanyProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const next = selectableCompanies[0]!.id;
|
||||
setSelectedCompanyIdState(next);
|
||||
setSelectionSource("bootstrap");
|
||||
localStorage.setItem(STORAGE_KEY, next);
|
||||
}, [companies, selectedCompanyId, sidebarCompanies]);
|
||||
|
||||
const setSelectedCompanyId = useCallback((companyId: string) => {
|
||||
const setSelectedCompanyId = useCallback((companyId: string, options?: CompanySelectionOptions) => {
|
||||
setSelectedCompanyIdState(companyId);
|
||||
setSelectionSource(options?.source ?? "manual");
|
||||
localStorage.setItem(STORAGE_KEY, companyId);
|
||||
}, []);
|
||||
|
||||
@@ -121,6 +111,7 @@ export function CompanyProvider({ children }: { children: ReactNode }) {
|
||||
companies,
|
||||
selectedCompanyId,
|
||||
selectedCompany,
|
||||
selectionSource,
|
||||
loading: isLoading,
|
||||
error: error as Error | null,
|
||||
setSelectedCompanyId,
|
||||
@@ -131,6 +122,7 @@ export function CompanyProvider({ children }: { children: ReactNode }) {
|
||||
companies,
|
||||
selectedCompanyId,
|
||||
selectedCompany,
|
||||
selectionSource,
|
||||
isLoading,
|
||||
error,
|
||||
setSelectedCompanyId,
|
||||
|
||||
@@ -125,7 +125,7 @@ function resolveIssueToastContext(
|
||||
}
|
||||
|
||||
const ISSUE_TOAST_ACTIONS = new Set(["issue.created", "issue.updated", "issue.comment_added"]);
|
||||
const AGENT_TOAST_STATUSES = new Set(["running", "idle", "error"]);
|
||||
const AGENT_TOAST_STATUSES = new Set(["running", "error"]);
|
||||
const TERMINAL_RUN_STATUSES = new Set(["succeeded", "failed", "timed_out", "cancelled"]);
|
||||
|
||||
function describeIssueUpdate(details: Record<string, unknown> | null): string | null {
|
||||
@@ -178,6 +178,10 @@ function buildActivityToast(
|
||||
}
|
||||
|
||||
if (action === "issue.updated") {
|
||||
if (details?.reopened === true && readString(details.source) === "comment") {
|
||||
// Reopen-via-comment emits a paired comment event; show one combined toast on the comment event.
|
||||
return null;
|
||||
}
|
||||
const changeDesc = describeIssueUpdate(details);
|
||||
const body = changeDesc
|
||||
? issue.title
|
||||
@@ -197,13 +201,26 @@ function buildActivityToast(
|
||||
|
||||
const commentId = readString(details?.commentId);
|
||||
const bodySnippet = readString(details?.bodySnippet);
|
||||
const reopened = details?.reopened === true;
|
||||
const reopenedFrom = readString(details?.reopenedFrom);
|
||||
const reopenedLabel = reopened
|
||||
? reopenedFrom
|
||||
? `reopened from ${reopenedFrom.replace(/_/g, " ")}`
|
||||
: "reopened"
|
||||
: null;
|
||||
const title = reopened ? `${actor} reopened and commented on ${issue.ref}` : `${actor} commented on ${issue.ref}`;
|
||||
const body = bodySnippet
|
||||
? reopenedLabel
|
||||
? `${reopenedLabel} - ${bodySnippet.replace(/^#+\s*/m, "").replace(/\n/g, " ")}`
|
||||
: bodySnippet.replace(/^#+\s*/m, "").replace(/\n/g, " ")
|
||||
: reopenedLabel
|
||||
? issue.title
|
||||
? `${reopenedLabel} - ${issue.title}`
|
||||
: reopenedLabel
|
||||
: issue.title ?? undefined;
|
||||
return {
|
||||
title: `${actor} commented on ${issue.ref}`,
|
||||
body: bodySnippet
|
||||
? truncate(bodySnippet.replace(/^#+\s*/m, "").replace(/\n/g, " "), 96)
|
||||
: issue.title
|
||||
? truncate(issue.title, 96)
|
||||
: undefined,
|
||||
title,
|
||||
body: body ? truncate(body, 96) : undefined,
|
||||
tone: "info",
|
||||
action: { label: `View ${issue.ref}`, href: issue.href },
|
||||
dedupeKey: `activity:${action}:${entityId}:${commentId ?? "na"}`,
|
||||
@@ -220,14 +237,12 @@ function buildAgentStatusToast(
|
||||
const status = readString(payload.status);
|
||||
if (!agentId || !status || !AGENT_TOAST_STATUSES.has(status)) return null;
|
||||
|
||||
const tone = status === "error" ? "error" : status === "idle" ? "success" : "info";
|
||||
const tone = status === "error" ? "error" : "info";
|
||||
const name = nameOf(agentId) ?? `Agent ${shortId(agentId)}`;
|
||||
const title =
|
||||
status === "running"
|
||||
? `${name} started`
|
||||
: status === "idle"
|
||||
? `${name} is idle`
|
||||
: `${name} errored`;
|
||||
: `${name} errored`;
|
||||
|
||||
const agents = queryClient.getQueryData<Agent[]>(queryKeys.agents.list(companyId));
|
||||
const agent = agents?.find((a) => a.id === agentId);
|
||||
|
||||
Reference in New Issue
Block a user