Runtime Contract

Stable implementation rules (Part A). The full contract is embedded below as plain text — no JavaScript required. Every Generate AI Prompt copy also embeds the applicable rules inline for AI tools. Markdown mirror · check “Include full contract” in the export dialog for all type mappings.

Getting started — paste into AI

  1. Open the Designer and lay out your panel.
  2. Click Generate AI Prompt (or Export JSON).
  3. Copy the prompt from the export dialog.
  4. Paste into Cursor, Claude, ChatGPT, or Gemini.
  5. Tell the AI your target: SwiftUI, Compose, WinUI, WinForms, or HTML.
  6. Require OS text remasure before shipping (PS-R019 / PS-R020) — helpers in exports/remeasure/.
  7. Set meta.nativeRemeasureDeclared: true only after that remasure pass exists in code.
Open Designer Download .md

# PanelSpec Runtime Contract v4.7

> Stable implementation rules for PanelSpec Designer exports. Each project export links here instead of repeating these rules.

## Instruction priority

When sources conflict (**highest first**):

- **PS-P001** — **PanelSpec JSON** — `pages[].objects`, bounds, `hostRelativeBounds`, navigation, hosting, `renderOrder`, labels on objects
- **PS-P002** — **Layout rules** — coordinate system, safe area, canvas overflow, container hosting
- **PS-P003** — **Required rules** — navigation policy, native chrome, type mapping, output format
- **PS-P004** — **UI Requirements** — structured controls, buttons, outputs, and behavior (must **not** override JSON inventory; every listed control should exist on the canvas)
- **PS-P005** — **Documentation blueprint** — pixel/visual reference for screenshots and polish (must **not** add or remove controls)

JSON wins on placement over UI requirements. Requirements define labels and behavior only.

## Universal rules

