Skip to content

Commit 633851f

Browse files
V48 Gate 3 (implementation-only): name Obfuscations anchors on save and show path include/exclude counts in the load dropdown
Optional name is drafted in an Anchor popover; path hints and exclusions ride along so dropdown sub-text is clipped body | include icon+count | exclude icon+count on one line, reusing the same icons as the picker headers.
1 parent a717025 commit 633851f

6 files changed

Lines changed: 514 additions & 46 deletions

File tree

uapi/app/deposits/DepositPageClient.tsx

Lines changed: 216 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,17 @@ import DepositSourceSelection, {
2828
import {
2929
buildTerminalExecutionHistoryRequest,
3030
buildTerminalObfuscationsAnchorDraft,
31+
formatObfuscationsAnchorDescription,
3132
mapExecutionHistoryRunToWorkspaceRun,
3233
readTerminalRouteError,
3334
type TerminalActivityRecordDraft,
3435
upsertWorkspaceRun,
3536
} from "@/app/terminal/terminal-activity-history";
37+
import {
38+
DepositExcludePathsIcon,
39+
DepositIncludePathsIcon,
40+
ObfuscationsAnchorDescription,
41+
} from "@/app/deposits/deposit-obfuscations-path-icons";
3642
import type { TerminalRepositoryContextState } from "@/app/terminal/terminal-repository-context";
3743
import {
3844
clearTerminalTransactionId,
@@ -82,6 +88,11 @@ import {
8288
import { TelemetryExplainerTrigger } from "@/components/base/bitcode/execution/TelemetryExplainerTrigger";
8389
import { VCSFileTreePicker } from "@/components/base/bitcode/vcs/VCSFileTreePicker";
8490
import { SearchableSelect } from "@/components/base/bitcode/forms/SearchableSelect";
91+
import {
92+
Popover,
93+
PopoverContent,
94+
PopoverTrigger,
95+
} from "@/components/base/shadcn/popover";
8596
import type {
8697
DepositOptionReviewDecision,
8798
DepositOptionReviewDecisionState,
@@ -147,6 +158,11 @@ export default function DepositPageClient() {
147158
const [repositoryContext, setRepositoryContext] =
148159
useState<TerminalRepositoryContextState | null>(null);
149160
const [obfuscations, setObfuscations] = useState("");
161+
// Optional display name drafted in the Anchor popover (and restored when a
162+
// named anchor is loaded from the activity ledger).
163+
const [obfuscationsAnchorName, setObfuscationsAnchorName] = useState("");
164+
const [isObfuscationsAnchorPopoverOpen, setIsObfuscationsAnchorPopoverOpen] =
165+
useState(false);
150166
// Picked from the repository file tree (selected repo·branch·commit);
151167
// hints and exclusions are mutually exclusive path sets.
152168
const [sourcePathHints, setSourcePathHints] = useState<string[]>([]);
@@ -422,22 +438,55 @@ export default function DepositPageClient() {
422438
}, [liveRuns]);
423439
// V48-Gate3-F13/F18: previously anchored Obfuscations configurations,
424440
// newest first — same derivation pattern as repositoryAnchors above.
441+
// Dedupe by name+text so two differently-named saves of the same body
442+
// both remain selectable.
425443
const obfuscationsAnchors = useMemo(() => {
426444
const seen = new Set<string>();
427-
const anchors: Array<{ id: string; text: string; repositoryFullName: string | null; createdAt: string }> = [];
445+
const anchors: Array<{
446+
id: string;
447+
name: string | null;
448+
text: string;
449+
sourcePathHints: string[];
450+
protectedIpExclusions: string[];
451+
repositoryFullName: string | null;
452+
createdAt: string;
453+
}> = [];
428454
for (const run of [...liveRuns].sort(
429455
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
430456
)) {
431457
if (
432458
run.contextSource !== "deposit-obfuscations-anchor" ||
433-
!run.obfuscationsAnchorText ||
434-
seen.has(run.obfuscationsAnchorText)
459+
!run.obfuscationsAnchorText
435460
)
436461
continue;
437-
seen.add(run.obfuscationsAnchorText);
462+
const name =
463+
typeof run.obfuscationsAnchorName === "string" &&
464+
run.obfuscationsAnchorName.trim()
465+
? run.obfuscationsAnchorName.trim()
466+
: null;
467+
const sourcePathHints = Array.isArray(run.obfuscationsAnchorSourcePathHints)
468+
? run.obfuscationsAnchorSourcePathHints.filter(
469+
(path): path is string =>
470+
typeof path === "string" && path.trim().length > 0,
471+
)
472+
: [];
473+
const protectedIpExclusions = Array.isArray(
474+
run.obfuscationsAnchorProtectedIpExclusions,
475+
)
476+
? run.obfuscationsAnchorProtectedIpExclusions.filter(
477+
(path): path is string =>
478+
typeof path === "string" && path.trim().length > 0,
479+
)
480+
: [];
481+
const dedupeKey = `${name || ""}\u0000${run.obfuscationsAnchorText}\u0000${sourcePathHints.join(",")}\u0000${protectedIpExclusions.join(",")}`;
482+
if (seen.has(dedupeKey)) continue;
483+
seen.add(dedupeKey);
438484
anchors.push({
439485
id: run.id,
486+
name,
440487
text: run.obfuscationsAnchorText,
488+
sourcePathHints,
489+
protectedIpExclusions,
441490
repositoryFullName: run.repository || null,
442491
createdAt: run.created_at,
443492
});
@@ -959,6 +1008,7 @@ export default function DepositPageClient() {
9591008
// V48-Gate3-F13/F18: anchor the CURRENT Obfuscations text into the activity
9601009
// ledger (mirrors handleAnchorRepository in DepositSourceSelection), so it
9611010
// can be reloaded on a later run via the "Load anchor" selector below.
1011+
// Optional display name (from the Anchor popover) labels the dropdown entry.
9621012
const handleAnchorObfuscations = useCallback(async () => {
9631013
if (!obfuscations.trim()) return;
9641014
setIsAnchoringObfuscations(true);
@@ -967,13 +1017,19 @@ export default function DepositPageClient() {
9671017
await handleRecordActivity(
9681018
buildTerminalObfuscationsAnchorDraft({
9691019
obfuscations,
1020+
name: obfuscationsAnchorName,
9701021
repositoryFullName:
9711022
repositoryContext?.selectedRepository?.fullName || null,
1023+
sourcePathHints,
1024+
protectedIpExclusions,
9721025
}),
9731026
);
9741027
setObfuscationsAnchorMessage(
975-
"Obfuscations configuration anchored into the Bitcode activity ledger.",
1028+
obfuscationsAnchorName.trim()
1029+
? `Obfuscations anchor "${obfuscationsAnchorName.trim()}" saved into the Bitcode activity ledger.`
1030+
: "Obfuscations configuration anchored into the Bitcode activity ledger.",
9761031
);
1032+
setIsObfuscationsAnchorPopoverOpen(false);
9771033
} catch (error) {
9781034
setObfuscationsAnchorMessage(
9791035
error instanceof Error
@@ -983,7 +1039,14 @@ export default function DepositPageClient() {
9831039
} finally {
9841040
setIsAnchoringObfuscations(false);
9851041
}
986-
}, [handleRecordActivity, obfuscations, repositoryContext]);
1042+
}, [
1043+
handleRecordActivity,
1044+
obfuscations,
1045+
obfuscationsAnchorName,
1046+
protectedIpExclusions,
1047+
repositoryContext,
1048+
sourcePathHints,
1049+
]);
9871050

9881051
// Real option synthesis via the AssetPacksSynthesis pipeline (deposit
9891052
// lens). The server route builds the exclusion-filtered source inventory,
@@ -1625,25 +1688,51 @@ export default function DepositPageClient() {
16251688
<BitcodeInlineExplainer explainer={DEPOSIT_SECTION_EXPLAINERS.obfuscations} />
16261689
</h2>
16271690
</div>
1628-
<div className="flex items-center gap-2">
1691+
<div className="flex flex-wrap items-center justify-end gap-2">
16291692
{obfuscationsAnchors.length > 0 ? (
1630-
<div className="w-40">
1693+
<div className="w-56">
16311694
<SearchableSelect
16321695
aria-label="Load a previously anchored Obfuscations configuration"
16331696
items={obfuscationsAnchors.map((anchor) => ({
16341697
key: anchor.id,
1635-
label: anchor.repositoryFullName || "Obfuscations anchor",
1636-
description:
1637-
anchor.text.length > 60
1638-
? `${anchor.text.slice(0, 60)}…`
1639-
: anchor.text,
1698+
label:
1699+
anchor.name ||
1700+
anchor.repositoryFullName ||
1701+
"Obfuscations anchor",
1702+
// Sub-text: clipped body | include icon+count | exclude
1703+
// icon+count — same icons as the picker section headers.
1704+
description: (
1705+
<ObfuscationsAnchorDescription
1706+
text={anchor.text}
1707+
sourcePathHints={anchor.sourcePathHints}
1708+
protectedIpExclusions={
1709+
anchor.protectedIpExclusions
1710+
}
1711+
/>
1712+
),
1713+
searchText: [
1714+
anchor.name,
1715+
anchor.repositoryFullName,
1716+
formatObfuscationsAnchorDescription({
1717+
text: anchor.text,
1718+
sourcePathHints: anchor.sourcePathHints,
1719+
protectedIpExclusions:
1720+
anchor.protectedIpExclusions,
1721+
}),
1722+
]
1723+
.filter(Boolean)
1724+
.join(" "),
16401725
}))}
16411726
value={null}
16421727
onSelect={(key) => {
16431728
const anchor = obfuscationsAnchors.find(
16441729
(entry) => entry.id === key,
16451730
);
1646-
if (anchor) setObfuscations(anchor.text);
1731+
if (!anchor) return;
1732+
setObfuscations(anchor.text);
1733+
setObfuscationsAnchorName(anchor.name || "");
1734+
setSourcePathHints(anchor.sourcePathHints);
1735+
setProtectedIpExclusions(anchor.protectedIpExclusions);
16471736
}}
16481737
placeholder="Load anchor..."
16491738
searchPlaceholder="Search anchors..."
@@ -1656,28 +1745,123 @@ export default function DepositPageClient() {
16561745
type="button"
16571746
aria-label="Clear obfuscations"
16581747
title="Clear obfuscations"
1659-
disabled={!obfuscations}
1660-
onClick={() => setObfuscations("")}
1748+
disabled={
1749+
!obfuscations &&
1750+
!obfuscationsAnchorName &&
1751+
sourcePathHints.length === 0 &&
1752+
protectedIpExclusions.length === 0
1753+
}
1754+
onClick={() => {
1755+
setObfuscations("");
1756+
setObfuscationsAnchorName("");
1757+
setSourcePathHints([]);
1758+
setProtectedIpExclusions([]);
1759+
setIsObfuscationsAnchorPopoverOpen(false);
1760+
}}
16611761
className="border border-white/10 px-2.5 py-1.5 text-[0.66rem] uppercase tracking-[0.14em] text-neutral-300 transition hover:border-rose-300/35 hover:text-rose-100 disabled:cursor-not-allowed disabled:opacity-40"
16621762
>
16631763
Clear
16641764
</button>
1665-
<button
1666-
type="button"
1667-
aria-label="Anchor obfuscations to the activity ledger"
1668-
title="Anchor obfuscations to the activity ledger"
1669-
disabled={!obfuscations.trim() || isAnchoringObfuscations}
1670-
onClick={() => {
1671-
void handleAnchorObfuscations();
1765+
<Popover
1766+
open={isObfuscationsAnchorPopoverOpen}
1767+
onOpenChange={(open) => {
1768+
// Require Obfuscations body before opening the name popover.
1769+
if (open && !obfuscations.trim()) return;
1770+
if (isAnchoringObfuscations) return;
1771+
setIsObfuscationsAnchorPopoverOpen(open);
16721772
}}
1673-
className="flex h-9 w-9 items-center justify-center border border-white/10 bg-white/5 text-neutral-200 transition hover:border-emerald-300/35 hover:bg-emerald-300/10 disabled:cursor-not-allowed disabled:opacity-40"
16741773
>
1675-
{isAnchoringObfuscations ? (
1676-
<RefreshCw className="h-4 w-4 animate-spin" />
1677-
) : (
1678-
<Anchor className="h-4 w-4" />
1679-
)}
1680-
</button>
1774+
<PopoverTrigger asChild>
1775+
<button
1776+
type="button"
1777+
aria-label="Anchor obfuscations to the activity ledger"
1778+
title="Anchor obfuscations to the activity ledger"
1779+
disabled={
1780+
!obfuscations.trim() || isAnchoringObfuscations
1781+
}
1782+
className="flex h-9 w-9 items-center justify-center border border-white/10 bg-white/5 text-neutral-200 transition hover:border-emerald-300/35 hover:bg-emerald-300/10 disabled:cursor-not-allowed disabled:opacity-40"
1783+
>
1784+
{isAnchoringObfuscations ? (
1785+
<RefreshCw className="h-4 w-4 animate-spin" />
1786+
) : (
1787+
<Anchor className="h-4 w-4" />
1788+
)}
1789+
</button>
1790+
</PopoverTrigger>
1791+
<PopoverContent
1792+
align="end"
1793+
sideOffset={6}
1794+
className="w-64 border-white/10 bg-neutral-950 p-3 text-neutral-100 shadow-xl"
1795+
>
1796+
<p className="text-[0.62rem] uppercase tracking-[0.14em] text-neutral-500">
1797+
Name this anchor
1798+
</p>
1799+
<input
1800+
id="deposit-obfuscations-anchor-name"
1801+
type="text"
1802+
value={obfuscationsAnchorName}
1803+
onChange={(event) =>
1804+
setObfuscationsAnchorName(
1805+
event.target.value.slice(0, 80),
1806+
)
1807+
}
1808+
onKeyDown={(event) => {
1809+
if (event.key === "Enter") {
1810+
event.preventDefault();
1811+
void handleAnchorObfuscations();
1812+
}
1813+
if (event.key === "Escape") {
1814+
event.preventDefault();
1815+
setIsObfuscationsAnchorPopoverOpen(false);
1816+
}
1817+
}}
1818+
placeholder="Optional name"
1819+
maxLength={80}
1820+
autoFocus
1821+
aria-label="Obfuscations anchor name"
1822+
className="mt-2 h-9 w-full border border-white/10 bg-black/40 px-2.5 text-xs text-neutral-100 outline-none transition placeholder:text-neutral-500 focus:border-emerald-300/35"
1823+
/>
1824+
<p className="mt-1.5 text-[0.68rem] leading-4 text-neutral-500">
1825+
Shown as the label when reloading. Leave blank to use
1826+
the repository name.
1827+
</p>
1828+
<div className="mt-3 flex items-center justify-end gap-2">
1829+
<button
1830+
type="button"
1831+
onClick={() =>
1832+
setIsObfuscationsAnchorPopoverOpen(false)
1833+
}
1834+
disabled={isAnchoringObfuscations}
1835+
className="border border-white/10 px-2.5 py-1.5 text-[0.62rem] uppercase tracking-[0.14em] text-neutral-300 transition hover:border-white/25 disabled:opacity-40"
1836+
>
1837+
Cancel
1838+
</button>
1839+
<button
1840+
type="button"
1841+
onClick={() => {
1842+
void handleAnchorObfuscations();
1843+
}}
1844+
disabled={
1845+
!obfuscations.trim() || isAnchoringObfuscations
1846+
}
1847+
className="inline-flex items-center gap-1.5 border border-emerald-300/30 bg-emerald-300/12 px-2.5 py-1.5 text-[0.62rem] uppercase tracking-[0.14em] text-emerald-100 transition hover:border-emerald-200/45 hover:bg-emerald-300/18 disabled:cursor-not-allowed disabled:opacity-40"
1848+
>
1849+
{isAnchoringObfuscations ? (
1850+
<RefreshCw
1851+
className="h-3 w-3 animate-spin"
1852+
aria-hidden="true"
1853+
/>
1854+
) : (
1855+
<Anchor
1856+
className="h-3 w-3"
1857+
aria-hidden="true"
1858+
/>
1859+
)}
1860+
Save anchor
1861+
</button>
1862+
</div>
1863+
</PopoverContent>
1864+
</Popover>
16811865
<Sparkles
16821866
className="h-5 w-5 text-emerald-200"
16831867
aria-hidden="true"
@@ -1713,6 +1897,7 @@ export default function DepositPageClient() {
17131897
<div className="mt-4 grid gap-4 tablet:grid-cols-2">
17141898
<div className="block">
17151899
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.16em] text-neutral-500">
1900+
<DepositIncludePathsIcon />
17161901
<span>Source path hints</span>
17171902
<span onClick={(event) => event.stopPropagation()}>
17181903
<BitcodeInlineExplainer explainer={DEPOSIT_SECTION_EXPLAINERS.sourcePathHints} triggerAriaLabel="More info about this field" />
@@ -1739,6 +1924,7 @@ export default function DepositPageClient() {
17391924
</div>
17401925
<div className="block">
17411926
<span className="flex items-center gap-2 text-[0.62rem] uppercase tracking-[0.16em] text-neutral-500">
1927+
<DepositExcludePathsIcon />
17421928
<span>Protected IP exclusions</span>
17431929
<span onClick={(event) => event.stopPropagation()}>
17441930
<BitcodeInlineExplainer explainer={DEPOSIT_SECTION_EXPLAINERS.protectedIpExclusions} triggerAriaLabel="More info about this field" />

0 commit comments

Comments
 (0)