PanelSpec · Part A
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
- Open the Designer and lay out your panel.
- Click Generate AI Prompt (or Export JSON).
- Copy the prompt from the export dialog.
- Paste into Cursor, Claude, ChatGPT, or Gemini.
- Tell the AI your target: SwiftUI, Compose, WinUI, WinForms, or HTML.
- Require OS text remasure before shipping (PS-R019 / PS-R020) — helpers in
exports/remeasure/. - Set
meta.nativeRemeasureDeclared: trueonly after that remasure pass exists in code.
# 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`.