- **PS-R001** — Implement every object in `pages[].objects` except `renderAs: "native"` (use `systemChrome` instead).
- **PS-R002** — Canvas `bounds` are always authoritative (full-device coords). For hosted children, `hostRelativeBounds` is also present as a placement aid inside tab/group/card/scroll/split hosts — prefer it for in-host layout; do not treat the two as alternatives that exclude each other.
- **PS-R003** — **Cross-page** navigation **only** via `linkToPageId` on click/tap — `tab-control` header selection alone must not change pages (**PS-R006** governs `tab-bar`).
- **PS-R004** — Tab-control: split `label` on `|`; honor `tabPosition`, `activeTabIndex`, `closable` (default false). Tab children need matching `hostId` + `hostTabIndex`.
- **PS-R005** — Apply authored `zIndex` and `renderOrder` as-is (export does not remap layers); clip or omit `canvasOverflow` objects — do not enlarge the window.
- **PS-R006** — `tab-bar`: **required** app-shell navigation — use `tabItems` (or `label` split on `|`). `implementsAs: "native-shell"` — map to **one** native tab bar (TabView / BottomAppBar / `<nav>`). **Not** the home indicator.
- **PS-R007** — `tab-bar` page routing: when `tabLinkToPageIds` is present, tab index `i` shows PanelSpec page `tabLinkToPageIds[i]` on tap — this **is** navigation for tab-bar (unlike `tab-control`). Use `meta.primaryTabBar` when exported.
- **PS-R008** — **No duplicate tab bars:** implement `tab-bar` **once** as native shell chrome. **Do not** also render the `tab-bar` JSON object as a positioned custom view inside tab content — that creates a double bar. Per-page layouts **exclude** objects with `type: "tab-bar"`.
- **PS-R009** — **`nav-bar` is mandatory per-page chrome** — render every `nav-bar` at JSON `bounds` as a **custom bar** (not `.navigationTitle` alone). Layout: **back chevron leading** (~12px inset), **title centered** in the bar from `label`. When `linkToPageId` is set, back tap navigates there. Honor `placement`. Wireframe fidelity beats native NavigationStack title styling.
- **PS-R010** — **Multi-page apps:** Implement a **separate screen/View per entry in `pages[]`**. Each page renders **only** its own `objects[]` at JSON bounds — **never** reuse Page 1's controls on Page 2/3. Secondary pages require the same layout fidelity as `startPageId`.
- **PS-R011** — **Bounds fidelity (±1px) on the design target:** `bounds` are canvas-absolute for `meta.designCanvas` / `layout.width`×`layout.height`. **Do NOT** add `safeAreaInsets` to coordinates — objects below the status bar already have the correct `bounds.y`. Never reflow chrome into toolbars on the design device.
- **PS-R012** — **Nav-row siblings (`logo`, buttons beside `nav-bar`):** These are **separate JSON objects** — render each at its own `position.center` + `bounds` size. **Forbidden:** `.toolbar`, `.navigationBarTrailing`, `ToolbarItem(placement: .topBarTrailing)`, or HStack+Spacer reflow. Use `trailingInsetPx` from JSON to verify right-edge spacing.
- **PS-R013** — **Responsive web (when `meta.responsiveLayout` is present):** On viewports **≥ breakpoint**, use desktop `bounds` (±1px). **Below breakpoint:** (1) if `layoutConstraints` or `layoutVariants.mobile` exist, apply **`meta.layoutResolve`** — do not invent geometry; (2) otherwise reflow labels/inputs/pickers/buttons to **full content width** with **16–24px** padding and **min-height 44px**, preserving vertical order. **Forbidden:** `transform: scale` of the desktop canvas.
- **PS-R014** — **Orientation policy (when `meta.layoutPolicy` is present):** Honor `supportedOrientations`. If only `portrait`, lock the app to portrait — do not enable landscape. If both orientations are listed, support rotation using `layoutConstraints` / `layoutVariants` via **`meta.layoutResolve`**; otherwise follow `layoutStrategy` and `layoutIntent`.
- **PS-R015** — **Adaptive native (when `layoutStrategy` is `adaptive-native`):** Canonical `bounds` are the **portrait / primary reference**. For other sizes/orientations, prefer `layoutConstraints` and `layoutVariants` resolved with **`meta.layoutResolve`**. Use `layoutIntent` only as a reflow hint when no explicit constraint/variant exists — do not invent pixel frames.
- **PS-R016** — **Object visibility:** When `visibility.orientations`, `visibility.minWidth`, or `visibility.maxWidth` is set, **omit** the object outside those contexts (CSS `display:none`, SwiftUI conditional, etc.). Do not render desktop-only chrome on mobile or landscape-only controls in portrait.
- **PS-R017** — **Layout trust:** Apply **`meta.layoutResolve`** priority exactly. Prefer `layoutConstraints.css` when present. Never invent geometry or multiply Y by width scale. Full formulas: `meta.layoutResolveDocUrl`.
- **PS-R018** — **Layout tree + intrinsics:** When `pages[].layoutTree` is present, use it as the containment source of truth (hosts → children). Absolute `bounds` stay canvas coords; prefer `hostRelativeBounds` inside hosts. Respect measured/wrapped text heights from the adapt recipe — do not clip labels that the preview grew.
- **PS-R019** — **Native text SoT (fail-closed):** Designer widths in `nativeTrust` are **layout floors only**. Remeasure with OS engines before clip: iOS CoreText/UIFont, Android TextPaint/StaticLayout, Windows DirectWrite, web `measureText`. Grow if wrapped height exceeds the JSON frame. Honor NT-001…NT-005.
- **PS-R020** — **Remasure gate:** When `remeasureGate.required` is true, codegen is invalid until OS remasure exists (RG-001…RG-006). Set `meta.nativeRemeasureDeclared: true` only after that pass. Helpers: `exports/remeasure/`.

## Error handling

- **PS-E001** — **Unknown object `type`:** Do **not** invent a replacement control. Emit `// TODO [PS-E001]: PanelSpec unknown type "<type>" (id: <id>)` and continue.
- **PS-E002** — **Missing type mapping:** Use the closest documented native equivalent; note the assumption in `// TODO [PS-E002]: …`.
- **PS-E003** — **Missing optional JSON fields:** Use rule defaults (e.g. `closable: false`, tab position `top`, `activeTabIndex: 0`).
- **PS-E004** — **Ambiguous hosting:** When `hostRelativeBounds` is present, prefer it over re-deriving placement from canvas `bounds`.
- **PS-E005** — **Safe area vs exact bounds:** Keep **exact bounds** from JSON; clip or comment if needed — do not nudge controls unless JSON says compliant.
- **PS-E007** — **boundsValidity.issueKind:** `canvas-overflow` — bounds extend past `layout.width`×`layout.height`; clip or reposition (stale coords), **not** safe-area overlap. `safe-area-inset` — within canvas but overlaps device insets; may be intentional. Prefer `boundsValidity.issueKind` over `safeArea.summary` when they disagree.
- **PS-E006** — **Partial generation failure:** Deliver all successfully mapped objects; list TODOs at the top of the output file with rule IDs.
- **PS-E008** — **Overlapping bounds:** When `boundsOverlap` or `boundsOverlaps` is present, apply the **Overlap resolution** section — do not place both controls at identical coordinates. Stack inputs with 12dp spacing; keep both objects and labels.

