Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/code-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
feature: **Code component**: `ui.code` and markup `<code>` render highlighted source with the Geist Code Block palette in both built-in themes, wrapping by default, opt-in logical line numbers, unwrapped horizontal scrolling, and vertical scrolling for height-constrained surfaces; Markdown fences share the same component.
1 change: 1 addition & 0 deletions changelog.d/code-transform-budgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix: **Bounded transformed code rendering**: heavily scaled code surfaces now degrade within the shared command and text-byte budgets instead of rejecting the entire display-list refresh.
1 change: 1 addition & 0 deletions changelog.d/markdown-list-code-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fix: **Polished Markdown lists and code blocks**: bullet and ordered-list markers now align with the first content line, while fenced code preserves source indentation and applies theme-aware highlighting with richer HTML/JSX tags and attributes.
Binary file added docs/public/components/code-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/components/code-hero-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/components/code-hero-light.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/public/components/code-light.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docs/src/app/components/code/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { pageMetadata } from "@/lib/page-metadata";

export const metadata = pageMetadata("components/code");

export default function CodeLayout({ children }: { children: React.ReactNode }) {
return children;
}
47 changes: 47 additions & 0 deletions docs/src/app/components/code/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ComponentPreview } from "@/components/component-preview";
import { AttrTable } from "@/components/attr-table";

# Code

Presents source text in a themed monospace surface with deterministic syntax highlighting. Code wraps by default. Set `wrap="false"` to preserve logical lines inside one horizontal scroll region, and opt into logical line numbers with `line-numbers`.

HTML-family highlighting understands HTML, XML, SVG, JSX, and TSX structure: element or component tags, attributes, strings, comments, numbers, and JavaScript/TypeScript expressions receive distinct theme-token colors.

<ComponentPreview name="code" alt="A JSX code sample with syntax highlighting and line numbers" caption="JSX tags, attributes, strings, expressions, and an unwrapped horizontal viewport" />

## Markup

```html
<code
source="{component_source}"
language="tsx"
line-numbers
wrap="false"
width="480"
label="Accordion example"
/>
```

`source` is required and must be one `{binding}` producing text. `language` is a literal lexer name; unknown names are validation errors. Line numbers are off by default and remain decorative, so selecting and copying a numbered block returns only the source text. Numbered presentation is limited to 128 logical lines; longer sources keep all code and omit the gutter.

## Programmatic construction (Zig)

```zig
ui.code(.{
.language = .html,
.line_numbers = true,
.wrap = false,
.width = 480,
.semantics = .{ .label = "Accordion example" },
}, model.component_source)
```

The public lexer model is `native_sdk.canvas.code`. `languageFromName` resolves markup spellings, `languageFromFence` reads a Markdown info string, and `highlight` produces the same bounded, theme-colored span runs both renderers use.

## Languages

Zig; JavaScript and TypeScript; JSX and TSX; JSON; shell; Python; Rust; C, C++, C#, Java, Kotlin, and Swift; Go; HTML, XML, and SVG; CSS, SCSS, and Less; and SQL. An omitted language renders plain monospace.

## Attributes

<AttrTable element="code" attrs={["source", "language", "line-numbers", "wrap", "width", "height", "min-width", "grow", "key", "global-key", "label"]} />
2 changes: 1 addition & 1 deletion docs/src/app/components/markdown/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AttrTable } from "@/components/attr-table";

# Markdown

Renders a markdown string (a GFM subset, pipe tables included) as native widgets through the same text pipeline as every other component — deterministic layout, selectable text. `source` is required and must be one `{binding}`; the element takes no children. Links dispatch `on-link` with the URL as payload (bare URLs autolink), `<details>` blocks toggle through `on-details` plus a model-owned `details-expanded` flag list, and `#123` references linkify through `issue-link-base`.
Renders a markdown string (a GFM subset, pipe tables included) as native widgets through the same text pipeline as every other component — deterministic layout, selectable text. `source` is required and must be one `{binding}`; the element takes no children. Links dispatch `on-link` with the URL as payload (bare URLs autolink), `<details>` blocks toggle through `on-details` plus a model-owned `details-expanded` flag list, and `#123` references linkify through `issue-link-base`. Fenced blocks lower through the reusable [Code](/components/code) component, so indentation and syntax behavior stay identical.

