chore: sync the frontend template from a0fbafb to 5434760

Carries eight template commits: the provider sandbox actually running, release
admission to a named environment, the product feature manifest with its runtime
kill switch, architecture and documentation rules that match what is enforced,
the removability fixtures, and the browser, visual and performance evidence.

Product identity is unchanged. `package.json` keeps `tech-log-frontend` and the
catalog keeps the Tech Log naming; the home page was not in the delta. The
visual baselines are this product's own — the template's were excluded from the
transplant and these were regenerated here, where the only difference is the
platform overview's new product-feature section.

What this repository gains operationally: `config/runtime/{local,development,
staging,production}.json` with `FE-GATE-027` refusing an artifact whose runtime
document does not match the environment it is being admitted to, and
`FEATURE_OVERRIDES` for taking an installed feature out of service without a
rebuild.

Verified here: eight gates green, build green, visual 5/5, and 1,858 of 1,859
tests in the suites that do not need a sandbox — the one failure passes in
isolation and is a jsdom lazy-chunk timeout under parallel load. The provider
suites cannot run on this machine at all: `kernel.apparmor_restrict_unprivileged
_userns=1` makes `bwrap --unshare-net` fail, reproducible without any code from
either repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-15 21:34:19 +09:00
co-authored by Claude Opus 5
parent 325a2a0843
commit bdee07a93b
101 changed files with 3116 additions and 448 deletions
@@ -226,6 +226,30 @@ function capabilityBadge(
return { text: `활성 (${status.active})`, variant: "success" };
}
/**
* §3.5. The three states an operator has to be able to tell apart: shipped and
* serving, shipped and switched off, and not in this build at all.
*/
const FEATURE_BADGE = Object.freeze({
ACTIVE: Object.freeze({
variant: "success" as const,
text: "사용 중",
description: "이 빌드에 설치되어 있고 런타임 설정이 끄지 않았습니다.",
}),
DISABLED_BY_CONFIG: Object.freeze({
variant: "warning" as const,
text: "설정으로 중지",
description:
"이 빌드에 포함되어 있으나 런타임 설정이 껐습니다. 재빌드 없이 다시 켤 수 있습니다.",
}),
NOT_INSTALLED: Object.freeze({
variant: "neutral" as const,
text: "미설치",
description:
"빌드 시 제품 매니페스트가 선택하지 않았습니다. 런타임 설정으로는 켤 수 없습니다.",
}),
});
function buildOperationRows(): readonly OperationRow[] {
return Object.freeze(
[...COMPOSED_CONTRACT_CONTRIBUTIONS.httpByOperationId.values()].map(
@@ -273,6 +297,7 @@ export default function PlatformOverviewPage() {
const routes = Object.values(ROUTE_REGISTRY);
const operations = buildOperationRows();
const capabilities = runtime.getCapabilitySnapshot();
const features = runtime.getFeatureSnapshot();
const activeCapabilityCount = capabilities.filter(
(status) => status.active > 0,
).length;
@@ -479,6 +504,37 @@ export default function PlatformOverviewPage() {
})}
</div>
</section>
<section
className="gallery-section"
aria-labelledby="platform-features-title"
>
<header className="gallery-section__header">
<h2 id="platform-features-title"> </h2>
<p>
,
.
.
.
</p>
</header>
<div className="component-grid component-grid--two">
{features.map((status) => {
const badge = FEATURE_BADGE[status.state];
return (
<Card
key={status.featureId}
title={status.featureId}
footer={<Badge variant={badge.variant}>{badge.text}</Badge>}
>
<p data-product-feature={status.featureId}>
{badge.description}
</p>
</Card>
);
})}
</div>
</section>
</section>
);
}
+8
View File
@@ -79,6 +79,10 @@ const PLATFORM_KO_MESSAGES = {
"route.invalid.title": "올바르지 않은 주소입니다.",
"route.invalid.description": "주소의 경로 또는 검색 조건을 확인해 주세요.",
"route.invalid.action": "안전한 탐색 링크를 사용해 주세요.",
"route.disabledFeature.title": "현재 사용할 수 없는 기능입니다.",
"route.disabledFeature.description":
"이 기능은 배포 설정에서 중지되어 있습니다. 코드에는 포함되어 있으며 운영자가 다시 켤 수 있습니다.",
"route.disabledFeature.action": "다른 탐색 링크를 사용해 주세요.",
"route.auth.integration.title": "로그인 연동이 필요합니다.",
"route.auth.integration.description":
"외부 인증 소유자가 연결되면 이 보호 라우트를 사용할 수 있습니다.",
@@ -233,6 +237,10 @@ const PLATFORM_EN_MESSAGES = {
"route.invalid.title": "This address is invalid.",
"route.invalid.description": "Check the path and search parameters.",
"route.invalid.action": "Use a safe navigation link.",
"route.disabledFeature.title": "This feature is not available right now.",
"route.disabledFeature.description":
"The deployment configuration has switched it off. It is still part of this build and an operator can switch it back on.",
"route.disabledFeature.action": "Use another navigation link.",
"route.auth.integration.title": "Sign-in integration is required.",
"route.auth.integration.description":
"This protected route is available after an external authentication owner is connected.",
+10 -1
View File
@@ -5,6 +5,7 @@ import type { SessionState } from "../../application/ports/in/application-api.ts
import { normalizeColorSchemePreference } from "../../application/policies/color-scheme.ts";
import {
NAVIGATION_ROUTES,
ROUTE_FEATURE_OWNER,
routePath,
} from "../../features/installed-feature-contracts.ts";
import {
@@ -19,6 +20,7 @@ import {
useLocale,
type MessageKey,
} from "../i18n/index.ts";
import { useApplication } from "../providers/application-provider.tsx";
import { useSession } from "../providers/session-provider.tsx";
import { useTheme } from "../providers/theme-provider.tsx";
@@ -166,10 +168,17 @@ export function AppShell() {
function PrimaryNavigation({ id }: Readonly<{ id: string }>) {
const { resolve, message } = useLocale();
const { runtime } = useApplication();
// §3.5. A feature the runtime document disabled does not advertise itself.
// The router refuses its routes too, so this is presentation, not the switch.
const routes = NAVIGATION_ROUTES.filter((definition) => {
const owner = ROUTE_FEATURE_OWNER[definition.routeId];
return owner === undefined || runtime.isFeatureActive(owner);
});
return (
<nav id={id} aria-label={message("shell.primaryNavigation")}>
<ul className="app-navigation">
{NAVIGATION_ROUTES.map((definition) => (
{routes.map((definition) => (
<li key={definition.routeId}>
<NavLink
className={({ isActive }) =>
+34 -2
View File
@@ -18,6 +18,7 @@ import {
import {
getRoute,
ROUTE_FEATURE_OWNER,
ROUTE_REGISTRY,
} from "../../features/installed-feature-contracts.ts";
import type { RouteDefinition } from "../../contracts/routes.ts";
@@ -96,6 +97,26 @@ function InvalidRouteSurface({ code }: { code: string }) {
);
}
/**
* §3.5. A route whose feature the runtime document switched off. It answers as
* "not available" rather than rendering the feature or crashing, so disabling a
* feature is a deployment action and not an outage.
*/
function DisabledFeatureSurface({ featureId }: { featureId: string }) {
const { message } = useLocale();
return (
<section className="ui-page" data-surface="disabled-feature">
<PageHeader
title={message("route.disabledFeature.title")}
description={message("route.disabledFeature.description")}
/>
<p data-disabled-feature={featureId}>
{message("route.disabledFeature.action")}
</p>
</section>
);
}
function RouteLifecycle({
definition,
buildId,
@@ -245,11 +266,22 @@ function RegisteredRoute({
buildId: string;
}) {
const definition = getRoute(routeId);
const runtime = ROUTE_RUNTIME[routeId];
const params = useParams();
const [search] = useSearchParams();
const location = useLocation();
const { diagnostics, recovery } = useApplication();
const { diagnostics, runtime: platformRuntime, recovery } = useApplication();
// §3.5. A feature the runtime document disabled is out of service, not
// merely hidden: withdrawing it from navigation alone would leave a typed
// deep link that still mounts it.
const owner = ROUTE_FEATURE_OWNER[routeId];
if (owner !== undefined && !platformRuntime.isFeatureActive(owner)) {
return <DisabledFeatureSurface featureId={owner} />;
}
// The registry and the runtime table are composed from the same manifest, so
// a route without a component means the two disagree — refuse rather than
// crash the shell.
const runtime = ROUTE_RUNTIME[routeId];
if (!runtime) return <DisabledFeatureSurface featureId={owner ?? routeId} />;
const parsed = parseRouteInput(routeId, params, search);
if (!parsed.success) return <InvalidRouteSurface code={parsed.code} />;