## Layout system

- Canvas origin: top-left, unit px.
- `bounds` are canvas-absolute on the full device canvas.
- Canvas `bounds` are always present. Hosted children also include `hostRelativeBounds` for in-host placement.
- Match bounds within **±1px** unless adaptive layout policy says otherwise.
- Do **not** add safe-area insets to `bounds.x/y` — coordinates already account for chrome.
- Apply authored `zIndex` and `renderOrder` as-is (export does not remap); clip `canvasOverflow` — do not enlarge the window.

## Safe area

- `safeArea.contentRect` describes the content region below system chrome.
- Objects with `safeArea.compliant: false` overlap device insets — keep exact bounds; do not invent an “intentional” flag unless the author set one.
- Use native safe-area APIs — do not redraw status bar, home indicator, or browser chrome.

## Validation framework

Before shipping any PanelSpec implementation, confirm:

- [ ] Object and page counts match JSON
- [ ] No extra controls beyond JSON + native `systemChrome`
- [ ] Each `bounds` matches JSON (±1px)
- [ ] `startPageId` is the initial view
- [ ] `linkToPageId` and tab routes wired correctly
- [ ] `hostRelativeBounds` used inside hosts
- [ ] `visual` fills applied where JSON specifies them
- [ ] Project builds / runs without errors
- [ ] Deferrals cite rule IDs: `// TODO [PS-R###]: reason`

---

## Platform — SwiftUI for iOS / iPadOS

**Target:** iOS 17+ / iPadOS 17+

### Platform rules

- Use `@main` SwiftUI App or UIViewControllerRepresentable host as needed.
- Respect `safeAreaInsets` — do NOT draw status bar, Dynamic Island, or home indicator.
- When `tab-bar` is present (**PS-R006** / **PS-R007** / **PS-R008**): root `TabView` with one `.tabItem` per `tabItems` entry; each tab renders the linked page **without** any `tab-bar` objects.
- When `nav-bar` is present (**PS-R009**): custom bar at JSON bounds — title **centered**, back chevron **leading**; wire `linkToPageId`. Do **not** use `.navigationTitle` alone (left-aligns title).
- Implement **every** PanelSpec page and wire all `linkToPageId` routes — the simulator must show the correct page when navigating.
- **Multi-page (PS-R010):** One View struct per page. Never duplicate Page 1 controls onto Page 2/3 — read each page's own `objects[]` from JSON.
- `tab-bar` is app navigation chrome — **not** the home indicator. TabView provides the bar — do not draw a second custom strip at JSON bounds.
- Minimum touch target 44×44pt for interactive controls.
- Use `.position()` on a **full-canvas** `ZStack` with `.ignoresSafeArea()` — bounds are canvas-absolute (**PS-R011**).
- Do **not** add `safeAreaInsets.top` to `bounds.y` — coordinates already sit below the status bar when designed that way.
- Nav-row siblings (`logo`, buttons) share the nav bar row but each has **separate JSON bounds** — position at `position.center`; **never** `.toolbar` / `.navigationBarTrailing` (**PS-R012**).
- Apply each object’s authored `zIndex` from JSON (same as the designer Layer value).
- `renderOrder` is back→front (sorted by `zIndex`, then id). Export does not remap zIndex. Place backdrops behind text in the designer.
- For iPad layouts, same coordinate space applies — bounds are absolute to canvas.
- When an object includes `visual` in JSON, apply exact fills — TextField/SecureField/TextEditor need `.background()` + border; never leave inputs transparent on a white canvas (**PS-S026**).
- `wheel-picker` → `Picker` with `.pickerStyle(.wheel)`; items from pipe-separated `label` (e.g. `Small|Medium|Large`). `date-picker` / `time-picker` on iPhone use wheel style when bounds height ≥ ~160px.
- Labels, titles, captions, links, spacers, and separators have no `visual` block — keep them transparent; do not add card backgrounds behind text.
- Button fills use `visual.background` with `visual.textColor` for label contrast; card/group-box use `visual.background` + `visual.borderColor` on a RoundedRectangle.

