/** * RECORDED EXCEPTION to the Framework-Component Test (gh#814). * * `docs/COMPOSITION-VS-COMPONENT.md` §2 says all seven criteria must PASS and any FAIL makes a * thing a composition pattern. This file FAILS two of them, and stays anyway: * * C2 (encapsulates reusable BEHAVIOR) — FAIL. It owns no state, no keyboard handling and no * focus management; its only ARIA is three static attributes. §2 calls this "pure static * layout/visual arrangement". * C3 (not expressible by composing primitives) — FAIL. Its own imports are the answer: this is * `Card` + `CardContent` + `Badge` + one Lucide glyph, and §2's heuristic — "could I build * this right now from existing primitives + token overrides?" — is answered yes. * * It is RETAINED because `src/components/layout/app-launcher.tsx` consumes it: it is an internal * building block of a component that does pass the test, so deleting it is not on the table, and * removing a public export is breaking. Keeping it public was the cheaper call. * * SO: a consumer reaching for a service tile should compose `Card` + `CardContent` + `Badge` * themselves, which is what C3 says. Reach for `ServiceLauncherCard` only to match `AppLauncher`'s * own tiles. Do not cite it as precedent for adding another static tile to `src/components/` — * the test still means what it says, and this is the exception that was argued and written down * rather than the rule. * * Ledger, options considered and the decision: gh#814. */ import * as React from "react"; import type { LucideIcon } from "lucide-react"; import type { HeadingLevelProp, ToneProp } from "../../props/vocabulary/index.js"; export type ServiceLauncherStatusTone = Extract; export interface ServiceLauncherCardProps extends Omit, "title"> { /** * The generic glyph for this KIND of service. Stays REQUIRED and stays a `LucideIcon`: every * service has one, including a service created ten seconds ago that has no logo yet, and it is * the thing `logo` falls back to. See the `logo` note for why this was not widened to a union. */ icon: LucideIcon; /** * URL of the service's OWN uploaded mark — the PNG/WebP an administrator uploaded (gh#850). * When it loads, it replaces `icon` in the medallion, in the glyph's exact box. * * It is a fallback chain, not a switch: the tile shows `icon` while the URL is in flight, and * KEEPS showing `icon` if the URL 404s, is empty, or decodes to nothing. A launcher never * degrades to an empty medallion or a torn-page glyph because a logo went missing — which is the * only state that reaches production, since a logo path outlives the file it points at. * * Decorative, and deliberately has no `logoAlt` companion: the medallion is `aria-hidden` and the * service NAME sits beside it. A screen reader must not hear "Attendance" twice (WCAG 2.2 · 1.1.1 * — an image adjacent to text conveying the same thing is decorative). */ logo?: string; title: React.ReactNode; titleLevel?: HeadingLevelProp; statusLabel?: React.ReactNode; statusTone?: ServiceLauncherStatusTone; description?: React.ReactNode; metadata?: React.ReactNode; action: React.ReactNode; disabledReason?: React.ReactNode; } /** * Token-owned launcher surface for an organization-scoped downstream service. Consumers supply * real status, metadata and actions; access is NEVER inferred — the component derives no * entitlement, no launch URL and no disabled state of its own. */ export declare const ServiceLauncherCard: React.ForwardRefExoticComponent>; export interface ServiceCatalogCtaProps extends Omit, "title"> { icon?: LucideIcon; title: React.ReactNode; action: React.ReactNode; } /** Dashed companion tile for the real catalog/add action beside launcher cards. */ export declare const ServiceCatalogCta: React.ForwardRefExoticComponent>; export interface ServiceLauncherCardSkeletonProps extends React.HTMLAttributes { label: string; } /** Shape-matched loading placeholder for a launcher card. */ export declare function ServiceLauncherCardSkeleton({ className, label, ...props }: ServiceLauncherCardSkeletonProps): React.JSX.Element;