<ComponentPreview name="markdown" alt="A markdown document rendered by the engine" caption="headings, emphasis, inline code, lists, links, and a code block" />

Expand Down
21 changes: 20 additions & 1 deletion docs/src/app/native-ui/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Color and radius attributes reference design tokens by name — literals only, v
</row>
```

The color attributes are `background`, `foreground`, `accent`, `accent-foreground`, `border-color`, and `focus-ring`; values are `ColorTokens` field names: `background`, `surface`, `surface_subtle`, `surface_pressed`, `text`, `text_muted`, `border`, `accent`, `accent_text`, `destructive`, `destructive_text`, `success`, `success_text`, `warning`, `warning_text`, `info`, `info_text`, `focus_ring`, `shadow`, `disabled`. `info` is the violet identity hue beside the status trio — merged PR badges, "new" chips, informational callouts. `radius` takes a `RadiusTokens` name: `sm`, `md`, `lg`, `xl`.
The color attributes are `background`, `foreground`, `accent`, `accent-foreground`, `border-color`, and `focus-ring`; values are `ColorTokens` field names: `background`, `surface`, `surface_subtle`, `surface_pressed`, `text`, `text_muted`, `syntax_plain`, `syntax_comment`, `syntax_keyword`, `syntax_literal`, `syntax_function`, `syntax_property`, `syntax_constant`, `border`, `accent`, `accent_text`, `destructive`, `destructive_text`, `success`, `success_text`, `warning`, `warning_text`, `info`, `info_text`, `focus_ring`, `shadow`, `scrim`, `disabled`. The `syntax_*` roles are the Geist Code Block palette shared by both built-in packs; `ui.code` and Markdown fences use them automatically. `info` is the violet identity hue beside the status trio — merged PR badges, "new" chips, informational callouts. `radius` takes a `RadiusTokens` name: `sm`, `md`, `lg`, `xl`.

References resolve against the app's live tokens on every rebuild, so themed apps re-resolve them when the theme changes — dark mode flips `surface` for free. Anything dynamic beyond that (raw colors, per-state styling) stays in Zig via `ElementOptions.style`, which always wins over a token reference.

Expand Down Expand Up @@ -559,6 +559,23 @@ ui.paragraph(.{ .on_link = Ui.linkMsg(.open_url) }, &spans)

Wrapping and measurement are span-aware (the platform text provider measures every run with the font it draws with), stacked paragraphs reserve their real wrapped height, and link spans are first-class: they carry `role=link` semantics in automation snapshots, show the pointing-hand cursor (the only place the engine uses it — controls keep the platform arrow, following native convention rather than the web's), and clicking one dispatches your `Msg` with the link payload.

Source presentation is the reusable `ui.code` / `<code>` component. It wraps by default; `wrap="false"` preserves logical lines inside one horizontal scroll region, and `line-numbers` opts into a muted logical-line gutter. A definite `height` keeps overflow reachable with vertical scrolling (or both axes when wrapping is off):

```html
<code source="{component_source}" language="tsx" line-numbers wrap="false" width="480" label="Accordion example" />
```

```zig
ui.code(.{
.language = .html,
.line_numbers = true,
.wrap = false,
.width = 480,
}, model.component_source)
```

Recognized languages receive theme-aware syntax color for keywords, types/numbers, strings, and comments: Zig; JavaScript/TypeScript; JSX/TSX; JSON; shell; Python; Rust; C, C++, C#, Java, Kotlin, and Swift; Go; HTML/XML/SVG; CSS/SCSS/Less; and SQL. HTML-family highlighting distinguishes element and component tags, attributes, strings, comments, and JavaScript/TypeScript expressions. Unknown or omitted tags stay plain monospace, and the bounded highlighting fallback never drops code. See [Code](/components/code).

Markdown builds on the same model. `native_sdk.markdown` maps a GitHub-flavored subset — headings, inline styles, links (including bare `http(s)://` URLs, which autolink with trailing punctuation trimmed), bullet/ordered/task lists, fenced code blocks, blockquotes, rules, pipe tables, and `<details>` collapsibles — onto ordinary widgets:

```zig
Expand All @@ -573,6 +590,8 @@ Md.view(ui, issue.body, .{

Malformed input degrades to plain text — the build fn never fails. Task-list checkboxes render as disabled (display-only) checkboxes, and `<details>` expansion is state the caller's model owns.

Fenced code preserves every source indentation level and lowers through the same `ui.code` component, including JSX/TSX-aware HTML-family highlighting. Markdown keeps the component defaults: wrapping on, line numbers off.

GFM pipe tables map onto the real `table`/`table-row`/`table-cell` widgets: the header row renders bold, the delimiter row's `:---`/`:--:`/`---:` cells set per-column start/center/end alignment, every cell runs the full inline grammar (code, bold, links — links in cells are clickable), `\|` puts a literal pipe in a cell, and cells wrap at their column width (columns share the width equally in v1). A pipe block whose delimiter row is missing or mismatched is not a table and renders as plain paragraphs.

In markup, the `<markdown>` element wires all of this declaratively:
Expand Down
76 changes: 75 additions & 1 deletion docs/src/lib/component-vocab.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@
"name": "icon",
"doc": "Vector icon leaf: name selects a curated built-in stroke icon (comptime-validated), an app-registered app:<name> (canvas.icons.registerAppIcons; native check verifies the name against the model contract), or one {binding} resolving to such a name. Tint via foreground, size with width/height or size."
},
{
"name": "code",
"doc": "Highlighted source-code surface. source is one required text {binding}; language is a literal lexer name. Wraps by default, line-numbers opts into logical line numbers, wrap=\"false\" keeps lines intact, and a definite height makes overflow scrollable."
},
{
"name": "markdown",
"doc": "Renders a markdown string (GFM subset, pipe tables included) as widgets; source is one {binding}, links dispatch on-link (bare URLs autolink), <details> blocks toggle via on-details + details-expanded, #123 refs linkify via issue-link-base."
Expand Down Expand Up @@ -271,6 +275,10 @@
{
"name": "video",
"doc": "The video leaf: plays the app's single platform-decoded video into the framework-owned media-surface (macOS decodes with AVFoundation; hosts without a decoder deliver one explicit failed event). src declares the source — an app-assets path or an http(s) URL, resolved local-first exactly like audio; autoplay (default true), loop, and muted shape the fresh playback; controls composes the house transport chrome (play/pause, scrub bar, time readout) under the picture, and without it the element is the surface alone — compose your own controls from the video command vocabulary. Until a decoded frame arrives (and in every golden, screenshot, and replay) the surface shows its deterministic placeholder: pixels are presentation chrome, transport is the journaled truth. Size it with width/height or grow (no intrinsic size); label it for screen readers."
},
{
"name": "terminal",
"doc": "The terminal leaf: renders the framework-owned emulator session behind a model-owned pty effect key — the grid as real text with geometric box drawing, a theme-derived ANSI palette, selection, cursor, and scrollback — and routes keys, IME text, and wheel scrollback to it when focused. pty is one {binding} to the u64 key the app's ptySpawn named (required; keys are model data, never markup literals; 0 renders the empty surface); scrollback echoes the app-visible offset back under the scroll value source-wins rule, and on-terminal delivers the post-change view state. The grid derives its cols/rows from the frame the layout resolves (the runtime pushes them to the pty), so size it like a leaf: grow or a definite width/height. An interactive control: give it a label."
}
],
"structure": [
Expand Down Expand Up @@ -526,6 +534,10 @@
"name": "on-resize",
"doc": "split element only: names a Msg variant with f32 payload; delivers the applied first-pane fraction after every divider drag, keyboard adjustment, and assistive increment/decrement. Echo it back into value - the delivered fraction never fights the reconcile."
},
{
"name": "on-terminal",
"doc": "terminal element only: names a bare Msg variant with canvas.TerminalState payload; delivers the post-change view state (scrollback, history, cols, rows) after every runtime-applied change - wheel and keyboard scrollback, and the layout-derived grid resize. Echo scrollback back into the attribute - the delivered state never fights the reconcile."
},
{
"name": "on-reach-end",
"doc": "scroll element only: Msg (tag or tag:{payload}) dispatched when a user scroll comes within one viewport of the content end - the infinite-scroll fetch signal. Fires once per approach with hysteresis: it re-arms only after the offset retreats past 1.5 viewports, which appending a batch causes on its own by growing the extent."
Expand Down Expand Up @@ -562,6 +574,52 @@
"doc": "markdown: literal URL prefix or one {binding}; '#123' refs become links to base ++ number (ghissue:// or https://github.com/owner/repo/issues/)."
}
],
"code": [
{
"name": "source",
"doc": "code: one required {binding} producing source text (a []const u8 field or fn; arena fns work)."
},
{
"name": "language",
"doc": "code: literal lexer name. Supports Zig, JavaScript/TypeScript, JSX/TSX, JSON, shell, Python, Rust, C-family, Go, HTML/XML/SVG, CSS-family, and SQL; unknown names are a validation error."
},
{
"name": "line-numbers",
"doc": "code: opt into muted logical line numbers. Off by default; a wrapped logical line stays paired with its number."
},
{
"name": "wrap",
"doc": "code: true by default. false preserves logical lines and puts the highlighted content in one horizontal scroll region."
},
{
"name": "width",
"doc": "Definite width (plain number)."
},
{
"name": "height",
"doc": "code: definite height (plain number). Overflow scrolls vertically; with wrap=false the region scrolls on both axes."
},
{
"name": "min-width",
"doc": "Width floor without a definite maximum."
},
{
"name": "grow",
"doc": "Flex grow factor."
},
{
"name": "key",
"doc": "Sibling-scoped identity key."
},
{
"name": "global-key",
"doc": "Parent-independent identity: ids survive reparenting between containers."
},
{
"name": "label",
"doc": "Accessible name for the code group."
}
],
"stepper": [
{
"name": "active",
Expand Down Expand Up @@ -662,6 +720,14 @@
{
"name": "surface",
"doc": "media-surface: one {binding} to the model-owned u64 surface id a Zig-tier producer targets (runtime.acquireMediaSurfaceProducer). Required; surface ids are model data, never markup literals; 0 leaves the surface unbound and it draws nothing, and usable ids are nonzero values below bit 63 — the reserved media-surface texture namespace, which the producer acquire refuses."
},
{
"name": "pty",
"doc": "terminal: one {binding} to the model-owned u64 pty effect key whose session the terminal renders (the key ptySpawn named). Required; pty keys are model data, never markup literals; 0 leaves the terminal unbound and it renders the empty surface."
},
{
"name": "scrollback",
"doc": "terminal only: the scrollback offset in rows above the live screen (0 is pinned to the bottom). Follows the scroll value source-wins reconcile rule - echo on-terminal's scrollback back to keep user scrollback across rebuilds; move it model-side to scroll programmatically."
}
],
"video": [
Expand Down Expand Up @@ -1114,6 +1180,10 @@
"width": 1120,
"height": 280
},
"code": {
"width": 1120,
"height": 600
},
"markdown": {
"width": 1120,
"height": 880
Expand Down Expand Up @@ -1202,6 +1272,10 @@
"width": 704,
"height": 396
},
"code-hero": {
"width": 704,
"height": 396
},
"dialog-hero": {
"width": 704,
"height": 396
Expand Down Expand Up @@ -1458,4 +1532,4 @@
"path": "src/components/timeline_item.zig"
}
]
}
}
1 change: 1 addition & 0 deletions docs/src/lib/components-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const componentPages: ComponentPage[] = [
{ slug: "card", name: "Card", preview: "card-hero", blurb: "The bordered, elevated surface container." },
{ slug: "chart", name: "Chart", preview: "chart-hero", blurb: "Line, bar, and band series (Zig builder)." },
{ slug: "checkbox", name: "Checkbox", preview: "checkbox-hero", blurb: "Binary choice with model-owned state." },
{ slug: "code", name: "Code", preview: "code-hero", blurb: "Highlighted source with line numbers and optional horizontal scrolling." },
{ slug: "combobox", name: "Combobox", preview: "combobox-hero", blurb: "Text entry with an anchored suggestions menu." },
{ slug: "dialog", name: "Dialog", preview: "dialog-hero", blurb: "Modal surface with model-owned dismissal." },
{ slug: "drawer", name: "Drawer", preview: "drawer-hero", blurb: "Side-anchored modal surface." },
Expand Down
14 changes: 14 additions & 0 deletions examples/calculator/src/theme.zig
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ pub const light_colors = canvas.ColorTokens{
.surface_pressed = Color.rgb8(229, 229, 229),
.text = Color.rgb8(10, 10, 10),
.text_muted = Color.rgb8(115, 115, 115),
.syntax_plain = Color.rgb8(23, 23, 23),
.syntax_comment = Color.rgb8(77, 77, 77),
.syntax_keyword = Color.rgb8(189, 40, 100),
.syntax_literal = Color.rgb8(41, 122, 58),
.syntax_function = Color.rgb8(120, 32, 188),
.syntax_property = Color.rgb8(203, 42, 47),
.syntax_constant = Color.rgb8(0, 104, 214),
.border = Color.rgb8(229, 229, 229),
.accent = Color.rgb8(20, 71, 230),
.accent_text = Color.rgb8(239, 246, 255),
Expand All @@ -119,6 +126,13 @@ pub const dark_colors = canvas.ColorTokens{
.surface_pressed = Color.rgba8(255, 255, 255, 38),
.text = Color.rgb8(250, 250, 250),
.text_muted = Color.rgb8(161, 161, 161),
.syntax_plain = Color.rgb8(237, 237, 237),
.syntax_comment = Color.rgb8(161, 161, 161),
.syntax_keyword = Color.rgb8(247, 95, 143),
.syntax_literal = Color.rgb8(98, 192, 115),
.syntax_function = Color.rgb8(191, 122, 240),
.syntax_property = Color.rgb8(255, 97, 102),
.syntax_constant = Color.rgb8(82, 168, 255),
.border = Color.rgba8(255, 255, 255, 26),
.accent = Color.rgb8(43, 127, 255),
// Near-black on the bright accent (6.0:1) — the light near-white
Expand Down
7 changes: 7 additions & 0 deletions examples/deck/src/theme.zig
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ pub const chassis_colors = canvas.ColorTokens{
.surface_pressed = key_pressed,
.text = ink,
.text_muted = engraving,
.syntax_plain = phosphor_pale,
.syntax_comment = phosphor_dim,
.syntax_keyword = Color.rgb8(247, 95, 143),
.syntax_literal = Color.rgb8(98, 192, 115),
.syntax_function = Color.rgb8(191, 122, 240),
.syntax_property = Color.rgb8(255, 97, 102),
.syntax_constant = Color.rgb8(82, 168, 255),
.border = putty_line,
.accent = phosphor,
.accent_text = Color.rgb8(7, 21, 13),
Expand Down
Loading
Loading