### Type mapping

- `alert` → Banner
- `avatar` → Circle + Text
- `badge` → Text on Circle
- `button` → Button
- `camera` → PhotosPicker
- `caption` → Text (.caption)
- `card` → VStack in RoundedRectangle
- `chart` → Swift Charts
- `checkbox` → Toggle (checkbox style)
- `chip` → Text in Capsule
- `color-picker` → ColorPicker
- `date-picker` → DatePicker
- `divider-vertical` → Divider
- `fab` → Button (FAB style)
- `gauge` → Gauge
- `grid` → LazyVGrid
- `group-box` → GroupBox
- `icon` → Image(systemName:)
- `icon-button` → Button
- `image` → Image
- `label` → Text
- `link` → Link
- `link-button` → Button
- `list` → List
- `logo` → Positioned view at position.center — HStack(◆ + uppercase label) per logoStyle; **never** toolbar trailing (**PS-R012**)
- `map` → Map
- `menu-bar` → N/A on iOS
- `nav-bar` → Custom ZStack at JSON bounds — **centered** title + **leading** back chevron; wire linkToPageId (**PS-R009**). **Never** .navigationTitle alone (left-aligns title).
- `number-field` → TextField
- `picker` → Picker
- `progress` → ProgressView (linear)
- `progress-circle` → ProgressView (circular)
- `radio` → Picker / custom
- `rating` → HStack of Image (star.fill)
- `scroll-view` → ScrollView
- `search-field` → TextField
- `secure-field` → SecureField
- `segmented-control` → Picker (.segmented)
- `separator` → Divider
- `sidebar` → NavigationSplitView (compact)
- `slider` → Slider
- `spacer` → Spacer
- `split-view` → NavigationSplitView / HSplitView
- `status-bar` → System safe area (do not draw)
- `stepper` → Stepper
- `subtitle` → Text (.title2)
- `tab-bar` → Root `TabView` shell ONLY — `.tabItem` per `tabItems[]`; exclude `tab-bar` from tab content (**PS-R008**)
- `tab-control` → TabView — non-closable tabs unless closable: true in JSON
- `table` → Table
- `text-area` → TextEditor
- `text-field` → TextField
- `time-picker` → DatePicker
- `title` → Text (.largeTitle)
- `toggle` → Toggle
- `toolbar` → Toolbar { ToolbarItem }
- `video` → VideoPlayer
- `web-view` → SafariView / WebView
- `wheel-picker` → Picker (.wheel)

### Deliver

- Deliver a SwiftUI `View` per page — **one struct per page id** (PS-R010). When `meta.primaryTabBar` exists, app root is `TabView` — individual page views omit `tab-bar` objects.
- When `nav-bar` objects exist, render each as a custom positioned bar (ZStack: centered title + leading chevron) at JSON bounds — not NavigationStack `.navigationTitle`. Wire all `linkToPageId` navigation.
- Page 2 and Page 3 must use their **own** object lists from JSON — verify button labels and nav titles match each page, not Page 1.
- Root each page in a fixed `ZStack` sized to `layout.width`×`layout.height` with `.ignoresSafeArea()` — use each object's `position.center` + `bounds` width/height (±1px).
- Nav-row objects (`logo`, trailing buttons) at `position.center` — forbidden: `.toolbar`, `ToolbarItem`, trailing Spacer reflow (**PS-R012**).
- Use `#Preview` with `.previewDevice` matching `device.name` and exact canvas dimensions from JSON.

---

## Platform — SwiftUI for macOS

**Target:** macOS 14+ (Sonoma)

### Platform rules

