diff --git a/.changeset/home-ai-studio-flag-5521.md b/.changeset/home-ai-studio-flag-5521.md new file mode 100644 index 000000000..411aa0a99 --- /dev/null +++ b/.changeset/home-ai-studio-flag-5521.md @@ -0,0 +1,34 @@ +--- +'@object-ui/app-shell': minor +--- + +Console Home stops offering the metadata-authoring front door on a deployment +whose own runtime config says authoring is not offered there (objectui#5521). + +The "Build an app" cover card is now withheld when +`GET /api/v1/runtime/config` reports `features.aiStudio: false`. On the composed +hosted-SaaS shape that card led a plain tenant into the full authoring flow +behind a runtime whose `/api/v1/meta/*` answers `403` and whose ToolRegistry +holds zero authoring handlers — the entry was offered and the refusal arrived at +submit. The lockdown criterion for that shape is two-part, UI entry hidden AND +API refused; only the backend half was green. + +- The card is **hidden, not dimmed**, because that is the flag's own declared + meaning on both sides of the wire: `RuntimeFeatures.aiStudio` documents "when + false, the SPA hides the AI authoring affordances", and the serving plugin + documents "set false to force-hide the authoring UI". +- `features.marketplace` keeps the different presentation objectui#5504 gave it + — a dimmed card plus a visible localized reason. That flag means a route is + reachable; this one means force-hide. "Start with a template" is untouched: + installing a marketplace package is not AI metadata authoring and answers to + its own flags. +- No reason line is rendered in its place. `home.build.noCapability` says the + *account* lacks "Manage Metadata"; on a runtime with no authoring at all the + surface is absent for everyone, and pointing a viewer at a permission that + would not help them is the misdirection objectui#5557 is about. +- Unknown fails **OPEN** (`!== false`), the doctrine `isMarketplaceEnabled()` + already encodes: a runtime predating the flag, or one whose config fetch + failed, keeps the card exactly as visible as before. + +No new authorable config key, no new server surface, and no new copy — the flag +was already being served and already reaches the browser. diff --git a/packages/app-shell/src/console/home/HomePage.tsx b/packages/app-shell/src/console/home/HomePage.tsx index ae6a26841..2ad50ef7c 100644 --- a/packages/app-shell/src/console/home/HomePage.tsx +++ b/packages/app-shell/src/console/home/HomePage.tsx @@ -39,7 +39,7 @@ import { Sparkles, ShieldAlert, X, UploadCloud, MessageSquareText, Hammer, Layou import { useMetadataClient } from '../../views/metadata-admin/useMetadata.js'; import { usePublishAllDrafts } from '../../preview/usePublishAllDrafts.js'; import { resolveAiApiBase } from '../../hooks/useAiSurface.js'; -import { isMarketplaceEnabled } from '../../runtime-config.js'; +import { getRuntimeConfig, isMarketplaceEnabled } from '../../runtime-config.js'; /** * Which AI home CTAs to surface, driven by the live agent catalog (the single @@ -379,6 +379,26 @@ export function HomePage() { // used to recommend it first and error afterwards; that ORDERING was the // injury, so the recommendation goes when the capability does. const marketplaceEnabled = isMarketplaceEnabled(); + // objectui#5521 — the DEPLOYMENT's own answer to "is metadata authoring + // offered here at all", distinct from the per-principal `canAuthorMetadata` + // above. `features.aiStudio` is derived server-side from the same resolution + // that decides whether the authoring agent is mounted, so on the composed + // hosted-SaaS shape it arrives `false` while the ToolRegistry holds zero + // authoring handlers and `/api/v1/meta/*` answers 403. + // + // Read inline rather than through a new accessor, so this card adds no + // export; lifting this and `ChatDock`'s identical read onto an + // `isAiStudioEnabled()` sibling of `isMarketplaceEnabled()` is filed as a + // follow-up rather than done here, where it would mean editing four + // neighbouring suites' module mocks. + // + // `features?.` and `!== false` are both load-bearing and are copied from + // `isMarketplaceEnabled()`'s body rather than invented: hosts (and four + // sibling suites) supply a runtime-config snapshot carrying only `branding`, + // so `features` is genuinely absent on real code paths — and an absent flag + // must fail OPEN. Withholding the product's front door over an unanswered + // question is the worse direction; the server refuses the write regardless. + const aiStudioEnabled = getRuntimeConfig().features?.aiStudio !== false; // Shown wherever an authoring entry point is withheld, so the posture is // explained on screen instead of surfacing as a refusal after a filled-in // dialog. Localized in all ten packs. @@ -552,17 +572,42 @@ export function HomePage() { {isAdmin && (
- navigate('/studio')} - disabled={!canAuthorMetadata} - disabledReason={authoringGateReason} - testId="home-build-app" - /> + {/* objectui#5521 — HIDDEN, not dimmed, when the runtime reports + * `features.aiStudio: false`. The distinction is the flag's own + * declared meaning, not a presentation preference: `RuntimeFeatures` + * documents it as "when false, the SPA HIDES the AI authoring + * affordances", and the serving plugin as "set false to force-hide + * the authoring UI". Honouring a producer's declared semantics is + * the whole point of reading its flag. + * + * It is also the honest answer here. `!canAuthorMetadata` is a fact + * about THIS PRINCIPAL — a dimmed card plus a reason line tells an + * admin something actionable about their own account, which is why + * that case stays disabled-with-reason. `aiStudio: false` is a fact + * about the DEPLOYMENT: authoring exists for nobody here, there is no + * permission to acquire and no admin to ask, so a permanently greyed + * front door would advertise a room that was never built. That is the + * misdirection class objectui#5557 names — reporting the wrong KIND + * of answer — applied to a card instead of a page. + * + * Deliberately NOT extended to "Start with a template": installing a + * marketplace package is not AI metadata authoring, it has its own + * flags (`features.marketplace` / `installLocal`), and objectui#5504 + * already ruled disable-and-explain for it. Its flag means + * reachability; this one means force-hide. */} + {aiStudioEnabled && ( + navigate('/studio')} + disabled={!canAuthorMetadata} + disabledReason={authoringGateReason} + testId="home-build-app" + /> + )}