The CSS layer of the SDK is governed by four doctrines, each pinned earlier in the Step 6 thread, consolidated here for reference.
Vibe apps consume SDK components and CSS classes. They do not import third-party UI libraries, embed inline custom styles beyond what the SDK permits, or extend the SDK's visual vocabulary outside an explicit doctrine PR. Apps that need a capability the SDK doesn't ship propose it as a platform addition that everyone inherits, not as an app-local extension that diverges. The platform retains the right to evolve the design system without breaking apps because apps never reach past the SDK boundary into the design.
Vibe apps consume CSS exclusively through external <link> to SDK stylesheets. Inline style="..." attributes, <style> blocks in HTML, and JS direct style assignment (element.style.X = ...) are prohibited for color, spacing, typography, and layout concerns. The narrow exception: runtime-computed positional or transform values for behaviors that cannot be expressed as static CSS classes (drag operations, measurement passes, cursor-anchored positioning).
The operational payoff: changing --pc-blue in design-tokens.css from #0094d9 to #005f99 updates every button, link, focus ring, and accent across every vibe app instantly. Zero per-app refactor.
The SDK is distributed at versioned paths (/sdk/v{major}/...). Each app copies the SDK version it was deployed with into its own S3 bucket at apply time. Within a major version, CSS classes' visual behavior is never edited, deleted, or renamed — only added to. New visual variants are new class names; design changes are new additions, not silent edits. Apps that don't redeploy continue to render exactly as they did at their deploy date, indefinitely.
The unversioned facade class (e.g., .pc-btn-primary) is deliberately mutable; it tracks the SDK release's chosen "current" version. Versioned classes (.pc-btn-primary-v1, -v2, etc.) are append-only, never changed. Apps choose: reference the facade to opt into visual evolution; reference a -vN class to pin to a specific visual forever.
Vibe app developers propose new visual components or CSS classes as part of the App Review PR process. The UI team reviews and either:
vibecode/sdk/v{N}/components.css as a -v1 variant. Becomes available to every vibe app; append-only forever within the SDK major version.app.css, scoped exclusively to the app's body class (.pc-app-{name} parent selector). Not visible to other apps. The app accepts a long-term maintenance burden in exchange for keeping a pattern that doesn't generalize.| File | Size | Contains |
|---|---|---|
sdk/v1/design-tokens.css | ~2KB | The :root { --pc-* } custom properties from CLAUDE.md — colors, typography scale, spacing, border radii. Single source of truth for the design system's values. |
sdk/v1/components.css | ~6KB | Component classes: .pc-btn-*, .pc-input, .pc-data-table, .pc-sidebar, .pc-app-menu, .pc-badge-*, .pc-streaming-message, etc. Each in both facade and -v1 forms per §17. |
sdk/v1/utilities.css | ~3KB | Small set of layout utilities: .pc-flex, .pc-stack-{xs,sm,md,lg}, .pc-grid-cols-{2,3,4}, .pc-text-{xs,sm,md}, .pc-hidden, .pc-mobile-only. Composable; not Tailwind. |
sdk/v1/assets/paycargo-logo.svg | ~9KB | Canonical PayCargo logo, white variant (paths recolored at render time for blue / sidebar variants). |
sdk/v1/themes/{default,exec,partner}.css | ~1KB each | Optional theme variants. Apps that need different accent colors consume a theme file in addition to the base; theme overrides happen via custom property reassignment at body.pc-app-{name} scope. |
Total CSS shipped per app: ~11KB minified, ~3KB gzipped. App pages add four <link> tags (tokens + components + utilities + Font Awesome kit) and consume from there.
/* sdk/v1.0/components.css */
.pc-btn-primary-v1 {
background: var(--pc-accent-orange);
height: 44px;
border-radius: 3px;
}
/* The facade — mirrors the SDK's "current" variant.
At v1.0 there's only v1, so it equals v1. */
.pc-btn-primary {
background: var(--pc-accent-orange);
height: 44px;
border-radius: 3px;
}
/* sdk/v1.3/components.css */
.pc-btn-primary-v1 { /* UNCHANGED from v1.0 — Lotus Notes durability */
background: var(--pc-accent-orange);
height: 44px;
border-radius: 3px;
}
.pc-btn-primary-v2 { /* added in v1.1 — NEVER edited again */
background: var(--pc-accent-orange);
height: 48px;
border-radius: 6px;
font-weight: 600;
}
.pc-btn-primary-v3 { /* added in v1.3 — current */
background: var(--pc-accent-orange);
height: 48px;
border-radius: 8px;
font-weight: 600;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
/* Facade now mirrors v3 */
.pc-btn-primary {
background: var(--pc-accent-orange);
height: 48px;
border-radius: 8px;
font-weight: 600;
box-shadow: 0 1px 2px rgba(0,0,0,0.08);
}
| App's HTML | Pin semantic | When SDK upgrades |
|---|---|---|
class="pc-btn-primary" | ^ — give me current | Re-deploy fetches the new SDK and the button gets the new visual |
class="pc-btn-primary-v2" | exact pin | Re-deploy doesn't change anything; visual is locked to v2 forever within v1.x |
Most apps use the facade. Apps with strict visual contracts (customer-facing brand-book commitments) pin to versioned classes.
| npm semver | SDK CSS equivalent | Meaning |
|---|---|---|
^1.2.3 (caret) | .pc-btn-primary (facade) | "Give me the latest non-breaking version" |
1.2.3 (exact) | .pc-btn-primary-v2 | "Give me exactly this version, forever" |
~1.2.3 (tilde) | (not provided in v1) | "Accept patch updates only" — no clean analog; added by doctrine PR if a real need emerges |
Updates ripple at app re-deployment, not continuously — the Terraform apply is the equivalent of npm install. The SDK file in each app's S3 bucket is the lockfile.
When a vibe app needs visual customization that doesn't fit the SDK's defaults, it consumes a per-app stylesheet at cartridges/<dept>/<app>/app.css.
Allowed in app.css | Prohibited in app.css |
|---|---|
Layout overrides scoped to body.pc-app-{name} | New component class definitions (those go through promotion review) |
| CSS custom property overrides scoped to the body class | Global overrides of SDK classes (anything not scoped to the body class) |
| App-specific media-query layouts | Color / typography / spacing decisions that should be design tokens |
| One-off keyframe animations for app-specific behaviors | New keyframe animations for general UI behaviors (those promote to SDK) |
Example app.css for the chatbot:
/* cartridges/engineering/chatbot/app.css */
/* Layout: thread list on left, message pane on right */
body.pc-app-chatbot main {
display: grid;
grid-template-columns: 280px 1fr;
gap: 0;
}
/* Minor theme override: slightly softer chat bubble background */
body.pc-app-chatbot {
--pc-chat-bubble-bg: #f0f9ff;
}
That's it. The chatbot's app.css contains only layout that's structurally specific to the chatbot and one custom property override. No new components; no overrides of platform classes outside the body scope.
| Stage | Owner | Authority |
|---|---|---|
| Propose new CSS | Vibe app developer | "I think the platform needs this component" — submitted as part of the App Review PR |
| Review | UI team | "Promote / app-local / reject" |
| If promoted: add to SDK | UI team | Edits vibecode/sdk/v1/components.css; new class enters Lotus Notes durability immediately |
If app-local: stays in app.css | Vibe app developer | Scoped to body.pc-app-{name}; never consumable by other apps |
| Consume | Vibe app developer | Imports SDK classes; reads design tokens; no inline styles |
## CSS / design changes
Pick one:
- [ ] No new CSS introduced — app consumes existing SDK classes only.
- [ ] New SDK promotion proposed (UI team review required).
- [ ] App-local additions in app.css only (UI team review still required, just for boundary check).
If proposing SDK promotion:
| Proposed class | Component purpose | Reusability rationale |
|----------------------------|-----------------------------------------|-----------------------------------------------------------------|
| .pc-streaming-message-v1 | Animated typing chatbot bubble | Every future chat/assistant vibe app will need this |
| .pc-chat-bubble-v1 | User/assistant chat message container | Chat shape is structurally similar across vibe apps |
UI team reviewer checklist:
- [ ] Does this fit the platform's visual vocabulary?
- [ ] Is this likely to be needed by future vibe apps?
- [ ] Is the class naming aligned with existing patterns (.pc-{component}-v{N})?
- [ ] Does it work with existing design tokens, or does it require new ones?
- [ ] Decision: PROMOTE / KEEP APP-LOCAL / REJECT
| Mistake | Caught by | Operator sees |
|---|---|---|
Developer adds inline style="color: red" to an app's HTML | PR linter (Phase D); discipline + review for now | PR comment: "§16 violation — use a design token or SDK class" |
| Developer imports Tailwind / Bootstrap / Bulma into their app | App Review PR template + dependency audit | PR rejected with §15 reference |
Developer adds a new .pc-shiny-button class to their app.css | UI team review | Reviewer asks: "Promote to SDK or scope to body.pc-app-{name}?" |
UI team edits .pc-btn-primary-v1 to change its background color | Doctrine PR review | Doctrine PR rejected: §17 violation — -v1 classes are append-only; add -v2 instead |
Developer overrides .pc-btn-primary globally in their app.css | UI team review | Reviewer demands the override be scoped to body.pc-app-{name} or proposed as a theme variant |
| App's CSS tries to import a third-party stylesheet via @import | App Review PR + dependency audit | PR rejected with §15 reference; if legitimate need (e.g., a calendar widget), triggers doctrine PR for a Tier 2 addition |
With one platform-owned design language and one set of versioned components, the platform can evolve the visual identity globally with a single PR. Without these doctrines, every vibe app drifts independently; by app number 10 the platform looks like ten different platforms; by app 20 the "PayCargo brand" is unrecognizable.
An obs app deployed at SDK v1.0 in week 1 will render exactly the same way at SDK v1.5 in week 20 — as long as it doesn't redeploy. New vibe apps shipped at v1.5 get the latest facade. Both can coexist; neither is forced to migrate. Predictability buys trust.
CSS bundle size grows over time (no deletions). For 60 days of additions, the bundle goes from ~11KB to maybe ~25KB minified. Still small. After 2 years, with the right discipline on facade promotions, it stays under ~50KB. If it ever exceeds that, the platform has earned a major-version bump (v2) where deprecation becomes possible.