- Build a native **macOS desktop app** — not an iOS port. Use mouse/keyboard interaction patterns.
- Use `NavigationSplitView` when the design has a sidebar; list + detail column layout.
- Menu bar is system-drawn — respect the top safe inset (32px). Do NOT recreate menu bar icons.
- Use `Toolbar` and `Commands` for toolbar/menu-bar objects. Wire menu labels from pipe-separated `label` values.
- Set window size to match spec: `.defaultSize(width: W, height: H)` on `WindowGroup`.
- Prefer `.buttonStyle(.borderedProminent)` / `.bordered` following macOS Human Interface Guidelines.
- Use `.position()` or `GeometryReader` for exact bounds. macOS windows use the same top-left origin.
- Apply each object’s authored `zIndex` from JSON (same as the designer Layer value).
- `renderOrder` is back→front (sorted by `zIndex`, then id). Export does not remap zIndex. Place backdrops behind text in the designer.
- Add hover effects (`.onHover`) for buttons and list rows where appropriate.

### Type mapping

- `alert` → Banner / .alert
- `avatar` → Circle + Text
- `badge` → Text on Circle
- `button` → Button
- `camera` → PhotosPicker
- `caption` → Text (.caption)
- `card` → VStack in RoundedRectangle
- `chart` → Swift Charts
- `checkbox` → Toggle (checkbox style)
- `chip` → Text in Capsule
- `color-picker` → ColorPicker
- `date-picker` → DatePicker
- `divider-vertical` → Divider
- `fab` → Button (toolbar style — macOS has no FAB)
- `gauge` → Gauge
- `grid` → LazyVGrid
- `group-box` → GroupBox
- `icon` → Image(systemName:)
- `icon-button` → Button
- `image` → Image
- `label` → Text
- `link` → Link
- `link-button` → Button
- `list` → List
- `logo` → Image (logo asset) / branded Image
- `map` → Map
- `menu-bar` → Commands { CommandMenu } / MenuBarExtra
- `nav-bar` → NavigationSplitView detail toolbar + .navigationTitle
- `number-field` → TextField
- `picker` → Picker
- `progress` → ProgressView (linear)
- `progress-circle` → ProgressView (circular)
- `radio` → Picker / RadioGroup
- `rating` → HStack of Image (star.fill)
- `scroll-view` → ScrollView
- `search-field` → TextField
- `secure-field` → SecureField
- `segmented-control` → Picker (.segmented)
- `separator` → Divider
- `sidebar` → NavigationSplitView { List } sidebar column
- `slider` → Slider
- `spacer` → Spacer
- `split-view` → NavigationSplitView / HSplitView
- `status-bar` → System menu bar — do not draw; respect top inset
- `stepper` → Stepper
- `subtitle` → Text (.title2)
- `tab-bar` → Root `TabView` or sidebar — one item per `tabItems[]` (**PS-R006**)
- `tab-control` → TabView — non-closable tabs unless closable: true in JSON
- `table` → Table
- `text-area` → TextEditor
- `text-field` → TextField
- `time-picker` → DatePicker
- `title` → Text (.largeTitle)
- `toggle` → Toggle
- `toolbar` → Toolbar { ToolbarItem }
- `video` → VideoPlayer
- `web-view` → WebView
- `wheel-picker` → Picker (.wheel)

### Deliver

- Deliver `@main` SwiftUI App with `WindowGroup` sized to the spec canvas.
- Include `NavigationSplitView` if sidebar objects are present.
- Use `#Preview` on macOS with `.frame(width:height:)` matching the spec.

---

## Platform — Jetpack Compose

**Target:** Android API 34+

### Platform rules

- Use `WindowInsets` for status bar and gesture navigation — do not draw system bars.
- When `tab-bar` is present (**PS-R006**): `BottomAppBar` / `NavigationBar` with one destination per `tabItems` entry.
- Per page: `Box(Modifier.fillMaxSize())`; children use `offset` + `size` + `zIndex` from JSON export.
- Wire Navigation table: WIRED/INFER navigate; NO NAV stays on screen.

### Type mapping

