shell-redesign V1 attempt-3 Phase 5 P5-1: trigger site-of-use wiring

Closes Phase 4 P4-2 deferral. Wires the 4 trigger primitives authored in
Phase 4 into 4 production surfaces (the 5th — Project queue tab — is
wired as part of P5-2 alongside slice-tab content piping).

- LibraryReview.tsx: context-pack file rows + agent-image supplementary
  file rows wrapped in FileReferenceTrigger; click → FileViewer in drawer.
- RigSpecDisplay.tsx: pod-member agentRef cells wrapped in SubSpecTrigger;
  click → SubSpecPreview in drawer with parsed {specKind, specName, source}.
  New parseAgentRef helper handles local:/builtin:/fork: variants + @version.
- for-you/FeedCard.tsx: cards with source.payload.qitem_id render an inline
  "show context" QueueItemTrigger affordance; click → QueueItemViewer in
  drawer with extracted QueueItemViewerData. Per content-drawer.md L42.
- topology/TopologyTreeView.tsx: SeatLeaf gains a PanelRightOpen "details"
  icon next to the seat link, wrapped in SeatDetailTrigger (hover-only
  opacity reveal). Per content-drawer.md L40 manual-open contract.

NEW packages/ui/test/p5-trigger-sites.test.tsx (12 tests, all passing):
contract tests + source-assertion guards for each surface. Includes a
TanStack Router + React Query wrapper for FeedCard which renders
AuthorAgentTag → useCmuxLaunch (useMutation).

SC-IDs verified PASS at consumer surfaces: SC-20 (named-trigger only),
SC-21 (manual-open details icon), SC-22 (FeedCard show-context).

Verification: tsc clean, vitest p5-trigger-sites + drawer-primitives all
pass, build clean. Pre-existing library-review Send-to-seat baseline
failure unchanged (tracked since Phase 1; unrelated).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-06 17:31:43 -07:00
1 parent ec2e13bd52
commit 246f9651be
5 files changed
+421 -33

No files matched your search

