feat: let Studio create the topics and projects publishing requires
Publishing needs a topic and nothing could create one. The backend now owns that surface; this is its consumer — the management contract vendored, a gateway over its nine operations, and one Studio screen that lists, creates, and deletes topics and projects. The screen adds no CSS. It reuses the classes the working-copy list already uses, so it inherits Studio's spacing, type, and colour rather than growing a second visual vocabulary beside them. Scope stops at list/create/delete: renaming, phase changes, and visibility are implemented in the backend and declared in the contract, but their screens are a separate design. Two real defects surfaced while making the public port async, and both would have shipped: The search page and the header search dialog shared a query key. With an empty query, `["tech-log","search",""]` was identical for both, so react-query handed one surface the other's cache — different shapes — and the page died reading a field that was not there. Keys now name the surface. The explore filter's selects are uncontrolled and read `defaultValue`, which React applies once. Their options arrive later now, so the first render had nothing to match and the value stayed empty: a topic in the URL no longer showed as selected. The form key includes whether the catalog has arrived, so it remounts with the options present. Controlled inputs would be the other answer, but this form submits to build a URL — the URL owns the value. The route brought its own bookkeeping: a build chunk, a manual accessibility evidence file, and the CI artifact baseline that counts them. The gate pins a digest of its own shape precisely so a new route cannot slip in without that count being reviewed. Test harnesses that render public screens now assemble the query providers and await the settled paint, because the screens they render became async.
This commit is contained in:
@@ -459,7 +459,12 @@ const CANONICAL_GATE_SHAPE_SHA256 =
|
||||
// Dev release manifest drift fix, item 2: recomputed again after FE-GATE-010
|
||||
// gained `check-dev-release-manifest`. Same method — 98d19911… was first
|
||||
// reproduced from the previous gates.json before this value was hashed.
|
||||
"b40962448e617883060e09eb7183837cfea6833519f435f11713efd083210dbe";
|
||||
// Taxonomy route: recomputed again after FE-GATE-009 gained the
|
||||
// TECH_LOG_STUDIO_TAXONOMY manual accessibility evidence artifact. The gate
|
||||
// lists one evidence artifact per installed route and refuses a set that does
|
||||
// not match the route scope exactly, so adding a route necessarily moves this
|
||||
// digest — that is the point of pinning it.
|
||||
"8c73d4477392a63e91088ff91e53293e7fc3ace891f8eb3da3b0d05fa714df60";
|
||||
|
||||
function canonicalGateShapeSha256(gates: CiGateContract["gates"]): string {
|
||||
const normalized = gates.map(
|
||||
@@ -512,8 +517,10 @@ function canonicalAuthorityBaselineFailures(contract: CiGateContract): string[]
|
||||
// Template merge. 126 product artifacts plus the two the template added.
|
||||
// Task 11 added one more: the TECH_LOG_STUDIO_ASSETS manual a11y evidence file.
|
||||
// Alignment follow-up, item 2 added the TechLog junit report.
|
||||
if (contract.artifacts.length !== 130) {
|
||||
failures.push(`artifact authority baseline must contain exactly 130 artifacts; received ${contract.artifacts.length}`);
|
||||
// The taxonomy route added its own manual a11y evidence file — every installed
|
||||
// route carries one, and the gate checks that the two sets match exactly.
|
||||
if (contract.artifacts.length !== 131) {
|
||||
failures.push(`artifact authority baseline must contain exactly 131 artifacts; received ${contract.artifacts.length}`);
|
||||
}
|
||||
if (contract.stages.length !== 5) {
|
||||
failures.push(`stage authority baseline must contain exactly 5 stages; received ${contract.stages.length}`);
|
||||
|
||||
@@ -50,6 +50,14 @@ const CONTRACTS: readonly ContractTarget[] = Object.freeze([
|
||||
generated: "src/features/tech-log/contracts/public/generated.ts",
|
||||
sourceRecord: "src/features/tech-log/contracts/public/canonical-source.json",
|
||||
}),
|
||||
Object.freeze({
|
||||
name: "management",
|
||||
packageId: "@tech-log/management-contract",
|
||||
canonicalYaml: `${CANONICAL_ROOT}/contracts/openapi/studio-management-v1.yaml`,
|
||||
vendorYaml: "src/features/tech-log/contracts/management/management-api.openapi.yaml",
|
||||
generated: "src/features/tech-log/contracts/management/generated.ts",
|
||||
sourceRecord: "src/features/tech-log/contracts/management/canonical-source.json",
|
||||
}),
|
||||
]);
|
||||
|
||||
const OPENAPI_TYPESCRIPT = "openapi-typescript@7.9.1";
|
||||
|
||||
@@ -56,6 +56,7 @@ const studioSpaPathPatterns = Object.freeze([
|
||||
// reload of /studio/assets was served the in-shell Studio 404 -- the screen
|
||||
// was only reachable by client-side navigation from another Studio page.
|
||||
"^/studio/assets$",
|
||||
"^/studio/taxonomy$",
|
||||
"^/studio/documents$",
|
||||
"^/studio/documents/new$",
|
||||
"^/studio/documents/[^/]+/(edit|validation|preview|publish)$",
|
||||
|
||||
Reference in New Issue
Block a user