- `alert` → Banner
- `avatar` → Surface(CircleShape) + Text
- `badge` → Badge
- `button` → Button
- `camera` → PhotoPicker
- `caption` → Text (bodySmall)
- `card` → Card
- `chart` → Chart library
- `checkbox` → Checkbox
- `chip` → AssistChip
- `color-picker` → Color picker
- `date-picker` → DatePicker
- `divider-vertical` → VerticalDivider
- `fab` → FloatingActionButton
- `gauge` → LinearProgressIndicator
- `grid` → LazyVerticalGrid
- `group-box` → Card with title
- `icon` → Icon
- `icon-button` → IconButton
- `image` → Image
- `label` → Text
- `link` → TextButton
- `link-button` → TextButton
- `list` → LazyColumn
- `logo` → Image (painterResource / vector logo)
- `map` → GoogleMap composable
- `menu-bar` → MenuBar
- `nav-bar` → TopAppBar
- `number-field` → OutlinedTextField (Number)
- `picker` → DropdownMenu
- `progress` → LinearProgressIndicator
- `progress-circle` → CircularProgressIndicator
- `radio` → RadioButton
- `rating` → Row of Icon
- `scroll-view` → verticalScroll / Modifier.verticalScroll
- `search-field` → SearchBar
- `secure-field` → OutlinedTextField (PasswordVisualTransformation)
- `segmented-control` → SingleChoiceSegmentedButtonRow
- `separator` → HorizontalDivider
- `sidebar` → NavigationDrawer
- `slider` → Slider
- `spacer` → Spacer
- `split-view` → Row / permanent NavigationRail + content
- `status-bar` → System bars (WindowInsets)
- `stepper` → NumberStepper
- `subtitle` → Text (titleMedium)
- `tab-bar` → NavigationBar / BottomAppBar — one destination per `tabItems[]` (**PS-R006**)
- `tab-control` → ScrollableTabRow / custom IDE-style tabs
- `table` → LazyColumn rows
- `text-area` → OutlinedTextField (multi-line)
- `text-field` → OutlinedTextField
- `time-picker` → TimePicker
- `title` → Text (titleLarge)
- `toggle` → Switch
- `toolbar` → TopAppBar actions
- `video` → Video player
- `web-view` → AndroidView WebView
- `wheel-picker` → WheelPicker / NumberPicker

### Deliver

- Output **only Kotlin** — one `NavHost` + one `@Composable` per page; Navigation Compose for all wired/INFER routes.
- No markdown prose. End with the PanelSpec checklist comment block from Output contract.

---

## Platform — WinUI 3

Use **WinUI 3** (.NET 8). Do **not** use WPF, UWP, or Windows Forms.

**Target:** Windows 11 / .NET 8

### Platform rules

- MainWindow client area matches spec canvas; title bar is native — do not draw window chrome.
- Bounds are client-area coordinates — do **not** add `safeArea.insets.top` again as layout margin.
- Tab-control: selecting tabs switches in-tab content only; never navigates pages unless that object has `linkToPageId`.
- Objects with `renderAs: "native"` are wireframe placeholders — use native MenuBar/status APIs.

### Type mapping

- `alert` → InfoBar
- `avatar` → PersonPicture
- `badge` → Badge
- `button` → Button
- `camera` → Image + file picker
- `caption` → TextBlock (Caption)
- `card` → Border + StackPanel
- `chart` → Border + TextBlock "Chart" (placeholder — no chart library)
- `checkbox` → CheckBox
- `chip` → Chip
- `color-picker` → ColorPicker
- `date-picker` → CalendarDatePicker
- `divider-vertical` → Separator (vertical)
- `fab` → Button (round)
- `gauge` → ProgressBar
- `grid` → GridView / ItemsWrapGrid
- `group-box` → GroupBox
- `icon` → FontIcon
- `icon-button` → AppBarButton
- `image` → Image
- `label` → TextBlock
- `link` → HyperlinkButton
- `link-button` → HyperlinkButton
- `list` → ListView
- `logo` → Image (/Assets/logo.png or gray placeholder)
- `map` → MapControl
- `menu-bar` → MenuBar
- `nav-bar` → Page header
- `number-field` → NumberBox
- `picker` → ComboBox (placeholder only unless items in brief)
- `progress` → ProgressBar
- `progress-circle` → ProgressRing
- `radio` → RadioButton
- `rating` → RatingControl
- `scroll-view` → ScrollViewer
- `search-field` → AutoSuggestBox
- `secure-field` → PasswordBox
- `segmented-control` → ToggleSwitch / RadioButtons
- `separator` → Separator
- `sidebar` → NavigationView pane
- `slider` → Slider
- `spacer` → Grid spacer
- `split-view` → TwoPaneView / Grid columns
- `status-bar` → System chrome
- `stepper` → NumberBox with buttons
- `subtitle` → TextBlock (Subheader)
- `tab-bar` → TabView (bottom) — one tab per `tabItems[]` (**PS-R006**)
- `tab-control` → TabView — tab selection swaps content only; no page navigation unless linkToPageId
- `table` → DataGrid
- `text-area` → TextBox (AcceptsReturn)
- `text-field` → TextBox
- `time-picker` → TimePicker
- `title` → TextBlock (Header)
- `toggle` → ToggleSwitch
- `toolbar` → CommandBar
- `video` → MediaPlayerElement
- `web-view` → WebView2
- `wheel-picker` → ComboBox or custom wheel UI

