Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
3d6ae1d
feat(i18n): gate Customer Master on published screen copy
Sep 5, 2026
fcceb7c
docs(gaps): record Customer Master translation cutover evidence
Sep 5, 2026
98de7c9
test(i18n): require auth-bound Customer Master copy admission
seonghobae Sep 5, 2026
90537bc
fix(i18n): bind Customer Master copy admission to auth token
seonghobae Sep 5, 2026
0c6c523
test(i18n): parse compound Customer Master admission guard
seonghobae Sep 5, 2026
fd3f032
test(i18n): reject false publication diagnosis
seonghobae Sep 5, 2026
cb09396
fix(i18n): keep translation failures cause-neutral
seonghobae Sep 5, 2026
28fbdee
docs(gaps): align Customer Master translation evidence
seonghobae Sep 5, 2026
170c230
docs(i18n): refresh exact-head visual evidence
Sep 5, 2026
51c7666
test(i18n): require localized translation bootstrap shell
seonghobae Sep 5, 2026
422064e
refactor(ui): allow localized bootstrap status semantics
seonghobae Sep 5, 2026
078f47d
fix(i18n): localize translation bootstrap gate across eight locales
seonghobae Sep 5, 2026
488b358
test(customer-master): reject stale auth data admission
seonghobae Sep 5, 2026
a0ebd1f
chore(i18n): converge Customer Master consumer onto parent test fixes
seonghobae Sep 5, 2026
c95736a
fix(customer-master): reject stale auth responses
Sep 5, 2026
aa601e3
docs(gaps): refresh Customer Master exact-head evidence
Sep 5, 2026
fe01453
test(customer-master): reject stale secondary auth projections
seonghobae Sep 5, 2026
846ec47
fix(customer-master): invalidate stale auth projections
seonghobae Sep 6, 2026
9b033e2
fix(i18n): give readers actionable screen recovery guidance
Sep 7, 2026
b98ebeb
fix(customer-master): retire requests across authorization lifecycles
Sep 7, 2026
afe7f8f
test(customer-master): intercept requests through the fetch boundary
Sep 7, 2026
c01e310
docs(evidence): record live login boundary and full-suite recheck
Sep 7, 2026
8c662a0
merge: preserve translation parent baseline and consumer evidence
Sep 8, 2026
f539295
chore(stack): converge #932 onto current translation parent
seonghobae Sep 8, 2026
727b23d
Merge remote-tracking branch 'origin/feat/i18n-versioned-translation-…
Sep 9, 2026
3f5f602
chore: converge #932 onto stable #929 baseline
seonghobae Sep 9, 2026
81bec97
chore: converge #932 onto executable #929 baseline
seonghobae Sep 9, 2026
578b048
chore: converge #932 onto scoped #929 snapshot
seonghobae Sep 9, 2026
07e130a
docs(i18n): keep stale screenshots out of acceptance
Sep 9, 2026
3af09e6
chore: converge #932 onto updated #929 head e1da2c26
Sep 9, 2026
d250248
chore: converge #932 onto rollback-safe translation parent
seonghobae Sep 9, 2026
dba4224
chore: converge #932 onto current #929 head
seonghobae Sep 9, 2026
6a13898
chore(i18n): converge #932 onto current translation-ledger owner
seonghobae Sep 10, 2026
798a930
test(i18n): require Customer Master ancestry screen keys
seonghobae Sep 10, 2026
6986098
test(customer-master): reproduce same-auth loading supersession
seonghobae Sep 10, 2026
edfb71d
test(customer-master): distinguish related lookup failure from empty
seonghobae Sep 10, 2026
bdfcdd2
test(customer-master): make related failure RED executable
seonghobae Sep 11, 2026
89e727e
test(#932): execute Customer Master request-state repair
seonghobae Sep 11, 2026
8456b89
fix(#932): repair exact Customer Master automation anchors
seonghobae Sep 11, 2026
e02270a
fix(#932): switch repair harness to deterministic patch
seonghobae Sep 11, 2026
3fa18a2
fix(#932): execute repair script without YAML patch ambiguity
seonghobae Sep 11, 2026
857284c
fix(customer-master): preserve related request state
github-actions[bot] Sep 11, 2026
bb36413
test(customer-master): align auth lifecycle contract with related loader
seonghobae Sep 11, 2026
012c30a
test(i18n): expose global locale fallback gap
seonghobae Sep 11, 2026
05a93b4
fix(i18n): separate global locale admission from ledger coverage
seonghobae Sep 11, 2026
f898ae0
test(i18n): pin product-wide locale admission
seonghobae Sep 11, 2026
8eb6e60
test(i18n): align bootstrap shell with global admission
seonghobae Sep 11, 2026
c993e44
fix(i18n): align bootstrap copy with global locale admission
seonghobae Sep 11, 2026
bdede9b
test(customer-master): reproduce same-entity request generation race
seonghobae Sep 11, 2026
1834a3b
ci: stage bounded #932 request-generation repair
seonghobae Sep 11, 2026
6d2e624
test(customer-master): satisfy RelatedNode contract in overlap regres…
seonghobae Sep 11, 2026
c13f6c3
fix(customer-master): fence same-entity related requests
github-actions[bot] Sep 11, 2026
4fa09d9
merge(parent): adopt UTF-8 cache-bound repair from #929
seonghobae Sep 11, 2026
207b737
merge(parent): adopt Customer Master eight-locale review draft
seonghobae Sep 17, 2026
8133220
merge(parent): adopt rollback-test transaction repair
seonghobae Sep 17, 2026
90f2e2e
merge(parent): adopt Customer Master seed ownership boundary
seonghobae Sep 17, 2026
0ce4201
merge(parent): adopt migration-provenance ownership repair
seonghobae Sep 17, 2026
bf641b4
merge(parent): preserve ownership rollback isolation
seonghobae Sep 17, 2026
6737148
merge(parent): scope Customer Master seed ownership lifecycle
seonghobae Sep 17, 2026
d03ce1c
merge(parent): pin translation ownership trigger search path
seonghobae Sep 17, 2026
faed910
merge(parent): pin translation ledger guard search paths
seonghobae Sep 17, 2026
8ff0354
merge(parent): converge concurrent seed ownership repair
seonghobae Sep 17, 2026
270fa7a
merge(parent): converge concurrent Customer Master seed replay repair
seonghobae Sep 17, 2026
c7103da
merge(parent): preserve reviewed translation replay repair
seonghobae Sep 17, 2026
60416fd
merge(parent): converge completed-seed replay admission
seonghobae Sep 17, 2026
2b9e839
merge(parent): converge one-time seed retirement
seonghobae Sep 17, 2026
9a5dc05
merge(parent): converge retired seed constraint upgrade
seonghobae Sep 17, 2026
5368b89
merge(parent): converge operator-check ownership repair
seonghobae Sep 17, 2026
fb24225
merge(parent): converge versioned retirement constraints
seonghobae Sep 17, 2026
9c6d87b
merge(parent): preserve retired seed rollback authority
seonghobae Sep 24, 2026
201327d
merge(parent): adopt production-path seed evidence
seonghobae Sep 24, 2026
4580aae
merge(parent): adopt truncate-proof seed provenance
seonghobae Sep 24, 2026
9c995ff
merge(parent): preserve truncate guard across failed rollback
seonghobae Sep 24, 2026
023d454
merge(parent): adopt seed replay lock-order repair
seonghobae Sep 24, 2026
4cfe2fe
merge(parent): adopt Customer Master action-copy review
seonghobae Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/adr/0362-versioned-ui-translation-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ Child insert/update/delete obtains a `FOR UPDATE` lock on the parent resource. P

The existing `user_account.preferred_locale` constraint expands to the same eight language tags. API request validation and frontend consumption must be cut over to the same contract before #922 can close; the database/read-model foundation alone is not buyer-visible completion.

The first consumer slice is the Customer Master destination. It requests the
latest published `customer-master` resource after authentication and after each
locale change, admits it only when every key used by that destination is
present, and does not request Customer Master data until that copy is ready.
Loading and retry states remain a small product shell outside the translated
screen resource so a missing resource can tell the reader what to do next.
Changing locale aborts the prior request logically; a late response for an old
locale cannot replace the active screen. Other destinations remain on their
existing bundle until they receive their own complete resource and executable
cutover evidence.

## DDD mapping

- Subdomain: product composition / presentation read model.
Expand Down
72 changes: 59 additions & 13 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,39 @@
> readiness. The authenticated `GET /api/translations/{screen_key}` API is
> implemented on the candidate branch. That is candidate implementation
> evidence, not protected-main, deployed, or release evidence.
> A stacked Customer Master consumer candidate now exists at exact head
> `cb093960d43e95cdfb1d9ed491e920e2106305db` on top of PR #929's
> exact head `f07a755972e38b4b2a961ab11acd9d3abb229967`. It admits all eight
> locale tags, fetches the authenticated `customer-master` resource before
> customer data, rejects incomplete screen projections, ignores late responses
> from a previous locale or authorization identity, and shows an actionable
> retry state instead of
> rendering bundled Customer Master copy. Review `5119233938` found that the
> retry shell incorrectly diagnosed every transport/auth/permission/not-found/
> service exception as an unpublished translation. RED
> `fd3f0326f539f23dfae75fc3511722ead4455d36` and causal repair
> `cb093960d43e95cdfb1d9ed491e920e2106305db` keep that unclassified failure
> cause-neutral while retaining one concrete retry action. This stacked branch
> is not protected-main, hosted-product-GREEN, authenticated PostgreSQL, or
> deployed evidence.
> Current-head local evidence is 59 frontend test files / 538 tests, lint,
> production build, Storybook build, nine focused Python contract tests, and
> freshly inspected 1440 x 900 plus 390 x 844 retry-state captures. These local
> results do not satisfy the protected delivery boundary.
>
> Two adjacent candidates remain outside protected `main`: PR #911 at
> `5d40eed35a0b6e0d182397f8d02b29c38e9bdd17` replaces the synchronous
> PostgreSQL driver and defaults omitted TLS policy to identity verification;
> PR #909 at `e82aed38c0997588529e21fe0e1bf4159f3c198c` keeps authorized Customer
> Master records visible when imported hierarchy edges are malformed and adds
> synthetic desktop/mobile Storybook evidence. #911 is Ready for exact-head
> validation after moving its colliding TLS ADR to Proposed ADR 0366. #909 is
> Draft because #922's eight-locale published-resource cutover and the required
> current-head material-UI/runtime evidence are still absent. Neither has
> qualifying independent current-head approval or terminal hosted checks, and
> neither is protected-main or deployed evidence.
> validation after moving its colliding TLS ADR to Proposed ADR 0366. Its
> repository-local Tests, PROV-O, and Ontology Pages runs are successful, while
> central Security/CodeQL/SAST remain queued. #909 is Draft because #922's
> eight-locale published-resource cutover and the required current-head
> material-UI/runtime evidence are still absent. Neither has qualifying
> independent current-head approval, and neither is protected-main or deployed
> evidence.
>
> Historical baseline overlays through the preceding snapshot are preserved as
> dated evidence at
Expand All @@ -31,10 +52,16 @@
> The buyer-visible gap in #922 remains open. Protected `main` still ships the
> production frontend translation source in `frontend/src/i18n.ts` with only
> `en/ko/zh/ja/vi`; `es/de/fr` are not first-class frontend locales. No material
> SPA screen has yet been cut over to a published eight-locale ledger resource,
> and there is no exact-head desktop/mobile evidence covering normal, loading,
> empty, error, permission, responsive, keyboard/focus/screen-reader, CJK text
> expansion, or font fallback states.
> SPA screen has yet been released on a published eight-locale ledger resource.
> The stacked Customer Master candidate covers API admission plus loading and
> retry rendering. The 1440×900 and 390×844 Storybook captures were regenerated
> and inspected after the current auth-bound/cause-neutral repair. They prove
> only the synthetic retry shell, not authenticated browser acceptance. The
> candidate does not contain reviewed eight-locale
> product copy or authenticated PostgreSQL normal/empty/permission evidence.
> There is no release evidence for normal, loading, empty, error, permission,
> responsive, keyboard/focus/screen-reader, CJK text expansion, or font fallback
> states.
>
> Do not synthesize translations and do not count English fallback as translated
> coverage. Ontology labels and concept names remain outside this presentation
Expand Down Expand Up @@ -77,6 +104,12 @@
incomplete requested-locale copy maps to 409. Unsupported locale, malformed
screen identity, and an unrepresentable resource version each map to a
distinct 422 response that tells the caller which request value to correct.
- The Customer Master consumer does not reinterpret those backend failure
categories when the fetch promise is caught generically. Until a typed
frontend failure contract is introduced, its retry shell says only that the
selected-language screen could not be loaded, retries the request first, and
asks an administrator to check access and publication status only if the
failure persists. It does not assert that publication is missing.
- Focused HTTP and asyncpg-boundary tests cover the route without adding a
direct `psycopg2` caller. The documentation-alignment contract prevents this
baseline from regressing to the obsolete claim that the API does not exist.
Expand All @@ -100,18 +133,24 @@
- None of the above is release evidence until the unchanged exact PR head has
terminal required/security checks and qualifying independent approval, then
reaches protected `main` normally.
- The stacked Customer Master consumer has no new ADR number, migration, API
route, schema object, or release number. It extends ADR 0362 and consumes the
route owned by #929, avoiding collisions with ADRs 0364–0366 and the
serialized report-release stack.

## Next buyer cut

1. Use reviewed product copy to create and publish one complete screen resource
for all eight locales. Do not invent copy to satisfy coverage.
2. Cut one material SPA screen off bundled `TRANSLATIONS` and onto the versioned
API. Customer Master is the natural first slice because #922 gates its open
material-UI work, but the screen identity must follow the actual product
composition contract rather than creating a second domain owner.
2. Finish the stacked Customer Master cutover by publishing reviewed product
copy for its declared keys in all eight locales and proving the authenticated
PostgreSQL/API normal path. The consumer and fail-closed loading/retry gate
exist only as branch evidence.
3. Prove normal/loading/empty/error/permission/responsive states plus
keyboard/focus/screen-reader behavior, CJK rendering, text expansion, and
font fallback on the same exact head with fresh desktop and mobile evidence.
Include the small loading/retry shell in locale and text-expansion review;
its English source copy is not evidence of eight-locale behavior.
4. Converge PRD/TRD/ARCHITECTURE/UX/OPERABILITY/TEST_STRATEGY/CHANGELOG and this
baseline with the actual cutover. Keep ontology labels separate from product
copy and consume only released owner contracts where another CWL product is
Expand Down Expand Up @@ -164,4 +203,11 @@
`tests/test_translation_api_driver_boundary.py`,
`tests/test_translation_cache_timeout.py`, and
`tests/test_translation_documentation_alignment.py`.
- Stacked Customer Master consumer: `frontend/src/api.ts`,
`frontend/src/i18n.ts`, `frontend/src/App.tsx`,
`frontend/src/components/ScreenTranslationGate.tsx`,
`frontend/src/components/ScreenTranslationGate.test.tsx`, and
`tests/test_customer_master_translation_auth_gate_contract.py`; current-head
synthetic visual evidence is
`docs/screenshots/customer-master-translation-gate-{desktop,mobile}.png`.
- Historical delivery/gap overlays: `docs/product-technical-gap-baseline-history-2026-09-04.md`.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/storybook-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ operator-facing control you can click before changing product CSS.
| `Admin/AdminPanel` | Change the tenant brand name, then verify the saved or failed state before leaving settings. | `--surface`, `--border`, `--space-panel-block`, `AdminPanel` |
| `Lineage/LineageDag` | Open a reconstructed connection to read its inferred channel scores and Allen interval relation, or open the current branch node; compare empty, single-branch, grouped/forked, mobile-scroll, ungrouped, and long-title states before changing graph CSS. On narrow viewports, swipe the named viewport or focus it and use arrow keys to inspect the full lineage. | `--color-accent-background`, `--radius-control`, `--surface`, `--border`, `--color-focus-border`, `--size-control-min`, `LineageDag` |
| `Chrome/StatusNotice` | Read success, unavailable, or retry copy, then take the named next action. Success and unavailable are a named region (not live `role=status`); Retry is `role=alert` and only on the retry kind. Calendar's missing Naruon projection uses unavailable. | `--badge-status-success-*`, `--badge-status-pending-*`, `--badge-status-danger-*`, `StatusNotice` |
| `Chrome/Screen translation gate` | Wait for the selected-language screen or retry after an administrator publishes its complete copy. The gated destination never flashes bundled screen copy. | `StatusNotice`, `ScreenTranslationGate` |
| `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` |
| `Workspace/WorkspaceCalendar` | Read observed Naruon events, or open a commitment to land on that post. Fail-closed copy stays `이 범위의 일정을 아직 받을 수 없습니다`. | `--color-chip-border`, `WorkspaceCalendar`, `EvidenceStatusMark` |
| `Ask Agent/Public claim verification` | Compare supported, refuted, and not-enough-information states; open only the external evidence link, then review the separate internal citation before changing governed graph state. | `--space-panel-block`, `--space-control-gap`, `--color-border`, `--size-control-min`, `PublicClaimVerification` |
Expand Down
17 changes: 16 additions & 1 deletion frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import App, { SurfaceBoundary } from "./App";
import { optionalKnowledgeCutoffIso } from "./api";
import { setLocale } from "./i18n";
import { CUSTOMER_MASTER_TRANSLATION_KEYS, clearCustomerMasterTranslations, setLocale } from "./i18n";
import { OIDC_RETURN_URL_STORAGE_KEY } from "./oidcReturnUrl";

const signinRedirect = vi.fn();
Expand All @@ -15,6 +15,7 @@ vi.mock("react-oidc-context", () => ({
}));

beforeEach(() => {
clearCustomerMasterTranslations();
setLocale("en");
signinRedirect.mockReset();
signoutRedirect.mockReset();
Expand All @@ -39,6 +40,7 @@ it("normalizes valid knowledge cutoffs and rejects invalid input", () => {
});

afterEach(() => {
clearCustomerMasterTranslations();
vi.unstubAllGlobals();
window.history.replaceState({}, "", "/");
window.sessionStorage.clear();
Expand Down Expand Up @@ -1905,6 +1907,19 @@ describe("App, authenticated", () => {
}),
);
}
if (url.includes("/api/translations/customer-master?") && method === "GET") {
const translations = Object.fromEntries(
CUSTOMER_MASTER_TRANSLATION_KEYS.map((key) => [key, key]),
);
return Promise.resolve(
jsonResponse({
screen_key: "customer-master",
resource_version: 1,
locale: new URL(url).searchParams.get("locale") ?? "en",
translations,
}),
);
}
if (url.endsWith("/api/customer-master") && method === "GET") {
return Promise.resolve(
jsonResponse({
Expand Down
41 changes: 40 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import {
type VocEvidence,
type SimilarVocItem,
fetchTenantConfig,
fetchTranslationScreen,
} from "./api";
import { CitationChip } from "./components/CitationChip";
import { PublicClaimVerification } from "./components/PublicClaimVerification";
Expand Down Expand Up @@ -115,10 +116,13 @@ import {
LOCALE_LABELS,
SUPPORTED_LOCALES,
setLocale,
setCustomerMasterTranslations,
clearCustomerMasterTranslations,
t,
tf,
useLocale,
} from "./i18n";
import { ScreenTranslationGate } from "./components/ScreenTranslationGate";
import "./App.css";

const AdminPanel = lazy(() => import("./components/AdminPanel").then((module) => ({ default: module.AdminPanel })));
Expand Down Expand Up @@ -4757,6 +4761,11 @@ function CustomerMasterPanel({
}: {
accessToken: string;
}) {
const locale = useLocale();
const [copyState, setCopyState] = useState<"loading" | "ready" | "retry">("loading");
const [copyLocale, setCopyLocale] = useState<string | null>(null);
const [copyAccessToken, setCopyAccessToken] = useState<string | null>(null);
const [copyAttempt, setCopyAttempt] = useState(0);
const [master, setMaster] = useState<CustomerMasterResponse | null>(null);
const [error, setError] = useState<string | null>(null);
const [expandedEntityId, setExpandedEntityId] = useState<string | null>(null);
Expand Down Expand Up @@ -4796,9 +4805,33 @@ function CustomerMasterPanel({
}, [accessToken]);

useEffect(() => {
let active = true;
setCopyState("loading");
setCopyLocale(null);
setCopyAccessToken(null);
clearCustomerMasterTranslations();
fetchTranslationScreen(accessToken, "customer-master", locale)
.then((screen) => {
if (!active || screen.screen_key !== "customer-master" || screen.locale !== locale) return;
setCustomerMasterTranslations(screen.translations);
setCopyLocale(locale);
setCopyAccessToken(accessToken);
setCopyState("ready");
})
.catch(() => {
if (active) setCopyState("retry");
});
return () => {
active = false;
clearCustomerMasterTranslations();
};
}, [accessToken, locale, copyAttempt]);

useEffect(() => {
if (copyState !== "ready" || copyLocale !== locale || copyAccessToken !== accessToken) return;
setMaster(null);
void loadMaster();
}, [loadMaster]);
}, [accessToken, copyAccessToken, copyLocale, copyState, loadMaster, locale]);

useEffect(() => {
if (!selectedPostId) {
Expand Down Expand Up @@ -4854,6 +4887,12 @@ function CustomerMasterPanel({
}
}

if (copyState === "retry") {
return <ScreenTranslationGate state="retry" onRetry={() => setCopyAttempt((attempt) => attempt + 1)} />;
}
if (copyState === "loading" || copyLocale !== locale || copyAccessToken !== accessToken) {
return <ScreenTranslationGate state="loading" />;
}
Comment thread
seonghobae marked this conversation as resolved.
return (
<section className="workspace-destination" aria-labelledby="customer-master-heading">
<p className="section-eyebrow">{t("Authorized customer scope")}</p>
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
fetchOccupationRatingSources,
fetchOccupationRatings,
fetchOperationsDashboard,
fetchTranslationScreen,
fetchRatingSourceOccupations,
updateTenantConfig,
} from "./api";
Expand All @@ -14,6 +15,19 @@ afterEach(() => {
});

describe("backendFetch provider-error boundary", () => {
it("requests one authenticated screen and locale without a bundled corpus", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ screen_key: "customer-master", resource_version: 3, locale: "de", translations: { title: "Kundenstamm" } }), {
headers: { "Content-Type": "application/json" },
}),
);
vi.stubGlobal("fetch", fetchMock);

await fetchTranslationScreen("access-token", "customer-master", "de");

expect(fetchMock.mock.calls[0][0]).toContain("/api/translations/customer-master?locale=de");
expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe("Bearer access-token");
});
it("binds the selected Dashboard period as inclusive API dates", async () => {
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ cases: [] }), { headers: { "Content-Type": "application/json" } }),
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,26 @@ export interface PostPage {
visibility_options?: PostFilterOption[];
}

export interface TranslationScreenResponse {
screen_key: string;
resource_version: number;
locale: string;
translations: Record<string, string>;
}

/** Fetch the latest complete published copy for one authenticated screen. */
export function fetchTranslationScreen(
accessToken: string,
screenKey: string,
locale: string,
): Promise<TranslationScreenResponse> {
const query = new URLSearchParams({ locale });
return backendFetch(
`/api/translations/${encodeURIComponent(screenKey)}?${query.toString()}`,
accessToken,
);
}

export interface OperationsDashboardFact {
fact_type_code: string;
fact_type_label: string;
Expand Down
18 changes: 18 additions & 0 deletions frontend/src/components/ScreenTranslationGate.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { ScreenTranslationGate } from "./ScreenTranslationGate";

const meta = {
title: "Chrome/Screen translation gate",
component: ScreenTranslationGate,
args: { onRetry: () => undefined },
} satisfies Meta<typeof ScreenTranslationGate>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Loading: Story = { args: { state: "loading" } };
export const Retry: Story = { args: { state: "retry" } };
export const RetryMobile: Story = {
args: { state: "retry" },
parameters: { viewport: { defaultViewport: "mobile1" } },
};
Loading