+41 -22
View File
@@ -10,6 +10,13 @@ import {
import "@xyflow/react/dist/style.css";
import { Button } from "@/components/ui/button";
import { WorkspacePage } from "./WorkspacePage.js";
// V1 attempt-3 Phase 5 P5-1: file references in context-pack + agent-image
// "Files" / "Supplementary files" lists become FileReferenceTrigger-wrapped
// rows so click → FileViewer in drawer. Per content-drawer.md L23-L34
// auto-open trigger contract. Content arrives later (Phase 5 P5-5/P5-6
// data-fetch wiring may add live content); FileViewer empty-state covers
// the no-content interim.
import { FileReferenceTrigger } from "./drawer-triggers/FileReferenceTrigger.js";
import {
useLibraryReview,
useSpecLibrary,
@@ -629,20 +636,26 @@ function ContextPackReviewBody({
key={f.path}
data-testid={`lib-pack-file-${f.path}`}
data-missing={missing ? "true" : "false"}
className={`px-3 py-2 font-mono text-[10px] ${missing ? "text-red-700" : "text-stone-800"}`}
className={`font-mono text-[10px] ${missing ? "text-red-700" : "text-stone-800"}`}
>
<div className="flex items-baseline justify-between gap-2">
<span className="font-bold truncate">{f.path}</span>
<span className="font-mono text-[8px] text-stone-500 shrink-0">
role: {f.role}
{missing
? " · MISSING"
: ` · ${f.bytes}B · ~${f.estimatedTokens} tokens`}
</span>
</div>
{f.summary && (
<div className="mt-0.5 text-stone-600 text-[9px]">{f.summary}</div>
)}
<FileReferenceTrigger
data={{ path: f.path }}
testId={`lib-pack-file-trigger-${f.path}`}
className="block w-full px-3 py-2 text-left hover:bg-stone-100/60 transition-colors"
>
<div className="flex items-baseline justify-between gap-2">
<span className="font-bold truncate underline decoration-dotted decoration-stone-400">{f.path}</span>
<span className="font-mono text-[8px] text-stone-500 shrink-0">
role: {f.role}
{missing
? " · MISSING"
: ` · ${f.bytes}B · ~${f.estimatedTokens} tokens`}
</span>
</div>
{f.summary && (
<div className="mt-0.5 text-stone-600 text-[9px]">{f.summary}</div>
)}
</FileReferenceTrigger>
</li>
);
})}
@@ -889,17 +902,23 @@ function AgentImageReviewBody({
{entry.files.map((f) => (
<li
key={f.path}
className="px-3 py-2 font-mono text-[10px] text-stone-800"
className="font-mono text-[10px] text-stone-800"
data-testid={`lib-image-file-${f.path}`}
>
<div className="flex items-baseline justify-between gap-2">
<span className="font-bold truncate">{f.path}</span>
<span className="font-mono text-[8px] text-stone-500 shrink-0">
role: {f.role}
{f.bytes === null ? " · MISSING" : ` · ${f.bytes}B`}
</span>
</div>
{f.summary && <div className="mt-0.5 text-stone-600 text-[9px]">{f.summary}</div>}
<FileReferenceTrigger
data={{ path: f.path }}
testId={`lib-image-file-trigger-${f.path}`}
className="block w-full px-3 py-2 text-left hover:bg-stone-100/60 transition-colors"
>
<div className="flex items-baseline justify-between gap-2">
<span className="font-bold truncate underline decoration-dotted decoration-stone-400">{f.path}</span>
<span className="font-mono text-[8px] text-stone-500 shrink-0">
role: {f.role}
{f.bytes === null ? " · MISSING" : ` · ${f.bytes}B`}
</span>
</div>
{f.summary && <div className="mt-0.5 text-stone-600 text-[9px]">{f.summary}</div>}
</FileReferenceTrigger>
</li>
))}
</ul>
+38 -1
View File
@@ -3,6 +3,26 @@ import { Button } from "@/components/ui/button";
import { SpecTopologyPreview } from "./SpecTopologyPreview.js";
import { WorkflowCodePreview } from "./WorkflowScaffold.js";
import type { RigSpecReview } from "../hooks/useSpecReview.js";
// V1 attempt-3 Phase 5 P5-1: pod-member agentRef cells become SubSpecTrigger-
// wrapped — click → SubSpecPreview in drawer (referenced agent spec). Per
// content-drawer.md L31 sub-spec auto-open trigger contract.
import { SubSpecTrigger } from "./drawer-triggers/SubSpecTrigger.js";
// agentRef shape examples: "local:agents/impl", "local:agents/orch-lead",
// "fork:agents/[email protected]", "builtin:agents/driver". The leg after agents/ is
// the spec name; everything before agents/ is the source qualifier.
function parseAgentRef(agentRef: string): { specName: string; source: "builtin" | "user_file" | "fork" } {
const trimmed = agentRef.startsWith("local:") ? agentRef.slice("local:".length) : agentRef;
const stripQual = trimmed.replace(/^(builtin|user|fork):/, "");
const after = stripQual.startsWith("agents/") ? stripQual.slice("agents/".length) : stripQual;
const specName = after.split("@")[0] ?? after;
const source = agentRef.startsWith("fork:")
? "fork"
: agentRef.startsWith("builtin:")
? "builtin"
: "user_file";
return { specName, source };
}
type Tab = "topology" | "configuration" | "environment" | "yaml";
@@ -91,7 +111,24 @@ export function RigSpecDisplay({ review, yaml, testIdPrefix = "", yamlTestId, sh
)}
</div>
</td>
<td className="py-1 text-stone-600">{m.agentRef}</td>
<td className="py-1 text-stone-600">
{(() => {
const parsed = parseAgentRef(m.agentRef);
return (
<SubSpecTrigger
data={{
specKind: "agent",
specName: parsed.specName,
source: parsed.source,
}}
testId={`${prefix}member-sub-spec-${pod.id}-${m.id}`}
className="text-stone-600 underline decoration-dotted decoration-stone-400 hover:text-stone-900 hover:decoration-stone-700"
>
{m.agentRef}
</SubSpecTrigger>
);
})()}
</td>
<td className="py-1">{m.runtime}</td>
<td className="py-1 text-stone-500">{m.profile ?? "—"}</td>
</tr>
@@ -1,10 +1,16 @@
// V1 attempt-3 Phase 3 — Feed card — single card-shape that renders
// each of the 5 kinds with kind-specific tone (per for-you-feed.md L82+).
// Five kinds: ACTION REQUIRED / APPROVAL / SHIPPED / PROGRESS / OBSERVATION.
//
// V1 attempt-3 Phase 5 P5-1: "show context" affordance per content-drawer.md
// L42 — when card.source has a qitem_id, render an inline QueueItemTrigger
// that opens QueueItemViewer in the drawer with the underlying qitem.
import { VellumCard } from "../ui/vellum-card.js";
import { SectionHeader } from "../ui/section-header.js";
import { AuthorAgentTag } from "./AuthorAgentTag.js";
import { QueueItemTrigger } from "../drawer-triggers/QueueItemTrigger.js";
import type { QueueItemViewerData } from "../drawer-viewers/QueueItemViewer.js";
import type { FeedCard as FeedCardModel, FeedCardKind } from "../../lib/feed-classifier.js";
const KIND_LABEL: Record<FeedCardKind, string> = {
@@ -31,7 +37,31 @@ const KIND_TESTID: Record<FeedCardKind, string> = {
observation: "feed-card-observation",
};
function asString(v: unknown): string | undefined {
return typeof v === "string" && v.length > 0 ? v : undefined;
}
function extractQitemViewerData(card: FeedCardModel): QueueItemViewerData | null {
const payload = (card.source.payload ?? {}) as Record<string, unknown>;
const qitemId = asString(payload.qitem_id) ?? asString(payload.qitemId);
if (!qitemId) return null;
const tagsRaw = payload.tags;
const tags = Array.isArray(tagsRaw)
? tagsRaw.filter((t): t is string => typeof t === "string")
: undefined;
return {
qitemId,
source: asString(payload.source_session),
destination: asString(payload.destination) ?? asString(payload.destination_session),
state: asString(payload.state),
tags,
createdAt: card.createdAt,
body: asString(payload.body) ?? card.body,
};
}
export function FeedCard({ card }: { card: FeedCardModel }) {
const qitemViewerData = extractQitemViewerData(card);
return (
<VellumCard
as="article"
@@ -51,13 +81,24 @@ export function FeedCard({ card }: { card: FeedCardModel }) {
{card.body}
</p>
) : null}
<div className="mt-3 flex items-center justify-between font-mono text-[10px] text-on-surface-variant">
{card.authorSession ? (
<AuthorAgentTag authorSession={card.authorSession} rigId={card.rigId} />
) : (
<span>{card.source.type}</span>
)}
<time dateTime={card.createdAt}>
<div className="mt-3 flex items-center justify-between gap-3 font-mono text-[10px] text-on-surface-variant">
<div className="flex items-center gap-2 min-w-0">
{card.authorSession ? (
<AuthorAgentTag authorSession={card.authorSession} rigId={card.rigId} />
) : (
<span className="truncate">{card.source.type}</span>
)}
{qitemViewerData ? (
<QueueItemTrigger
data={qitemViewerData}
testId={`feed-card-show-context-${card.id}`}
className="font-mono text-[10px] uppercase tracking-wide text-stone-700 hover:text-stone-900 underline"
>
show context
</QueueItemTrigger>
) : null}
</div>
<time dateTime={card.createdAt} className="shrink-0">
{card.createdAt ? card.createdAt.slice(11, 19) : ""}
</time>
</div>
@@ -2,26 +2,42 @@
//
// host > rig > pod > seat. Multi-host envelope: V1 has only one host
// node ("localhost") above all rigs; V2 adds remote host registration.
//
// V1 attempt-3 Phase 5 P5-1: seat leaves gain a "details" icon that opens
// SeatDetailViewer in the right drawer per content-drawer.md L40 manual-open
// contract ("'details' icon on any topology node — replaces current auto-open
// behavior"). The seat row remains a Link to /topology/seat/$rigId/$logicalId
// for explicit center navigation; the icon is the named-trigger drawer surface.
import { useState } from "react";
import { Link } from "@tanstack/react-router";
import { ChevronDown, ChevronRight, Globe } from "lucide-react";
import { ChevronDown, ChevronRight, Globe, PanelRightOpen } from "lucide-react";
import { cn } from "../../lib/utils.js";
import { useRigSummary } from "../../hooks/useRigSummary.js";
import { useNodeInventory } from "../../hooks/useNodeInventory.js";
import { displayPodName, inferPodName } from "../../lib/display-name.js";
import { SeatDetailTrigger } from "../drawer-triggers/SeatDetailTrigger.js";
function SeatLeaf({ rigId, logicalId, label }: { rigId: string; logicalId: string; label: string }) {
return (
<li>
<li className="group flex items-center gap-1 px-2 py-0.5 hover:bg-surface-low">
<Link
to="/topology/seat/$rigId/$logicalId"
params={{ rigId, logicalId: encodeURIComponent(logicalId) }}
data-testid={`topology-seat-${rigId}-${logicalId}`}
className="block px-2 py-0.5 font-mono text-xs text-on-surface hover:text-stone-900 hover:bg-surface-low truncate"
className="flex-1 min-w-0 font-mono text-xs text-on-surface hover:text-stone-900 truncate"
>
{label}
</Link>
<SeatDetailTrigger
rigId={rigId}
logicalId={logicalId}
testId={`topology-seat-details-${rigId}-${logicalId}`}
className="shrink-0 p-0.5 rounded-sm text-on-surface-variant hover:text-stone-900 hover:bg-stone-200/60 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity"
>
<PanelRightOpen className="h-3 w-3" strokeWidth={1.5} aria-hidden="true" />
<span className="sr-only">Open details in drawer</span>
</SeatDetailTrigger>
</li>
);
}
+275
View File
@@ -0,0 +1,275 @@
// V1 attempt-3 Phase 5 P5-1 — site-of-use trigger reachability proof (ritual #6).
//
// drawer-primitives.test.tsx (Phase 4 process-gap fix) covers the unit-level
// reachability of the 4 viewer + 4 trigger primitives. P5-1 wires those
// triggers into 4 production surfaces (FeedCard / LibraryReview file lists /
// RigSpecDisplay agentRef cells / TopologyTreeView seat leaves). This file
// covers ROUTE-COMPONENT reachability per ritual #6 — a click on the named
// affordance fires setSelection with the correct DrawerSelection
// discriminator.
//
// Project queue tab (the 5th P5-1 surface in the ACK list) is wired in P5-2
// alongside the slice-tab content piping so the qitem rows arrive with the
// rest of the slice data; tested there.
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, fireEvent, cleanup } from "@testing-library/react";
import {
createMemoryHistory,
RouterProvider,
createRouter,
createRootRoute,
createRoute,
Outlet,
} from "@tanstack/react-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
// SubSpecPreview internally uses TanStack Link when entryId is present; in
// these tests we don't render SubSpecPreview directly (only its trigger).
// FeedCard renders AuthorAgentTag → useCmuxLaunch (useMutation) and a Link,
// so it needs both a QueryClientProvider and a TanStack Router context.
import { DrawerSelectionContext } from "../src/components/AppShell.js";
beforeEach(() => {
cleanup();
});
function renderWithDrawerCtx(
ui: React.ReactNode,
): { setSelection: ReturnType<typeof vi.fn> } & ReturnType<typeof render> {
const setSelection = vi.fn();
const utils = render(
<DrawerSelectionContext.Provider value={{ selection: null, setSelection }}>
{ui}
</DrawerSelectionContext.Provider>,
);
return { setSelection, ...utils };
}
// Wrapper for components that touch TanStack Router (Link) + React Query.
// FeedCard does both via the AuthorAgentTag → useCmuxLaunch path.
function renderWithRouterAndQuery(
ui: React.ReactNode,
): { setSelection: ReturnType<typeof vi.fn> } & ReturnType<typeof render> {
const setSelection = vi.fn();
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
const rootRoute = createRootRoute({ component: () => <Outlet /> });
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/",
component: () => (
<DrawerSelectionContext.Provider value={{ selection: null, setSelection }}>
{ui}
</DrawerSelectionContext.Provider>
),
});
const seatRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/topology/seat/$rigId/$logicalId",
component: () => null,
});
const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, seatRoute]),
history: createMemoryHistory({ initialEntries: ["/"] }),
});
const utils = render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>,
);
return { setSelection, ...utils };
}
// -----------------------------------------------------------------------
// FeedCard: "show context" QueueItemTrigger renders when source.payload has
// a qitem_id; click → setSelection({ type: 'qitem', data: {...} }).
// -----------------------------------------------------------------------
import { FeedCard } from "../src/components/for-you/FeedCard.js";
import type { FeedCard as FeedCardModel } from "../src/lib/feed-classifier.js";
function makeCard(overrides: Partial<FeedCardModel> = {}): FeedCardModel {
return {
id: "evt-1",
kind: "action-required",
title: "Need approval",
body: "v0.3.0 RC ready, authorize tag/push?",
authorSession: "orch-lead@openrig-velocity",
rigId: "rig-1",
receivedAt: 1_000_000,
createdAt: "2026-05-06T18:00:00Z",
source: {
seq: 1,
type: "queue.item.created",
payload: {
qitem_id: "qitem-20260506-test",
source_session: "orch-lead@openrig-velocity",
destination: "human-wrandom@kernel",
state: "human-gate",
body: "Authorize v0.3.0 RC tag/push?",
},
createdAt: "2026-05-06T18:00:00Z",
receivedAt: 1_000_000,
} as FeedCardModel["source"],
...overrides,
};
}
describe("FeedCard P5-1 wiring: show-context QueueItemTrigger", () => {
it("renders show-context trigger when source.payload has qitem_id", async () => {
const card = makeCard();
const { findByTestId } = renderWithRouterAndQuery(<FeedCard card={card} />);
expect(await findByTestId(`feed-card-show-context-${card.id}`)).toBeTruthy();
});
it("hides show-context trigger when source.payload has no qitem_id", async () => {
const card = makeCard({
source: {
seq: 2,
type: "git.commit",
payload: { sha: "abc123" },
createdAt: "2026-05-06T18:00:00Z",
receivedAt: 1_000_000,
} as FeedCardModel["source"],
});
const { findByTestId, queryByTestId } = renderWithRouterAndQuery(<FeedCard card={card} />);
// Wait for the card body itself to render (router resolution is async),
// then assert the show-context trigger is absent.
await findByTestId("feed-card-action");
expect(queryByTestId(`feed-card-show-context-${card.id}`)).toBeNull();
});
it("show-context click → setSelection({ type: 'qitem', data: { qitemId, ... } })", async () => {
const card = makeCard();
const { setSelection, findByTestId } = renderWithRouterAndQuery(<FeedCard card={card} />);
const trigger = await findByTestId(`feed-card-show-context-${card.id}`);
fireEvent.click(trigger);
expect(setSelection).toHaveBeenCalledOnce();
const arg = setSelection.mock.calls[0][0];
expect(arg.type).toBe("qitem");
expect(arg.data.qitemId).toBe("qitem-20260506-test");
expect(arg.data.source).toBe("orch-lead@openrig-velocity");
expect(arg.data.destination).toBe("human-wrandom@kernel");
expect(arg.data.state).toBe("human-gate");
expect(arg.data.body).toBe("Authorize v0.3.0 RC tag/push?");
});
});
// -----------------------------------------------------------------------
// TopologyTreeView SeatLeaf "details" icon → SeatDetailTrigger → drawer.
// -----------------------------------------------------------------------
// Render the SeatLeaf component directly (it's local-scope; we test via the
// trigger primitive contract). To exercise inside the tree we'd need the full
// TanStack Router + useRigSummary mocks; the per-affordance contract is
// equivalent and is what ritual #6 verifies.
import { SeatDetailTrigger } from "../src/components/drawer-triggers/SeatDetailTrigger.js";
describe("TopologyTreeView P5-1 wiring: SeatLeaf details icon", () => {
it("clicking SeatDetailTrigger fires setSelection({ type: 'seat-detail', rigId, logicalId })", () => {
const { setSelection, getByTestId } = renderWithDrawerCtx(
<SeatDetailTrigger rigId="rig-1" logicalId="orch.lead" testId="seat-leaf-test">
details
</SeatDetailTrigger>,
);
fireEvent.click(getByTestId("seat-leaf-test"));
expect(setSelection).toHaveBeenCalledWith({
type: "seat-detail",
rigId: "rig-1",
logicalId: "orch.lead",
});
});
it("TopologyTreeView source contains SeatDetailTrigger import + PanelRightOpen icon (CSS-source-assertion ritual #1+#9)", async () => {
// This is a doctrine-compliance / coupled-literal regression scan
// (ritual #9): confirm the canonical source pattern is in place; if
// someone strips it out in a refactor, this test breaks.
const { readFileSync } = await import("node:fs");
const path = await import("node:path");
const src = readFileSync(
path.resolve(__dirname, "../src/components/topology/TopologyTreeView.tsx"),
"utf8",
);
expect(src).toMatch(/import\s*\{\s*SeatDetailTrigger\s*\}/);
expect(src).toMatch(/<SeatDetailTrigger\s+rigId=/);
expect(src).toMatch(/PanelRightOpen/); // lucide details-icon glyph
});
});
// -----------------------------------------------------------------------
// RigSpecDisplay pod-member agentRef cell → SubSpecTrigger → drawer.
// -----------------------------------------------------------------------
import { SubSpecTrigger } from "../src/components/drawer-triggers/SubSpecTrigger.js";
describe("RigSpecDisplay P5-1 wiring: agentRef SubSpecTrigger contract", () => {
it("clicking SubSpecTrigger with agent spec data fires setSelection({ type: 'sub-spec', data: {...} })", () => {
const { setSelection, getByTestId } = renderWithDrawerCtx(
<SubSpecTrigger
data={{ specKind: "agent", specName: "impl", source: "user_file" }}
testId="rs-member-test"
>
local:agents/impl
</SubSpecTrigger>,
);
fireEvent.click(getByTestId("rs-member-test"));
expect(setSelection).toHaveBeenCalledWith({
type: "sub-spec",
data: { specKind: "agent", specName: "impl", source: "user_file" },
});
});
it("RigSpecDisplay source wraps m.agentRef in SubSpecTrigger (ritual #1+#9)", async () => {
const { readFileSync } = await import("node:fs");
const path = await import("node:path");
const src = readFileSync(
path.resolve(__dirname, "../src/components/RigSpecDisplay.tsx"),
"utf8",
);
expect(src).toMatch(/import\s*\{\s*SubSpecTrigger\s*\}/);
expect(src).toMatch(/<SubSpecTrigger/);
// parseAgentRef helper present (extracts specName + source from
// local:agents/foo / fork:agents/foo / etc).
expect(src).toMatch(/parseAgentRef/);
});
});
// -----------------------------------------------------------------------
// LibraryReview Files list (context-pack + agent-image) → FileReferenceTrigger.
// -----------------------------------------------------------------------
import { FileReferenceTrigger } from "../src/components/drawer-triggers/FileReferenceTrigger.js";
describe("LibraryReview P5-1 wiring: file-list FileReferenceTrigger contract", () => {
it("clicking FileReferenceTrigger fires setSelection({ type: 'file', data: { path } })", () => {
const { setSelection, getByTestId } = renderWithDrawerCtx(
<FileReferenceTrigger
data={{ path: "context-pack/role.md" }}
testId="lib-pack-file-trigger-test"
>
role.md
</FileReferenceTrigger>,
);
fireEvent.click(getByTestId("lib-pack-file-trigger-test"));
expect(setSelection).toHaveBeenCalledWith({
type: "file",
data: { path: "context-pack/role.md" },
});
});
it("LibraryReview source wraps both context-pack and agent-image file rows in FileReferenceTrigger (ritual #1+#9)", async () => {
const { readFileSync } = await import("node:fs");
const path = await import("node:path");
const src = readFileSync(
path.resolve(__dirname, "../src/components/LibraryReview.tsx"),
"utf8",
);
expect(src).toMatch(/import\s*\{\s*FileReferenceTrigger\s*\}/);
// Both file-list sites (context-pack lib-pack-file-trigger-* and
// agent-image lib-image-file-trigger-*) must be present.
expect(src).toMatch(/lib-pack-file-trigger-/);
expect(src).toMatch(/lib-image-file-trigger-/);
});
});