### Deliver

- WinUI 3 solution: `App`, `MainWindow` (Title from `systemChrome.titleBar`, size = canvas), one `Page` per spec page, `NavigationHelper` for `linkToPageId` routing.
- Name pages from spec (`LoginPage`, etc.); wire `startPageId` as initial content.
- Match bounds within ±1px.

---

## Platform — Windows Forms (.NET)

**Target:** .NET 8+ WinForms (or .NET Framework 4.x)

### Platform rules

- Deliver **C# WinForms** — `partial class` Form with `InitializeComponent()` in `FormName.Designer.cs`.
- Use `System.Windows.Forms` controls — map types from the object table below (Button, TextBox, ComboBox, DataGridView, etc.).
- Convert spec `bounds` to control `Location` and `Size` (x, y, width, height in pixels). Top-left origin matches WinForms client area.
- Respect title bar inset from `safeArea` — do NOT draw the window title bar; start content below it.
- Wireframe menu-bar / status-bar with `renderAs: "native"` → use MenuStrip / StatusStrip in the form, not custom painted chrome.
- Tab-control: split `label` on `|` for tab pages; children with matching `hostId` + `hostTabIndex` go on each `TabPage`.
- Group-box children with `hostId` belong inside the GroupBox; use `hostRelativeBounds` when present.
- Set Form `ClientSize` to match spec canvas (default VS 2022 template: 800×450).
- Navigation: `linkToPageId` → show another Form or swap `TabControl` selected tab.
- Prefer `SuspendLayout()` / `ResumeLayout()` in Designer.cs when adding many controls.

### Type mapping

- `alert` → Panel + Label
- `avatar` → PictureBox (circle clip)
- `badge` → Label
- `button` → Button
- `camera` → PictureBox + OpenFileDialog
- `caption` → Label (small)
- `card` → Panel (border)
- `chart` → Chart control
- `checkbox` → CheckBox
- `chip` → Label
- `color-picker` → ColorDialog trigger
- `date-picker` → DateTimePicker
- `divider-vertical` → Panel (1px wide)
- `fab` → Button (round)
- `gauge` → ProgressBar
- `grid` → FlowLayoutPanel / TableLayoutPanel
- `group-box` → GroupBox
- `icon` → PictureBox
- `icon-button` → Button (icon)
- `image` → PictureBox
- `label` → Label
- `link` → LinkLabel
- `link-button` → LinkLabel
- `list` → ListBox
- `logo` → PictureBox (logo)
- `map` → Panel (map placeholder)
- `menu-bar` → MenuStrip
- `nav-bar` → Panel + Label
- `number-field` → NumericUpDown
- `picker` → ComboBox
- `progress` → ProgressBar
- `progress-circle` → ProgressBar (Marquee style)
- `radio` → RadioButton
- `rating` → Custom / TrackBar
- `scroll-view` → Panel (AutoScroll)
- `search-field` → TextBox
- `secure-field` → TextBox (UseSystemPasswordChar = true)
- `segmented-control` → RadioButton group
- `separator` → Label (border) / Panel
- `sidebar` → Panel + ListBox
- `slider` → TrackBar
- `spacer` → Panel (empty)
- `split-view` → SplitContainer
- `status-bar` → StatusStrip
- `stepper` → NumericUpDown
- `subtitle` → Label (medium)
- `tab-bar` → TabControl (bottom) — one tab page per `tabItems[]` (**PS-R006**)
- `tab-control` → TabControl
- `table` → DataGridView
- `text-area` → RichTextBox
- `text-field` → TextBox
- `time-picker` → DateTimePicker (Format = Time)
- `title` → Label (large)
- `toggle` → CheckBox
- `toolbar` → ToolStrip
- `video` → Windows Media Player / placeholder Panel
- `web-view` → WebBrowser
- `wheel-picker` → ComboBox

### Deliver

