refactor(housekeeping): adopt ase route namespace
This commit is contained in:
1 parent
74756dfed2
commit
f72a2b6c74
32 files changed
+388
-264
No files matched your search
@@ -35,8 +35,11 @@ export default async function AdminNextDomainLayout({
|
|||||||
if (!satisfiesCapability(context, activeDomain.capability)) notFound();
|
if (!satisfiesCapability(context, activeDomain.capability)) notFound();
|
||||||
|
|
||||||
const translate = await getTranslations("pages.housekeeping");
|
const translate = await getTranslations("pages.housekeeping");
|
||||||
const navigation = buildHousekeepingNavigation(registry, context, (key) =>
|
const navigation = buildHousekeepingNavigation(
|
||||||
translate(namespaceKey(key) as never),
|
registry,
|
||||||
|
context,
|
||||||
|
"preview",
|
||||||
|
(key) => translate(namespaceKey(key) as never),
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
File renamed without changes.
File renamed without changes.
@@ -1,6 +1,7 @@
|
|||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { satisfiesCapability } from "@/features/housekeeping/foundation/capability-context";
|
import { satisfiesCapability } from "@/features/housekeeping/foundation/capability-context";
|
||||||
import { createHousekeepingRegistry } from "@/features/housekeeping/foundation/registry";
|
import { createHousekeepingRegistry } from "@/features/housekeeping/foundation/registry";
|
||||||
|
import { toHousekeepingHref } from "@/features/housekeeping/foundation/routing/href";
|
||||||
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
|
import { getHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/server-capability-context";
|
||||||
import { HOUSEKEEPING_MANIFESTS } from "@/features/housekeeping/manifests";
|
import { HOUSEKEEPING_MANIFESTS } from "@/features/housekeeping/manifests";
|
||||||
|
|
||||||
@@ -13,5 +14,5 @@ export default async function AdminNextPage() {
|
|||||||
|
|
||||||
if (!firstVisibleDomain) notFound();
|
if (!firstVisibleDomain) notFound();
|
||||||
|
|
||||||
redirect(firstVisibleDomain.previewHref);
|
redirect(toHousekeepingHref(firstVisibleDomain.canonicalHref, "preview"));
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ export const contentManifest = {
|
|||||||
labelKey: "pages.housekeeping.domains.content.title",
|
labelKey: "pages.housekeeping.domains.content.title",
|
||||||
descriptionKey: "pages.housekeeping.domains.content.description",
|
descriptionKey: "pages.housekeeping.domains.content.description",
|
||||||
iconId: "file-text",
|
iconId: "file-text",
|
||||||
previewHref: "/admin-next/content",
|
canonicalHref: "/ase/content",
|
||||||
capability: anyCapability(
|
capability: anyCapability(
|
||||||
PERMS.NEWS_VIEW,
|
PERMS.NEWS_VIEW,
|
||||||
PERMS.PAGES_VIEW,
|
PERMS.PAGES_VIEW,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const economyManifest = {
|
|||||||
labelKey: "pages.housekeeping.domains.economy.title",
|
labelKey: "pages.housekeeping.domains.economy.title",
|
||||||
descriptionKey: "pages.housekeeping.domains.economy.description",
|
descriptionKey: "pages.housekeeping.domains.economy.description",
|
||||||
iconId: "gem",
|
iconId: "gem",
|
||||||
previewHref: "/admin-next/economy",
|
canonicalHref: "/ase/economy",
|
||||||
capability: anyCapability(
|
capability: anyCapability(
|
||||||
PERMS.CATALOG_VIEW,
|
PERMS.CATALOG_VIEW,
|
||||||
PERMS.SHOP_VIEW,
|
PERMS.SHOP_VIEW,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const hotelManifest = {
|
|||||||
labelKey: "pages.housekeeping.domains.hotel.title",
|
labelKey: "pages.housekeeping.domains.hotel.title",
|
||||||
descriptionKey: "pages.housekeeping.domains.hotel.description",
|
descriptionKey: "pages.housekeeping.domains.hotel.description",
|
||||||
iconId: "hotel",
|
iconId: "hotel",
|
||||||
previewHref: "/admin-next/hotel",
|
canonicalHref: "/ase/hotel",
|
||||||
capability: anyCapability(
|
capability: anyCapability(
|
||||||
PERMS.ROOMS_VIEW,
|
PERMS.ROOMS_VIEW,
|
||||||
PERMS.RADIO_VIEW,
|
PERMS.RADIO_VIEW,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const operationsManifest = {
|
|||||||
labelKey: "pages.housekeeping.domains.operations.title",
|
labelKey: "pages.housekeeping.domains.operations.title",
|
||||||
descriptionKey: "pages.housekeeping.domains.operations.description",
|
descriptionKey: "pages.housekeeping.domains.operations.description",
|
||||||
iconId: "inbox",
|
iconId: "inbox",
|
||||||
previewHref: "/admin-next/operations",
|
canonicalHref: "/ase",
|
||||||
capability: anyCapability(PERMS.ADMIN_DASHBOARD),
|
capability: anyCapability(PERMS.ADMIN_DASHBOARD),
|
||||||
routes: [],
|
routes: [],
|
||||||
searchProviders: [],
|
searchProviders: [],
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const peopleManifest = {
|
|||||||
labelKey: "pages.housekeeping.domains.people.title",
|
labelKey: "pages.housekeeping.domains.people.title",
|
||||||
descriptionKey: "pages.housekeeping.domains.people.description",
|
descriptionKey: "pages.housekeeping.domains.people.description",
|
||||||
iconId: "users",
|
iconId: "users",
|
||||||
previewHref: "/admin-next/people",
|
canonicalHref: "/ase/people",
|
||||||
capability: anyCapability(
|
capability: anyCapability(
|
||||||
PERMS.USERS_VIEW,
|
PERMS.USERS_VIEW,
|
||||||
PERMS.MODERATION_VIEW,
|
PERMS.MODERATION_VIEW,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const systemManifest = {
|
|||||||
labelKey: "pages.housekeeping.domains.system.title",
|
labelKey: "pages.housekeeping.domains.system.title",
|
||||||
descriptionKey: "pages.housekeeping.domains.system.description",
|
descriptionKey: "pages.housekeeping.domains.system.description",
|
||||||
iconId: "settings",
|
iconId: "settings",
|
||||||
previewHref: "/admin-next/system",
|
canonicalHref: "/ase/system",
|
||||||
capability: anyCapability(
|
capability: anyCapability(
|
||||||
PERMS.SETTINGS_VIEW,
|
PERMS.SETTINGS_VIEW,
|
||||||
PERMS.LOGS_VIEW,
|
PERMS.LOGS_VIEW,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const workItem = {
|
|||||||
occurredAt: "2026-08-24T12:00:00.000Z",
|
occurredAt: "2026-08-24T12:00:00.000Z",
|
||||||
titleKey: "pages.housekeeping.items.ticket",
|
titleKey: "pages.housekeeping.items.ticket",
|
||||||
context: { ticketId: "42" },
|
context: { ticketId: "42" },
|
||||||
href: "/admin-next/people/tickets/42",
|
href: "/ase-next/people/tickets/42",
|
||||||
freshness: "fresh",
|
freshness: "fresh",
|
||||||
} satisfies HousekeepingWorkItem;
|
} satisfies HousekeepingWorkItem;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ const searchResult = {
|
|||||||
id: "user-42",
|
id: "user-42",
|
||||||
domain: "people",
|
domain: "people",
|
||||||
title: "operator",
|
title: "operator",
|
||||||
href: "/admin-next/people/users/42",
|
href: "/ase-next/people/users/42",
|
||||||
} satisfies HousekeepingSearchResult;
|
} satisfies HousekeepingSearchResult;
|
||||||
|
|
||||||
const searchProvider: HousekeepingSearchProvider = {
|
const searchProvider: HousekeepingSearchProvider = {
|
||||||
@@ -87,7 +87,7 @@ const manifest: HousekeepingDomainManifest = {
|
|||||||
labelKey: "pages.housekeeping.domains.people.title",
|
labelKey: "pages.housekeeping.domains.people.title",
|
||||||
descriptionKey: "pages.housekeeping.domains.people.description",
|
descriptionKey: "pages.housekeeping.domains.people.description",
|
||||||
iconId: "users",
|
iconId: "users",
|
||||||
previewHref: "/admin-next/people",
|
canonicalHref: "/ase-next/people",
|
||||||
capability,
|
capability,
|
||||||
routes: [],
|
routes: [],
|
||||||
searchProviders: [searchProvider],
|
searchProviders: [searchProvider],
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import type { HousekeepingInboxSource } from "./inbox";
|
|||||||
import type { HousekeepingSearchProvider } from "./search";
|
import type { HousekeepingSearchProvider } from "./search";
|
||||||
import type { HousekeepingWidgetDefinition } from "./widget";
|
import type { HousekeepingWidgetDefinition } from "./widget";
|
||||||
|
|
||||||
|
export type HousekeepingSurface = "preview" | "canonical";
|
||||||
|
export type CanonicalHousekeepingHref = `/ase${string}`;
|
||||||
export interface HousekeepingRouteDefinition {
|
export interface HousekeepingRouteDefinition {
|
||||||
id: string;
|
id: string;
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
href: string;
|
href: CanonicalHousekeepingHref;
|
||||||
capability: CapabilityRequirement;
|
capability: CapabilityRequirement;
|
||||||
matchPrefixes?: readonly string[];
|
matchPrefixes?: readonly string[];
|
||||||
}
|
}
|
||||||
@@ -17,7 +19,7 @@ export interface HousekeepingDomainManifest {
|
|||||||
labelKey: string;
|
labelKey: string;
|
||||||
descriptionKey: string;
|
descriptionKey: string;
|
||||||
iconId: "inbox" | "users" | "file-text" | "gem" | "hotel" | "settings";
|
iconId: "inbox" | "users" | "file-text" | "gem" | "hotel" | "settings";
|
||||||
previewHref: `/admin-next/${HousekeepingDomainId}`;
|
canonicalHref: CanonicalHousekeepingHref;
|
||||||
capability: CapabilityRequirement;
|
capability: CapabilityRequirement;
|
||||||
routes: readonly HousekeepingRouteDefinition[];
|
routes: readonly HousekeepingRouteDefinition[];
|
||||||
searchProviders: readonly HousekeepingSearchProvider[];
|
searchProviders: readonly HousekeepingSearchProvider[];
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ export {
|
|||||||
} from "./capability";
|
} from "./capability";
|
||||||
export type { HousekeepingCommand } from "./command";
|
export type { HousekeepingCommand } from "./command";
|
||||||
export type {
|
export type {
|
||||||
|
CanonicalHousekeepingHref,
|
||||||
HousekeepingDomainManifest,
|
HousekeepingDomainManifest,
|
||||||
HousekeepingRouteDefinition,
|
HousekeepingRouteDefinition,
|
||||||
|
HousekeepingSurface,
|
||||||
} from "./domain";
|
} from "./domain";
|
||||||
export type {
|
export type {
|
||||||
HousekeepingInboxSource,
|
HousekeepingInboxSource,
|
||||||
|
|||||||
@@ -555,7 +555,7 @@ describe("housekeeping foundation completion contracts", () => {
|
|||||||
for (const path of [
|
for (const path of [
|
||||||
"src/app/admin/layout.tsx",
|
"src/app/admin/layout.tsx",
|
||||||
"src/app/mod/layout.tsx",
|
"src/app/mod/layout.tsx",
|
||||||
"src/app/admin-next/layout.tsx",
|
"src/app/ase-next/layout.tsx",
|
||||||
]) {
|
]) {
|
||||||
expect(existsSync(path), path).toBe(true);
|
expect(existsSync(path), path).toBe(true);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -3,8 +3,10 @@ import { satisfiesCapability } from "./capability-context";
|
|||||||
import type {
|
import type {
|
||||||
HousekeepingCapabilityContext,
|
HousekeepingCapabilityContext,
|
||||||
HousekeepingDomainManifest,
|
HousekeepingDomainManifest,
|
||||||
|
HousekeepingSurface,
|
||||||
} from "./contracts";
|
} from "./contracts";
|
||||||
import type { HousekeepingRegistry } from "./registry";
|
import type { HousekeepingRegistry } from "./registry";
|
||||||
|
import { toHousekeepingHref } from "./routing/href";
|
||||||
|
|
||||||
export interface HousekeepingNavigationDomain {
|
export interface HousekeepingNavigationDomain {
|
||||||
id: HousekeepingDomainId;
|
id: HousekeepingDomainId;
|
||||||
@@ -18,13 +20,14 @@ export interface HousekeepingNavigationDomain {
|
|||||||
export function buildHousekeepingNavigation(
|
export function buildHousekeepingNavigation(
|
||||||
registry: HousekeepingRegistry,
|
registry: HousekeepingRegistry,
|
||||||
context: HousekeepingCapabilityContext,
|
context: HousekeepingCapabilityContext,
|
||||||
|
surface: HousekeepingSurface,
|
||||||
translate: (key: string) => string,
|
translate: (key: string) => string,
|
||||||
): readonly HousekeepingNavigationDomain[] {
|
): readonly HousekeepingNavigationDomain[] {
|
||||||
return registry.domains
|
return registry.domains
|
||||||
.filter((domain) => satisfiesCapability(context, domain.capability))
|
.filter((domain) => satisfiesCapability(context, domain.capability))
|
||||||
.map((domain) => ({
|
.map((domain) => ({
|
||||||
id: domain.id,
|
id: domain.id,
|
||||||
href: domain.previewHref,
|
href: toHousekeepingHref(domain.canonicalHref, surface),
|
||||||
iconId: domain.iconId,
|
iconId: domain.iconId,
|
||||||
label: translate(domain.labelKey),
|
label: translate(domain.labelKey),
|
||||||
description: translate(domain.descriptionKey),
|
description: translate(domain.descriptionKey),
|
||||||
@@ -32,7 +35,7 @@ export function buildHousekeepingNavigation(
|
|||||||
.filter((route) => satisfiesCapability(context, route.capability))
|
.filter((route) => satisfiesCapability(context, route.capability))
|
||||||
.map((route) => ({
|
.map((route) => ({
|
||||||
id: route.id,
|
id: route.id,
|
||||||
href: route.href,
|
href: toHousekeepingHref(route.href, surface),
|
||||||
label: translate(route.labelKey),
|
label: translate(route.labelKey),
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -75,12 +75,12 @@ describe("HousekeepingPageState", () => {
|
|||||||
state="error"
|
state="error"
|
||||||
title="Could not load"
|
title="Could not load"
|
||||||
description="Try again later"
|
description="Try again later"
|
||||||
retryAction={<a href="/admin-next/operations">Retry preview</a>}
|
retryAction={<a href="/ase-next/operations">Retry preview</a>}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain('role="alert"');
|
expect(html).toContain('role="alert"');
|
||||||
expect(html).toContain('href="/admin-next/operations"');
|
expect(html).toContain('href="/ase-next/operations"');
|
||||||
expect(html).toContain(">Retry preview<");
|
expect(html).toContain(">Retry preview<");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -92,7 +92,7 @@ describe("HousekeepingPageShell", () => {
|
|||||||
title="People"
|
title="People"
|
||||||
description="Review operator-facing people data"
|
description="Review operator-facing people data"
|
||||||
context={<span>Preview context</span>}
|
context={<span>Preview context</span>}
|
||||||
primaryAction={<a href="/admin-next/people/new">Create preview</a>}
|
primaryAction={<a href="/ase-next/people/new">Create preview</a>}
|
||||||
>
|
>
|
||||||
<p>Page body</p>
|
<p>Page body</p>
|
||||||
</HousekeepingPageShell>,
|
</HousekeepingPageShell>,
|
||||||
@@ -102,7 +102,7 @@ describe("HousekeepingPageShell", () => {
|
|||||||
expect(html).toContain(">People<");
|
expect(html).toContain(">People<");
|
||||||
expect(html).toContain(">Review operator-facing people data<");
|
expect(html).toContain(">Review operator-facing people data<");
|
||||||
expect(html).toContain(">Preview context<");
|
expect(html).toContain(">Preview context<");
|
||||||
expect(html).toContain('href="/admin-next/people/new"');
|
expect(html).toContain('href="/ase-next/people/new"');
|
||||||
expect(html).toContain(">Create preview<");
|
expect(html).toContain(">Create preview<");
|
||||||
expect(html).toContain(">Page body</p>");
|
expect(html).toContain(">Page body</p>");
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -34,8 +34,11 @@ export function createHousekeepingRegistry(
|
|||||||
}
|
}
|
||||||
domainIds.add(manifest.id);
|
domainIds.add(manifest.id);
|
||||||
|
|
||||||
if (manifest.previewHref !== `/admin-next/${manifest.id}`) {
|
if (
|
||||||
throw new Error(`invalid preview href: ${manifest.previewHref}`);
|
manifest.canonicalHref !==
|
||||||
|
(manifest.id === "operations" ? "/ase" : `/ase/${manifest.id}`)
|
||||||
|
) {
|
||||||
|
throw new Error(`invalid canonical href: ${manifest.canonicalHref}`);
|
||||||
}
|
}
|
||||||
validateNonEmpty(manifest.labelKey, "label key");
|
validateNonEmpty(manifest.labelKey, "label key");
|
||||||
validateNonEmpty(manifest.descriptionKey, "description key");
|
validateNonEmpty(manifest.descriptionKey, "description key");
|
||||||
@@ -72,6 +75,9 @@ export function createHousekeepingRegistry(
|
|||||||
routeIds.add(route.id);
|
routeIds.add(route.id);
|
||||||
|
|
||||||
validateNonEmpty(route.href, "route href");
|
validateNonEmpty(route.href, "route href");
|
||||||
|
if (route.href !== "/ase" && !route.href.startsWith("/ase/")) {
|
||||||
|
throw new Error(`invalid canonical route href: ${route.href}`);
|
||||||
|
}
|
||||||
if (routeHrefs.has(route.href)) {
|
if (routeHrefs.has(route.href)) {
|
||||||
throw new Error(`duplicate route href: ${route.href}`);
|
throw new Error(`duplicate route href: ${route.href}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { CanonicalHousekeepingHref } from "../contracts";
|
||||||
|
import { toHousekeepingHref } from "./href";
|
||||||
|
|
||||||
|
describe("toHousekeepingHref", () => {
|
||||||
|
it("keeps the canonical operations root unchanged", () => {
|
||||||
|
expect(toHousekeepingHref("/ase", "canonical")).toBe("/ase");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps a canonical domain route to the preview namespace", () => {
|
||||||
|
expect(toHousekeepingHref("/ase/people/users", "preview")).toBe(
|
||||||
|
"/ase-next/people/users",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects paths outside the canonical ase namespace", () => {
|
||||||
|
expect(() =>
|
||||||
|
toHousekeepingHref("/admin" as CanonicalHousekeepingHref, "canonical"),
|
||||||
|
).toThrow("invalid canonical housekeeping href");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type {
|
||||||
|
CanonicalHousekeepingHref,
|
||||||
|
HousekeepingSurface,
|
||||||
|
} from "../contracts";
|
||||||
|
|
||||||
|
export function toHousekeepingHref(
|
||||||
|
href: CanonicalHousekeepingHref,
|
||||||
|
surface: HousekeepingSurface,
|
||||||
|
): CanonicalHousekeepingHref | `/ase-next${string}` {
|
||||||
|
if (href !== "/ase" && !href.startsWith("/ase/")) {
|
||||||
|
throw new Error(`invalid canonical housekeeping href: ${href}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return surface === "canonical" ? href : href.replace(/^\/ase/, "/ase-next");
|
||||||
|
}
|
||||||
@@ -17,17 +17,17 @@ const labels = {
|
|||||||
const domains: readonly HousekeepingNavigationDomain[] = [
|
const domains: readonly HousekeepingNavigationDomain[] = [
|
||||||
{
|
{
|
||||||
id: "operations",
|
id: "operations",
|
||||||
href: "/admin-next/operations",
|
href: "/ase-next/operations",
|
||||||
iconId: "inbox",
|
iconId: "inbox",
|
||||||
label: "Operations",
|
label: "Operations",
|
||||||
description: "Manage operations",
|
description: "Manage operations",
|
||||||
items: [
|
items: [
|
||||||
{ id: "queue", href: "/admin-next/operations/queue", label: "Queue" },
|
{ id: "queue", href: "/ase-next/operations/queue", label: "Queue" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "people",
|
id: "people",
|
||||||
href: "/admin-next/people",
|
href: "/ase-next/people",
|
||||||
iconId: "users",
|
iconId: "users",
|
||||||
label: "People",
|
label: "People",
|
||||||
description: "Manage people",
|
description: "Manage people",
|
||||||
@@ -35,7 +35,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "content",
|
id: "content",
|
||||||
href: "/admin-next/content",
|
href: "/ase-next/content",
|
||||||
iconId: "file-text",
|
iconId: "file-text",
|
||||||
label: "Content",
|
label: "Content",
|
||||||
description: "Manage content",
|
description: "Manage content",
|
||||||
@@ -43,7 +43,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "economy",
|
id: "economy",
|
||||||
href: "/admin-next/economy",
|
href: "/ase-next/economy",
|
||||||
iconId: "gem",
|
iconId: "gem",
|
||||||
label: "Economy",
|
label: "Economy",
|
||||||
description: "Manage economy",
|
description: "Manage economy",
|
||||||
@@ -51,7 +51,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "hotel",
|
id: "hotel",
|
||||||
href: "/admin-next/hotel",
|
href: "/ase-next/hotel",
|
||||||
iconId: "hotel",
|
iconId: "hotel",
|
||||||
label: "Hotel",
|
label: "Hotel",
|
||||||
description: "Manage hotel",
|
description: "Manage hotel",
|
||||||
@@ -59,7 +59,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system",
|
id: "system",
|
||||||
href: "/admin-next/system",
|
href: "/ase-next/system",
|
||||||
iconId: "settings",
|
iconId: "settings",
|
||||||
label: "System",
|
label: "System",
|
||||||
description: "Manage system",
|
description: "Manage system",
|
||||||
@@ -178,7 +178,7 @@ describe("HousekeepingShell", () => {
|
|||||||
expect(html).toContain('<nav aria-label="Operations navigation"');
|
expect(html).toContain('<nav aria-label="Operations navigation"');
|
||||||
expect(html).toContain('<main id="housekeeping-content"');
|
expect(html).toContain('<main id="housekeeping-content"');
|
||||||
expect(html).toContain(">Deck body</p>");
|
expect(html).toContain(">Deck body</p>");
|
||||||
expect(html).toContain('href="/admin-next/operations/queue"');
|
expect(html).toContain('href="/ase-next/operations/queue"');
|
||||||
expect(html).toContain(">Queue<");
|
expect(html).toContain(">Queue<");
|
||||||
expect(html).toContain(">Nora<");
|
expect(html).toContain(">Nora<");
|
||||||
expect(html).toContain(">6<");
|
expect(html).toContain(">6<");
|
||||||
@@ -196,7 +196,7 @@ describe("HousekeepingShell", () => {
|
|||||||
const activeAnchors = allAnchors.filter((anchor) =>
|
const activeAnchors = allAnchors.filter((anchor) =>
|
||||||
anchor.includes('aria-current="page"'),
|
anchor.includes('aria-current="page"'),
|
||||||
);
|
);
|
||||||
const peopleAnchor = anchorForHref(html, "/admin-next/people");
|
const peopleAnchor = anchorForHref(html, "/ase-next/people");
|
||||||
for (const iconClass of [
|
for (const iconClass of [
|
||||||
"lucide-inbox",
|
"lucide-inbox",
|
||||||
"lucide-users",
|
"lucide-users",
|
||||||
@@ -222,7 +222,7 @@ describe("HousekeepingShell", () => {
|
|||||||
);
|
);
|
||||||
expect(() => renderShell("system", domainsWithoutSystem)).not.toThrow();
|
expect(() => renderShell("system", domainsWithoutSystem)).not.toThrow();
|
||||||
const html = renderShell("system", domainsWithoutSystem);
|
const html = renderShell("system", domainsWithoutSystem);
|
||||||
expect(html).not.toContain('href="/admin-next/operations/queue"');
|
expect(html).not.toContain('href="/ase-next/operations/queue"');
|
||||||
expect(html).not.toContain(">Queue<");
|
expect(html).not.toContain(">Queue<");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -7,7 +7,7 @@ export const operationsMigrationEntries: readonly MigrationEntry[] = [
|
|||||||
legacyPath: "/admin",
|
legacyPath: "/admin",
|
||||||
sourceFile: "src/app/admin/page.tsx",
|
sourceFile: "src/app/admin/page.tsx",
|
||||||
targetDomain: "operations",
|
targetDomain: "operations",
|
||||||
targetPath: "/admin/work",
|
targetPath: "/ase",
|
||||||
decision: "REBUILD",
|
decision: "REBUILD",
|
||||||
capabilities: { read: [PERMS.ADMIN_DASHBOARD], mutate: [] },
|
capabilities: { read: [PERMS.ADMIN_DASHBOARD], mutate: [] },
|
||||||
dependencies: {
|
dependencies: {
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -11,7 +11,7 @@ const page = (legacyPath: string): LegacyPage => ({
|
|||||||
const entry = (legacyPath: string): MigrationEntry => ({
|
const entry = (legacyPath: string): MigrationEntry => ({
|
||||||
...page(legacyPath),
|
...page(legacyPath),
|
||||||
targetDomain: "system",
|
targetDomain: "system",
|
||||||
targetPath: "/admin/system/example",
|
targetPath: "/ase/system/example",
|
||||||
decision: "REHOST",
|
decision: "REHOST",
|
||||||
capabilities: { read: ["admin.dashboard"], mutate: [] },
|
capabilities: { read: ["admin.dashboard"], mutate: [] },
|
||||||
dependencies: { queries: [], mutations: [] },
|
dependencies: { queries: [], mutations: [] },
|
||||||
@@ -112,7 +112,7 @@ describe("validateMigrationEntries", () => {
|
|||||||
"MERGE requires requiredTests: /admin",
|
"MERGE requires requiredTests: /admin",
|
||||||
"mutation dependencies require auditRequirement: /admin",
|
"mutation dependencies require auditRequirement: /admin",
|
||||||
"mutation dependencies require mutate capabilities: /admin",
|
"mutation dependencies require mutate capabilities: /admin",
|
||||||
"non-REMOVE targetPath must start with /admin/: /admin",
|
"non-REMOVE targetPath must use /ase: /admin",
|
||||||
"prohibited placeholder: /admin",
|
"prohibited placeholder: /admin",
|
||||||
"VERIFIED requires parityEvidence: /admin",
|
"VERIFIED requires parityEvidence: /admin",
|
||||||
]);
|
]);
|
||||||
@@ -134,3 +134,19 @@ describe("validateMigrationEntries", () => {
|
|||||||
).toEqual(["REHOST requires targetPath: /admin"]);
|
).toEqual(["REHOST requires targetPath: /admin"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("requires non-removal targets to use the ase namespace", () => {
|
||||||
|
expect(
|
||||||
|
validateMigrationEntries(
|
||||||
|
[page("/admin")],
|
||||||
|
[{ ...entry("/admin"), targetPath: "/ase/system/example" }],
|
||||||
|
),
|
||||||
|
).toEqual([]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateMigrationEntries(
|
||||||
|
[page("/admin")],
|
||||||
|
[{ ...entry("/admin"), targetPath: "/admin/system/example" }],
|
||||||
|
),
|
||||||
|
).toContain("non-REMOVE targetPath must use /ase: /admin");
|
||||||
|
});
|
||||||
@@ -84,11 +84,9 @@ export function validateMigrationEntries(
|
|||||||
if (
|
if (
|
||||||
entry.targetPath !== null &&
|
entry.targetPath !== null &&
|
||||||
entry.decision !== "REMOVE" &&
|
entry.decision !== "REMOVE" &&
|
||||||
!entry.targetPath.startsWith("/admin/")
|
!(entry.targetPath === "/ase" || entry.targetPath.startsWith("/ase/"))
|
||||||
) {
|
) {
|
||||||
issues.add(
|
issues.add(`non-REMOVE targetPath must use /ase: ${legacyPath}`);
|
||||||
`non-REMOVE targetPath must start with /admin/: ${legacyPath}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
const ROOTS = [
|
const ROOTS = [
|
||||||
"src/app/admin",
|
"src/app/admin",
|
||||||
"src/components/admin",
|
"src/components/admin",
|
||||||
"src/app/admin-next",
|
"src/app/ase-next",
|
||||||
"src/features/housekeeping",
|
"src/features/housekeeping",
|
||||||
];
|
];
|
||||||
const GRAPHICAL_ALLOWLIST = [
|
const GRAPHICAL_ALLOWLIST = [
|
||||||
|
|||||||
Reference in new issue
Block a user