- Deliver `Form1.cs` + `Form1.Designer.cs` like a fresh Visual Studio 2022 WinForms App — empty `InitializeComponent()` until controls are in the spec.
- Include `using System.Windows.Forms;` and namespace matching the project.
- Set Form `Text` from page name (e.g. Form1) and `ClientSize` to canvas width×height.

---

## Platform — HTML + CSS

**Target:** Modern browsers (Chrome, Safari, Firefox, Edge)

### Platform rules

- Use semantic HTML5 — `<header>`, `<nav>`, `<main>`, `<section>`, `<article>`, `<aside>`, `<footer>` where objects map naturally.
- Use `position: absolute` with px values from `bounds` for pixel-exact layout matching the spec viewport.
- Include `<meta name="viewport" content="width=device-width, initial-scale=1">` for mobile web targets.
- Accessible markup: `<label for>` on inputs, alt text on images, ARIA roles from the type mapping (e.g. `role="alert"`).
- Do NOT recreate browser chrome (tabs, address bar) — `systemChrome.browserBar` is designer reference only.
- Navigation: wire `linkToPageId` with `<a href="#">` + JS show/hide or a minimal client router.
- Tab-control: `[role=tablist]` / `[role=tabpanel]`; tabs non-closable unless `closable: true`.
- Tab-control children: when `hostRelativeBounds` is present, position inside the tab panel using those values (relative to the tab content area, not the page canvas).
- web-view (`iframe`): if the URL is blocked by X-Frame-Options / CSP, render a bordered placeholder box showing the URL from `label` instead of an empty frame.
- Apply authored `z-index` from JSON (same as designer Layer); lower values render behind.
- When `meta.responsiveLayout` is present (desktop web design): implement **PS-R013** / **PS-S027** — CSS `@media (max-width: …)` mobile reflow for form controls; desktop JSON bounds for wide viewports only.

### Type mapping

- `alert` → div[role=alert]
- `avatar` → div.avatar
- `badge` → span.badge
- `button` → button
- `camera` → input[type=file][accept=image/*] + img preview
- `caption` → small
- `card` → article.card
- `chart` → canvas / chart lib
- `checkbox` → input[type=checkbox]
- `chip` → span.chip
- `color-picker` → input[type=color]
- `date-picker` → input[type=date]
- `divider-vertical` → div.divider-v
- `fab` → button.fab
- `gauge` → meter
- `grid` → div.grid
- `group-box` → fieldset
- `icon` → span.icon
- `icon-button` → button.icon
- `image` → img
- `label` → span / p
- `link` → a
- `link-button` → a.button-link
- `list` → ul
- `logo` → img.logo — alt text from label; object-fit: contain
- `map` → div.map / iframe
- `menu-bar` → menubar
- `nav-bar` → header nav
- `number-field` → input[type=number]
- `picker` → select
- `primary-button` → button.primary
- `progress` → progress
- `progress-circle` → progress (circle SVG)
- `radio` → input[type=radio]
- `rating` → div.stars
- `scroll-view` → div.scroll-view (overflow:auto)
- `search-field` → input[type=search]
- `secondary-button` → button.secondary
- `secure-field` → input[type=password]
- `segmented-control` → div[role=tablist]
- `separator` → hr
- `sidebar` → aside nav
- `slider` → input[type=range]
- `spacer` → div.spacer
- `split-view` → div.split-view (CSS grid / flex)
- `status-bar` → native (CSS env(safe-area-inset-*))
- `stepper` → input[type=number]
- `subtitle` → h2
- `tab-bar` → <nav> bottom tabs — one button per `tabItems[]` (**PS-R006**)
- `tab-control` → div[role=tablist] — non-closable tabs unless closable: true in JSON
- `table` → table
- `text-area` → textarea
- `text-field` → input[type=text]
- `time-picker` → input[type=time]
- `title` → h1
- `toggle` → input[type=checkbox].toggle
- `toolbar` → menu.toolbar
- `video` → video
- `web-view` → iframe — if X-Frame-Options blocks embed, use bordered placeholder showing URL from label
- `wheel-picker` → select[multiple size] / custom wheel

### Deliver

- Deliver `index.html` with embedded `<style>` (or linked `styles.css`).
- Optional vanilla JS for multi-page and tab switching — React/Vue only if the AI Brief requests it.
- Match all bounds within ±1px; clip or omit objects with `canvasOverflow`.