diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 4780d96800..ac3d9cf35b 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -4,6 +4,7 @@ on: paths: - "website/**" - "themes/**" + - "Cargo.toml" - "script/build-website-versions" - "script/snapshot-website" - "script/test-snapshot-website" @@ -40,7 +41,9 @@ jobs: SHOWCASES_DIR: ${{ github.workspace }}/.showcases run: | ../script/test-snapshot-website + bun run test:docs bun run test:showcases bun run build bun run test:seo + bun run test:links bun run test:versioned-examples diff --git a/script/build-website-versions b/script/build-website-versions index 602e9a39f8..a00c7cf985 100755 --- a/script/build-website-versions +++ b/script/build-website-versions @@ -62,6 +62,15 @@ mkdir -p "$repo_root/website/dist" git -C "$repo_root" worktree add --detach "$work_dir/main" origin/main >/dev/null +# Documentation writes `{{gpui_pre_version}}` for the GPUI snapshot; each +# build resolves it from the Cargo.toml of the revision it documents, because +# the temporary website copies have no workspace manifest next to them. +gpui_pre_version() { + git -C "$repo_root" show "$1:Cargo.toml" 2>/dev/null \ + | sed -nE 's/^gpui[[:space:]]*=.*package[[:space:]]*=[[:space:]]*"gpui-pre".*version[[:space:]]*=[[:space:]]*"=?([^"]+)".*/\1/p' \ + | head -n 1 +} + snapshot_website() { local tag=$1 local target=$2 @@ -78,6 +87,7 @@ ln -s "$repo_root/website/node_modules" "$latest_dir/node_modules" echo "Building $latest_release at /" ( cd "$latest_dir" + GPUI_PRE_VERSION="$(gpui_pre_version "$latest_release")" \ PUBLIC_SITE_VERSION="$latest_release" \ PUBLIC_SITE_LATEST_VERSION="$latest_release" \ PUBLIC_SITE_INDEXABLE=true \ @@ -100,6 +110,7 @@ echo "Building main at /versions/main" ( cd "$main_dir" ln -s "$repo_root/website/node_modules" node_modules + GPUI_PRE_VERSION="$(gpui_pre_version origin/main)" \ PUBLIC_SITE_VERSION=main \ PUBLIC_SITE_LATEST_VERSION="$latest_release" \ PUBLIC_SITE_INDEXABLE=false \ @@ -134,6 +145,7 @@ for tag in "${release_tags[@]:1}"; do echo "Building $tag at /versions/$tag" ( cd "$version_dir" + GPUI_PRE_VERSION="$(gpui_pre_version "$tag")" \ PUBLIC_SITE_VERSION="$tag" \ PUBLIC_SITE_LATEST_VERSION="$latest_release" \ PUBLIC_SITE_INDEXABLE=false \ diff --git a/website/DESIGN.md b/website/DESIGN.md index bc550e125b..c85694eb07 100644 --- a/website/DESIGN.md +++ b/website/DESIGN.md @@ -93,6 +93,9 @@ Chinese. Monospace prefers `ui-monospace` / SF Mono and falls back to - **Body** — 1rem, line-height 1.7; docs prose is capped at `46rem`. - **Kicker / label** — 0.66–0.68rem mono, uppercase, wide tracking, muted. Small mono labels, not colour, mark structure. +- **Maturity labels** (`maturity` frontmatter) sit between a page title and its + standfirst as the same mono labels on a hairline border. Preview and + Experimental are not warnings, so they take no status colour. Two constraints that are easy to get wrong: diff --git a/website/README.md b/website/README.md index 22da9c1133..e6352d7587 100644 --- a/website/README.md +++ b/website/README.md @@ -31,3 +31,31 @@ with `bun install --frozen-lockfile --cwd ../../gpui-kit-showcases` before build The release workflow fetches the latest approved catalog automatically; see the [Showcase contribution guide](https://github.com/longbridge/gpui-kit-showcases/blob/main/CONTRIBUTING.md) for app submissions and full-window screenshot instructions. + +## Writing documentation + +The site is published once per version: the latest release at `/`, `main` at +`/versions/main`, and older releases at `/versions/`. Pages must keep the +reader in the version they are reading. + +- **Link to the Markdown file with a relative path**, such as + `[Entity](./entity.md)` or `[Dialog](../component/dialog.md)`. The build + resolves it inside the current version. A site-root path such as + `/docs/entity` points at the default version and is rejected by + `bun run test:docs`. A Chinese page links the Chinese page when one exists. +- **Write `{{gpui_pre_version}}` for the GPUI snapshot version**, in prose, + code and links alike, for example + `https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Window.html`. + The value comes from the `gpui` entry in the workspace `Cargo.toml`, so a + snapshot bump updates every page in both locales. `bun run test:docs` + rejects the pinned version written out by hand; a deliberate reference to + another snapshot is listed in `tests/doc-sources.test.ts`. +- **Mark maturity in the frontmatter** of a page whose capability is not on + the stable desktop path: `maturity: [preview]`, `[experimental]`, + `[showcase-only]`, or `[platform-dependent]`, combined as needed. Unmarked pages are Stable. The + labels render under the title and link to their definitions on the + documentation home. Both locales must carry the same value. + +`bun run test:links` checks every link in a finished build, and +`bun run test:versioned-examples` repeats that check for a build at +`/versions/test/`, where a link that leaves the version fails. diff --git a/website/astro.config.mjs b/website/astro.config.mjs index fae2dfe40a..47424c7fe3 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -10,6 +10,8 @@ import { rehypeHeadingIds, unified } from '@astrojs/markdown-remark'; import { remarkCallouts } from './src/lib/remark-callouts.js'; import { remarkComparisonStatus } from './src/lib/remark-comparison-status.js'; import { remarkDocLinks } from './src/lib/remark-doc-links.js'; +import { remarkDocVariables } from './src/lib/doc-variables.js'; +import { remarkMaturity } from './src/lib/remark-maturity.js'; import { remarkSnippets } from './src/lib/remark-snippets.js'; import { rehypeHeadingAnchors } from './src/lib/rehype-heading-anchors.js'; import { wasmExamplesDevServer } from './src/lib/wasm-middleware.js'; @@ -56,12 +58,16 @@ export default defineConfig({ outDir: resolve(process.cwd(), process.env.SITE_OUT_DIR || './dist'), output: 'static', trailingSlash: 'never', - redirects: { - ...componentRedirects, - ...legacyDocRedirects, - '/docs/ui-testing': '/docs/test', - '/zh-CN/docs/ui-testing': '/zh-CN/docs/test', - }, + // Astro places each source below `base` but writes the destination as + // given, so a versioned build would redirect into the default version. + redirects: Object.fromEntries( + Object.entries({ + ...componentRedirects, + ...legacyDocRedirects, + '/docs/ui-testing': '/docs/test', + '/zh-CN/docs/ui-testing': '/zh-CN/docs/test', + }).map(([from, to]) => [from, `${BASE.replace(/\/$/, '')}${to}`]), + ), integrations: [ vue({ devtools: false }), @@ -72,7 +78,7 @@ export default defineConfig({ // Astro 7 made Sätteri the default processor; the remark/rehype pipeline is // opt-in now, and the math plugins only run on it. processor: unified({ - remarkPlugins: [remarkMath, remarkSnippets, remarkCallouts, remarkComparisonStatus, [remarkDocLinks, { base: BASE }]], + remarkPlugins: [remarkMath, remarkSnippets, remarkCallouts, remarkComparisonStatus, remarkDocVariables, remarkMaturity, [remarkDocLinks, { base: BASE }]], rehypePlugins: [rehypeMathjax, rehypeHeadingIds, rehypeHeadingAnchors], }), shikiConfig, diff --git a/website/component/collapsible.md b/website/component/collapsible.md index ef03ef8c7a..4ebbd6761c 100644 --- a/website/component/collapsible.md +++ b/website/component/collapsible.md @@ -65,6 +65,6 @@ Collapsible::new() .content(options) ``` -The content remains mounted while closed so it can be measured and immediately reverse if toggled mid-animation. Without `motion_id`, the component keeps the immediate mount/unmount behavior. See the [GPUI Base Motion guide](/base/motion) for timing, reduced-motion, and performance details. +The content remains mounted while closed so it can be measured and immediately reverse if toggled mid-animation. Without `motion_id`, the component keeps the immediate mount/unmount behavior. See the [GPUI Base Motion guide](../base/motion.md) for timing, reduced-motion, and performance details. [Collapsible]: https://docs.rs/gpui-component/latest/gpui_component/collapsible/struct.Collapsible.html diff --git a/website/component/dock.md b/website/component/dock.md index c54f33fe77..4d34fb380a 100644 --- a/website/component/dock.md +++ b/website/component/dock.md @@ -10,7 +10,7 @@ Dock builds application workspaces from draggable tab groups, nested splits, and `gpui-base` owns the data model, layout calculation, and drag-and-drop behavior. `gpui-component` supplies the polished controls and visual language. Use `gpui_kit::component::dock` when you want a Dock ready to fit into a real application. -For the renderer-independent architecture and custom-renderer API, see [Dock — gpui-base](/base/dock). +For the renderer-independent architecture and custom-renderer API, see [Dock — gpui-base](../base/dock.md). ## Create a dock area diff --git a/website/component/focus-trap.md b/website/component/focus-trap.md index 09a750f089..ffde4da828 100644 --- a/website/component/focus-trap.md +++ b/website/component/focus-trap.md @@ -7,7 +7,7 @@ description: A utility element that traps keyboard focus within a container, pre Focus trap utility for constraining keyboard focus within a specific container. Essential for modal dialogs, sheets, and overlay components to provide proper keyboard navigation accessibility. -**Note:** [Dialog](/component/dialog) and [Sheet](/component/sheet) components have focus trap built-in. You only need to manually use `focus_trap()` for custom modal-like components. +**Note:** [Dialog](./dialog.md) and [Sheet](./sheet.md) components have focus trap built-in. You only need to manually use `focus_trap()` for custom modal-like components. ## Import @@ -245,12 +245,12 @@ impl Render for ModalView { ## See Also -- [Root View System](/component/root) - Manages focus trap behavior at the window level -- [Dialog](/component/dialog) - Uses focus trap automatically -- [Sheet](/component/sheet) - Uses focus trap automatically +- [Root View System](./root.md) - Manages focus trap behavior at the window level +- [Dialog](./dialog.md) - Uses focus trap automatically +- [Sheet](./sheet.md) - Uses focus trap automatically - [focus-trap-react](https://github.com/focus-trap/focus-trap-react) - Similar concept for React applications [Root]: https://docs.rs/gpui-component/latest/gpui_component/struct.Root.html [FocusTrapElement]: https://docs.rs/gpui-component/latest/gpui_component/trait.FocusTrapElement.html -[Dialog]: /component/dialog -[Sheet]: /component/sheet +[Dialog]: ./dialog.md +[Sheet]: ./sheet.md diff --git a/website/component/hover-card.md b/website/component/hover-card.md index 5a2ab6af08..ad241035ed 100644 --- a/website/component/hover-card.md +++ b/website/component/hover-card.md @@ -9,7 +9,7 @@ HoverCard component for displaying rich content that appears when the mouse hove This is most like the [Popover] component, but triggered by hover instead of click, and with timing controls for a smoother user experience. -On iOS and Android, tap the trigger to open or close the card. Tapping outside closes it; tapping inside keeps it open. Hover delays do not apply. Tooltip hints remain disabled; see [Mobile](/docs/mobile). +On iOS and Android, tap the trigger to open or close the card. Tapping outside closes it; tapping inside keeps it open. Hover delays do not apply. Tooltip hints remain disabled; see [Mobile](../docs/mobile.md). ## Import diff --git a/website/component/text-view.md b/website/component/text-view.md index 2623401fac..68d3b02078 100644 --- a/website/component/text-view.md +++ b/website/component/text-view.md @@ -7,9 +7,9 @@ description: Renders Markdown and HTML text with optional custom Markdown plugin `TextView` renders formatted text in GPUI. It supports Markdown and simple HTML, text selection, code block actions, and custom Markdown plugins for project-specific syntax. -The canonical implementation now lives in `gpui-base`; this module remains a compatibility re-export and provides component-theme adaptation. Base-only setup, complete default styling, and opt-in syntax highlighting are documented on [GPUI Base TextView](/base/text-view). +The canonical implementation now lives in `gpui-base`; this module remains a compatibility re-export and provides component-theme adaptation. Base-only setup, complete default styling, and opt-in syntax highlighting are documented on [GPUI Base TextView](../base/text-view.md). -TextView is selectable by default and uses the shared window selection engine from `gpui-base`. Use `.selectable(false)` only when selection must be disabled. See [GPUI Base Text Selection](/base/text-selection) when integrating plain text or a custom renderer with the same selection. +TextView is selectable by default and uses the shared window selection engine from `gpui-base`. Use `.selectable(false)` only when selection must be disabled. See [GPUI Base Text Selection](../base/text-selection.md) when integrating plain text or a custom renderer with the same selection. ## Import @@ -93,7 +93,7 @@ reduced motion. Nothing animates unless the view opts in. Pass a `TextViewMotion` through `.motion(...)` to choose the duration or easing yourself, or to reveal each chunk word by word; see -[GPUI Base TextView](/base/text-view#retained-state-and-streaming-updates). +[GPUI Base TextView](../base/text-view.md#retained-state-and-streaming-updates). ### Highlight ranges @@ -215,7 +215,7 @@ following the finger while it stays down. Lifting it opens an edit menu with Dragging a handle moves that end while the other stays put; `Select All` selects the view that was pressed, and its handles keep working on the result. -The handles and the menu are drawn by [`Root`](/component/root) for the whole +The handles and the menu are drawn by [`Root`](./root.md) for the whole window selection, so they cover a selection that spans several views. A tap elsewhere clears them, and the menu steps aside while the content scrolls under a finger. diff --git a/website/component/title-bar.md b/website/component/title-bar.md index 904bc04a81..731be44492 100644 --- a/website/component/title-bar.md +++ b/website/component/title-bar.md @@ -1,6 +1,7 @@ --- title: TitleBar description: A custom window title bar component with window controls and custom content support. +maturity: [platform-dependent] --- # TitleBar diff --git a/website/component/tooltip.md b/website/component/tooltip.md index 7eb7cc1f0d..2135dd2f6d 100644 --- a/website/component/tooltip.md +++ b/website/component/tooltip.md @@ -9,7 +9,7 @@ A versatile tooltip component that displays helpful information when hovering ov ## Mobile behavior -On iOS and Android, tooltips managed by the GPUI Base overlay are disabled. Shared components may keep their tooltip configuration, but mobile actions still need visible or accessible labels. Direct GPUI `.tooltip()` calls, including the basic `div()` example below, bypass this overlay and are not disabled by this policy. See [Mobile](/docs/mobile) for integration guidance. +On iOS and Android, tooltips managed by the GPUI Base overlay are disabled. Shared components may keep their tooltip configuration, but mobile actions still need visible or accessible labels. Direct GPUI `.tooltip()` calls, including the basic `div()` example below, bypass this overlay and are not disabled by this policy. See [Mobile](../docs/mobile.md) for integration guidance. ## Import diff --git a/website/docs/action.md b/website/docs/action.md index 4c0330ac0a..a5f9bbe02f 100644 --- a/website/docs/action.md +++ b/website/docs/action.md @@ -8,7 +8,7 @@ order: -2.62 An **Action** represents an operation the application can perform. A shortcut, menu item, command palette, button, or another Action handler can all dispatch the same typed value. GPUI routes it to the part of the [Element](./element) tree that owns the command. An [Event](./event) serves the other direction: it reports something that happened after state changed. -The [GPUI Action source](https://docs.rs/crate/gpui-pre/0.3.6/source/src/action.rs) defines the macro, trait, and registry described here. +The [GPUI Action source](https://docs.rs/crate/gpui-pre/{{gpui_pre_version}}/source/src/action.rs) defines the macro, trait, and registry described here. This page explains command definition and dispatch. Start with [Focus](./focus) if you have not yet created a keyboard target; see [KeyBinding](./keybinding) for key notation, context matching, and keymap setup. diff --git a/website/docs/animation.md b/website/docs/animation.md index ee89af21e5..351aef6f16 100644 --- a/website/docs/animation.md +++ b/website/docs/animation.md @@ -11,7 +11,7 @@ GPUI Kit offers three levels of motion. Choose by **what owns the changing value | Level | Use it for | State and policy | | --- | --- | --- | | GPUI `Animation` and `AnimationExt` | An element entering, pulsing, or running a fixed series while mounted | GPUI retains playback under the wrapper's [`ElementId`](./element_id); the caller chooses duration, easing, and visual property. | -| [GPUI Base Motion](/base/motion) | A target that changes during motion, an exit before unmount, keyframes, or measured reveal | Base retains each channel under a stable key and requests frames through [Window](./window) while active; the caller chooses the visual result. | +| [GPUI Base Motion](../base/motion.md) | A target that changes during motion, an exit before unmount, keyframes, or measured reveal | Base retains each channel under a stable key and requests frames through [Window](./window) while active; the caller chooses the visual result. | | GPUI Component motion | A styled control whose appearance follows the theme | `cx.theme().motion_tokens()` supplies semantic timing, easing, springs, and distances; components compose these with GPUI or Base. | The application owns the semantic state: whether a dialog is open, which tab is selected, or where a slider points. An animation samples that state for presentation. Keep the result understandable at both endpoints and when motion is disabled. @@ -113,7 +113,7 @@ The excerpt shows the lifecycle decision; the [full example](https://github.com/ ## GPUI's element animation -`Animation::new(duration)` creates a one-shot, linear animation. `AnimationExt::with_animation(id, animation, animator)` wraps an `IntoElement`; the callback receives that element and an eased progress value. GPUI calls it during layout, applies the returned element's style, and requests another frame until the animation ends. The callback may change any property supported by that element, such as opacity or a transform. See the [GPUI 0.3.6 animation source](https://docs.rs/gpui-pre/0.3.6/src/gpui/elements/animation.rs.html) for the wrapper's playback rules. Here `gpui-pre` is the publication and version-alignment package name for GPUI; it is not an additional application layer. +`Animation::new(duration)` creates a one-shot, linear animation. `AnimationExt::with_animation(id, animation, animator)` wraps an `IntoElement`; the callback receives that element and an eased progress value. GPUI calls it during layout, applies the returned element's style, and requests another frame until the animation ends. The callback may change any property supported by that element, such as opacity or a transform. See the [GPUI {{gpui_pre_version}} animation source](https://docs.rs/gpui-pre/{{gpui_pre_version}}/src/gpui/elements/animation.rs.html) for the wrapper's playback rules. Here `gpui-pre` is the publication and version-alignment package name for GPUI; it is not an additional application layer. ```rust use std::time::Duration; @@ -280,7 +280,7 @@ Base also provides the following choices: | Delay repeated items | `Stagger` | Computes a delay by index and origin; it does not own the list or its IDs. | | Expand content of unknown height | `MotionReveal` | Measures the child and clips its visible height by caller-supplied progress. It does not sample or animate progress itself. | -See the [Base Motion guide](/base/motion) for the full signatures, validation rules, examples, and benchmark. Its `Transition` is distinct from the older `gpui_kit::base::animation::EffectTransition`, which wraps GPUI `with_animation` to apply predefined fade, slide, width, and height effects. For new target-driven work, use `base::motion` primitives and apply the sampled value yourself. +See the [Base Motion guide](../base/motion.md) for the full signatures, validation rules, examples, and benchmark. Its `Transition` is distinct from the older `gpui_kit::base::animation::EffectTransition`, which wraps GPUI `with_animation` to apply predefined fade, slide, width, and height effects. For new target-driven work, use `base::motion` primitives and apply the sampled value yourself. ## GPUI Component: semantic motion policy diff --git a/website/docs/assets.md b/website/docs/assets.md index 6e4434d903..4457cbb335 100644 --- a/website/docs/assets.md +++ b/website/docs/assets.md @@ -327,6 +327,6 @@ On WebAssembly, `Assets::new(endpoint)` and `AllAssets::new(endpoint)` use the s [rust-embed]: https://docs.rs/rust-embed/latest/rust_embed/ [IconName]: https://docs.rs/gpui-kit-assets/0.6.5/gpui_kit_assets/enum.IconName.html -[Icon]: https://docs.rs/gpui-component/0.6.5/gpui_component/struct.Icon.html +[Icon]: https://docs.rs/gpui-component/latest/gpui_component/struct.Icon.html [assets]: https://github.com/longbridge/gpui-kit/tree/main/crates/assets/assets/icons [gpui-kit-assets]: https://docs.rs/crate/gpui-kit-assets/0.6.5 diff --git a/website/docs/comparison.md b/website/docs/comparison.md index 3e30cf446b..37cf686059 100644 --- a/website/docs/comparison.md +++ b/website/docs/comparison.md @@ -15,7 +15,7 @@ Legend: Yes · Yes · AI can edit GPUI Kit's Rust UI code directly; a drag-and-drop editor is unnecessary. @@ -82,22 +82,22 @@ The capability matrix shows coverage; these trade-offs show when that coverage m **Large-table virtualization.** This retains the original table's distinction between visible rows and visible columns: GPUI Kit's `DataTable` tracks both in its [visible range](https://github.com/longbridge/gpui-kit/blob/main/crates/component/src/table/state.rs); [egui_extras `TableBody::rows`](https://docs.rs/egui_extras/latest/egui_extras/struct.TableBody.html#method.rows) renders visible rows; and Qt Quick [`TableView`](https://doc.qt.io/qt-6/qml-qtquick-tableview.html) reuses delegates as rows and columns leave the viewport. Iced's documented [table API](https://docs.rs/iced/0.14.0/iced/widget/table/fn.table.html) does not promise built-in virtualization. Slint's [`StandardTableView`](https://github.com/slint-ui/slint/blob/master/internal/compiler/widgets/fluent/tableview.slint) places its row repeater inside a `ListView`, which [instantiates only visible rows](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/listview/). Its column and cell repeaters do not use the same viewport virtualization. -**Editor and highlighting.** GPUI Kit's [Editor](/component/editor) stores text in a [Rope](https://github.com/longbridge/gpui-kit/blob/main/crates/base/src/input/base/state.rs#L343). It provides code-oriented behavior such as folding, diagnostics, completion, and hover. Syntax highlighting uses [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) when the matching [grammar feature](/component/editor#basic-usage) is enabled; large buffers are parsed in the background, and edits can reuse the previous parse tree. The editor's text model and highlighting engine are separate decisions: a Rope alone does not imply highlighted text. Iced's [TextEditor](https://docs.rs/iced/latest/iced/widget/text_editor/struct.TextEditor.html) has feature-gated highlighting, egui's [TextEdit](https://docs.rs/egui/latest/egui/widgets/text_edit/struct.TextEdit.html) accepts a custom layouter and can use [egui_extras syntax highlighting](https://docs.rs/egui_extras/latest/egui_extras/syntax_highlighting/), while Qt provides [QSyntaxHighlighter](https://doc.qt.io/qt-6/qsyntaxhighlighter.html). This row describes an available API, not comparable performance at the same document size. +**Editor and highlighting.** GPUI Kit's [Editor](../component/editor.md) stores text in a [Rope](https://github.com/longbridge/gpui-kit/blob/main/crates/base/src/input/base/state.rs#L343). It provides code-oriented behavior such as folding, diagnostics, completion, and hover. Syntax highlighting uses [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) when the matching [grammar feature](../component/editor.md#basic-usage) is enabled; large buffers are parsed in the background, and edits can reuse the previous parse tree. The editor's text model and highlighting engine are separate decisions: a Rope alone does not imply highlighted text. Iced's [TextEditor](https://docs.rs/iced/latest/iced/widget/text_editor/struct.TextEditor.html) has feature-gated highlighting, egui's [TextEdit](https://docs.rs/egui/latest/egui/widgets/text_edit/struct.TextEdit.html) accepts a custom layouter and can use [egui_extras syntax highlighting](https://docs.rs/egui_extras/latest/egui_extras/syntax_highlighting/), while Qt provides [QSyntaxHighlighter](https://doc.qt.io/qt-6/qsyntaxhighlighter.html). This row describes an available API, not comparable performance at the same document size. **Text models.** The table names each editor's storage or editing abstraction: GPUI Kit keeps text in a Rope; Iced uses [COSMIC Text](https://docs.iced.rs/src/iced_graphics/text/editor.rs.html), egui accepts a [TextBuffer](https://docs.rs/egui/latest/egui/widgets/text_edit/trait.TextBuffer.html), and Qt exposes [QTextDocument](https://doc.qt.io/qt-6/qtextdocument.html). This describes an API shape, not a comparative speed result. -**Formatted text.** GPUI Kit [TextView](/component/text-view) renders selectable Markdown and HTML; its Markdown parser also passes inline HTML nodes to its HTML parser. Iced's [Markdown widget](https://docs.rs/iced/latest/iced/widget/markdown/) does not expose an HTML item; egui relies on add-ons for Markdown, and Qt's [QTextEdit](https://doc.qt.io/qt-6/qtextedit.html) supports Markdown with only part of embedded HTML. “No” in this row means the documented built-in Markdown path does not handle inline HTML; an application can still add another renderer. +**Formatted text.** GPUI Kit [TextView](../component/text-view.md) renders selectable Markdown and HTML; its Markdown parser also passes inline HTML nodes to its HTML parser. Iced's [Markdown widget](https://docs.rs/iced/latest/iced/widget/markdown/) does not expose an HTML item; egui relies on add-ons for Markdown, and Qt's [QTextEdit](https://doc.qt.io/qt-6/qtextedit.html) supports Markdown with only part of embedded HTML. “No” in this row means the documented built-in Markdown path does not handle inline HTML; an application can still add another renderer. -**HTML rendering.** [TextView::html](/component/text-view#html) handles article content such as headings, paragraphs, links, images, lists, and tables. It is partial because it does not implement general CSS layout or run scripts; it is not a WebView. Qt's [QTextDocument](https://doc.qt.io/qt-6/richtext-html-subset.html) supports a broader HTML 4 and CSS subset, also without browser behavior. This row compares documented native HTML document rendering, so a separate WebView integration does not count. Iced, egui, and Slint do not provide an equivalent native document renderer in the compared APIs. +**HTML rendering.** [TextView::html](../component/text-view.md#html) handles article content such as headings, paragraphs, links, images, lists, and tables. It is partial because it does not implement general CSS layout or run scripts; it is not a WebView. Qt's [QTextDocument](https://doc.qt.io/qt-6/richtext-html-subset.html) supports a broader HTML 4 and CSS subset, also without browser behavior. This row compares documented native HTML document rendering, so a separate WebView integration does not count. Iced, egui, and Slint do not provide an equivalent native document renderer in the compared APIs. -**Tables and lists.** “Advanced data table” means a component for large datasets with virtualized rendering, sorting, selection, and column management. GPUI Kit's [DataTable](/component/data-table) includes these behaviors; sorting the underlying data is supplied by its delegate. Qt's [QTableView](https://doc.qt.io/qt-6/qtableview.html) and model/view system also cover this use case. [egui_extras TableBuilder](https://docs.rs/egui_extras/latest/egui_extras/struct.TableBuilder.html) virtualizes rows and resizes columns, but leaves more data behavior to the app. Slint's [StandardTableView](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/standardtableview/) exposes sorting callbacks and selection; Iced's [Table](https://docs.rs/iced/latest/iced/widget/table/fn.table.html) provides a simpler starting point. Separately, GPUI Kit [VirtualList](/component/virtual-list), egui [ScrollArea::show_rows](https://docs.rs/egui/latest/egui/containers/scroll_area/struct.ScrollArea.html#method.show_rows), Qt [ListView](https://doc.qt.io/qt-6/qml-qtquick-listview.html), and Slint [ListView](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/listview/) create visible list items on demand. Iced offers [scrolling and visibility primitives](https://docs.rs/iced/latest/iced/widget/struct.Sensor.html), with virtualization left to the application. +**Tables and lists.** “Advanced data table” means a component for large datasets with virtualized rendering, sorting, selection, and column management. GPUI Kit's [DataTable](../component/data-table.md) includes these behaviors; sorting the underlying data is supplied by its delegate. Qt's [QTableView](https://doc.qt.io/qt-6/qtableview.html) and model/view system also cover this use case. [egui_extras TableBuilder](https://docs.rs/egui_extras/latest/egui_extras/struct.TableBuilder.html) virtualizes rows and resizes columns, but leaves more data behavior to the app. Slint's [StandardTableView](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/standardtableview/) exposes sorting callbacks and selection; Iced's [Table](https://docs.rs/iced/latest/iced/widget/table/fn.table.html) provides a simpler starting point. Separately, GPUI Kit [VirtualList](../component/virtual-list.md), egui [ScrollArea::show_rows](https://docs.rs/egui/latest/egui/containers/scroll_area/struct.ScrollArea.html#method.show_rows), Qt [ListView](https://doc.qt.io/qt-6/qml-qtquick-listview.html), and Slint [ListView](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/listview/) create visible list items on demand. Iced offers [scrolling and visibility primitives](https://docs.rs/iced/latest/iced/widget/struct.Sensor.html), with virtualization left to the application. -**Dense desktop UI.** GPUI Kit includes [VirtualList](/component/virtual-list), [DataTable](/component/data-table), [charts](/component/chart), [TextView](/component/text-view), and [Dock](/component/dock). Iced has [rich text](https://docs.rs/iced/latest/iced/widget/fn.rich_text.html), a [table](https://docs.rs/iced/latest/iced/widget/table/fn.table.html), a feature-gated [Markdown widget](https://docs.rs/iced/latest/iced/widget/markdown/), [Canvas](https://docs.rs/iced/latest/iced/widget/struct.Canvas.html), and [pane grids](https://docs.rs/iced/latest/iced/widget/pane_grid/); assembling a virtualized table or chart remains application work. The egui ecosystem supplies third-party [egui_commonmark](https://docs.rs/egui_commonmark/latest/egui_commonmark/) for Markdown, [egui_dock](https://docs.rs/egui_dock/latest/egui_dock/) for docking, [egui_extras tables](https://docs.rs/egui_extras/latest/egui_extras/struct.TableBuilder.html) and [egui_plot](https://docs.rs/egui_plot/latest/egui_plot/). Qt provides [model/view tables](https://doc.qt.io/qt-6/modelview.html), [dock widgets](https://doc.qt.io/qt-6/qdockwidget.html), [QTextDocument](https://doc.qt.io/qt-6/qtextdocument.html), and the separately licensed [Qt Graphs](https://doc.qt.io/qt-6/qtgraphs-index.html) module. Slint provides [StandardTableView](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/standardtableview/) and a [ListView that instantiates visible items](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/listview/); its [Path](https://docs.slint.dev/latest/docs/slint/reference/elements/path/) can draw custom graphics, but is not a ready-made chart. +**Dense desktop UI.** GPUI Kit includes [VirtualList](../component/virtual-list.md), [DataTable](../component/data-table.md), [charts](../component/chart.md), [TextView](../component/text-view.md), and [Dock](../component/dock.md). Iced has [rich text](https://docs.rs/iced/latest/iced/widget/fn.rich_text.html), a [table](https://docs.rs/iced/latest/iced/widget/table/fn.table.html), a feature-gated [Markdown widget](https://docs.rs/iced/latest/iced/widget/markdown/), [Canvas](https://docs.rs/iced/latest/iced/widget/struct.Canvas.html), and [pane grids](https://docs.rs/iced/latest/iced/widget/pane_grid/); assembling a virtualized table or chart remains application work. The egui ecosystem supplies third-party [egui_commonmark](https://docs.rs/egui_commonmark/latest/egui_commonmark/) for Markdown, [egui_dock](https://docs.rs/egui_dock/latest/egui_dock/) for docking, [egui_extras tables](https://docs.rs/egui_extras/latest/egui_extras/struct.TableBuilder.html) and [egui_plot](https://docs.rs/egui_plot/latest/egui_plot/). Qt provides [model/view tables](https://doc.qt.io/qt-6/modelview.html), [dock widgets](https://doc.qt.io/qt-6/qdockwidget.html), [QTextDocument](https://doc.qt.io/qt-6/qtextdocument.html), and the separately licensed [Qt Graphs](https://doc.qt.io/qt-6/qtgraphs-index.html) module. Slint provides [StandardTableView](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/standardtableview/) and a [ListView that instantiates visible items](https://docs.slint.dev/latest/docs/slint/reference/std-widgets/views/listview/); its [Path](https://docs.slint.dev/latest/docs/slint/reference/elements/path/) can draw custom graphics, but is not a ready-made chart. -**Product readiness.** GPUI Kit's [Accessibility](/docs/accessibility), [Testing](/docs/test), [I18n](/docs/i18n), and [Animation](/docs/animation) guides describe the local implementation and its limits. Its components contain translations, while application strings and locale policy remain the application's responsibility. Iced's [accessibility integration is still tracked as open work](https://github.com/iced-rs/iced/issues/552); its [test harness](https://docs.rs/iced_test/latest/iced_test/) is available. egui documents [AccessKit and egui_kittest](https://github.com/emilk/egui/blob/main/docs/accessibility.md). Qt has mature [accessibility](https://doc.qt.io/qt-6/accessible.html), [internationalization](https://doc.qt.io/qt-6/internationalization.html), and [Qt Test](https://doc.qt.io/qt-6/qtest-overview.html) facilities. Slint documents [accessibility properties](https://docs.slint.dev/latest/docs/slint/reference/common/#accessibility-properties) and [translations](https://docs.slint.dev/latest/docs/slint/guide/development/translations/); its [testing backend](https://docs.slint.dev/latest/docs/rust/i_slint_backend_testing/) is preliminary and belongs to an internal crate. A “Yes” in the accessibility row still requires testing with assistive technology on each target platform. +**Product readiness.** GPUI Kit's [Accessibility](./accessibility.md), [Testing](./test.md), [I18n](./i18n.md), and [Animation](./animation.md) guides describe the local implementation and its limits. Its components contain translations, while application strings and locale policy remain the application's responsibility. Iced's [accessibility integration is still tracked as open work](https://github.com/iced-rs/iced/issues/552); its [test harness](https://docs.rs/iced_test/latest/iced_test/) is available. egui documents [AccessKit and egui_kittest](https://github.com/emilk/egui/blob/main/docs/accessibility.md). Qt has mature [accessibility](https://doc.qt.io/qt-6/accessible.html), [internationalization](https://doc.qt.io/qt-6/internationalization.html), and [Qt Test](https://doc.qt.io/qt-6/qtest-overview.html) facilities. Slint documents [accessibility properties](https://docs.slint.dev/latest/docs/slint/reference/common/#accessibility-properties) and [translations](https://docs.slint.dev/latest/docs/slint/guide/development/translations/); its [testing backend](https://docs.slint.dev/latest/docs/rust/i_slint_backend_testing/) is preliminary and belongs to an internal crate. A “Yes” in the accessibility row still requires testing with assistive technology on each target platform. -**Platform reach.** GPUI Kit has [experimental iOS integration](/docs/mobile) and working [WebAssembly showcases](/docs/webassembly); the latter have not been validated here as a full application distribution path. Iced has a [web example](https://github.com/iced-rs/iced/blob/master/examples/README.md#tour), while [native mobile support remains under discussion](https://github.com/iced-rs/iced/issues/302). [eframe](https://github.com/emilk/egui/blob/main/README.md#official-integrations) runs egui on web and native platforms, with Android/iOS integration to validate for each app. Qt documents its [supported platforms](https://doc.qt.io/qt-6/supported-platforms.html) and [WebAssembly limits](https://doc.qt.io/qt-6/wasm.html). Slint documents [mobile](https://docs.slint.dev/latest/docs/slint/guide/platforms/mobile/general/) and [web](https://docs.slint.dev/latest/docs/slint/guide/platforms/web/) targets; its web output is a canvas, without browser screen-reader support. None of these canvas-based Wasm paths should be treated as an HTML application by default. +**Platform reach.** GPUI Kit has [experimental iOS integration](./mobile.md) and working [WebAssembly showcases](./webassembly.md); the latter have not been validated here as a full application distribution path. Iced has a [web example](https://github.com/iced-rs/iced/blob/master/examples/README.md#tour), while [native mobile support remains under discussion](https://github.com/iced-rs/iced/issues/302). [eframe](https://github.com/emilk/egui/blob/main/README.md#official-integrations) runs egui on web and native platforms, with Android/iOS integration to validate for each app. Qt documents its [supported platforms](https://doc.qt.io/qt-6/supported-platforms.html) and [WebAssembly limits](https://doc.qt.io/qt-6/wasm.html). Slint documents [mobile](https://docs.slint.dev/latest/docs/slint/guide/platforms/mobile/general/) and [web](https://docs.slint.dev/latest/docs/slint/guide/platforms/web/) targets; its web output is a canvas, without browser screen-reader support. None of these canvas-based Wasm paths should be treated as an HTML application by default. -**WebView.** GPUI Kit has an experimental [Wry integration](/docs/webview). Its native view covers GPUI elements in the same bounds, so use a separate window or popup when layering matters. The integration currently documents macOS and Windows; its Linux example is unfinished. Qt provides official [WebEngine](https://doc.qt.io/qt-6/qwebengineview.html) and [WebView](https://doc.qt.io/qt-6/qtwebview-index.html) modules, though Qt WebView also limits overlapping QML items. This row counts integrations maintained for the compared framework version: Iced has a third-party [iced_webview](https://docs.rs/iced_webview/latest/iced_webview/) crate targeting Iced 0.13, but no verified integration here for current Iced 0.14; egui has no comparable maintained native WebView, and Slint's [WebView request](https://github.com/slint-ui/slint/issues/3930) remains open. A red dot does not rule out an application-specific bridge. +**WebView.** GPUI Kit has an experimental [Wry integration](./webview.md). Its native view covers GPUI elements in the same bounds, so use a separate window or popup when layering matters. The integration currently documents macOS and Windows; its Linux example is unfinished. Qt provides official [WebEngine](https://doc.qt.io/qt-6/qwebengineview.html) and [WebView](https://doc.qt.io/qt-6/qtwebview-index.html) modules, though Qt WebView also limits overlapping QML items. This row counts integrations maintained for the compared framework version: Iced has a third-party [iced_webview](https://docs.rs/iced_webview/latest/iced_webview/) crate targeting Iced 0.13, but no verified integration here for current Iced 0.14; egui has no comparable maintained native WebView, and Slint's [WebView request](https://github.com/slint-ui/slint/issues/3930) remains open. A red dot does not rule out an application-specific bridge. **Licensing and scope.** GPUI Kit is Apache-2.0; Iced is [MIT](https://github.com/iced-rs/iced/blob/master/LICENSE); egui is [MIT or Apache-2.0](https://github.com/emilk/egui/blob/main/LICENSE-MIT); Qt uses [commercial, LGPLv3, or GPLv3 terms by module](https://doc.qt.io/qt-6/licensing.html), with Qt Graphs GPLv3 or commercial; Slint has [GPLv3 and commercial or royalty-free terms](https://slint.dev/pricing). Native single-platform stacks such as SwiftUI and WinUI have a different platform scope. Electron and Tauri use a WebView/JavaScript UI architecture and deserve a separate evaluation rather than a score in this native UI matrix. Bundle size and frame rate depend on build features, renderers, fonts, packaging, and workload; measure a release build of the product you intend to distribute. diff --git a/website/docs/context.md b/website/docs/context.md index 924cf683c1..c081a7ac00 100644 --- a/website/docs/context.md +++ b/website/docs/context.md @@ -30,7 +30,7 @@ Read the table as a progression: `App` is available first in `application().run` `Context` dereferences to `App`, so code with `cx: &mut Context` can already call App APIs and does not need a separate `&mut App`. It also knows which Entity is current; plain `App` does not. Window remains separate because the same Entity may appear in different windows, while a data-only update may not belong to any window. Window also owns per-window state keyed by [ElementId](./element_id). Async contexts are handles, not long-lived `&mut App` or `&mut Window` borrows. -The [GPUI `Context` source](https://docs.rs/crate/gpui-pre/0.3.6/source/src/app/context.rs) defines the entity-specific methods used below. +The [GPUI `Context` source](https://docs.rs/crate/gpui-pre/{{gpui_pre_version}}/source/src/app/context.rs) defines the entity-specific methods used below. GPUI Kit applications depend on `gpui-kit` and import GPUI through `use gpui_kit::*;`. Call `gpui_kit::init(cx)` before creating component-backed Views. An application-wide [Global](./global) lives on `App`; a component or feature View keeps retained state in an [Entity]. @@ -183,7 +183,7 @@ fn finish_edit(&mut self, window: &mut Window, cx: &mut Context) { } ``` -The deferred closure still receives `this: &mut Self`; do not call `update` on that same Entity from inside it. Deferral is for work that needs the current update to finish, such as focus restoration after changing the UI tree. [`window.on_next_frame(...)`](https://docs.rs/crate/gpui-pre/0.3.6/source/src/window.rs) instead queues a callback for the next platform frame request and wakes the frame source. The callback runs before any drawing for that request; registering it does not mark the window dirty or cause a render. If its work changes visible Entity state, call `cx.notify()` from the callback. Use `window.request_animation_frame()` when the intent is to request a redraw on the next frame. A closed window or released View may prevent deferred work from running, so do not use it as a durable job queue. +The deferred closure still receives `this: &mut Self`; do not call `update` on that same Entity from inside it. Deferral is for work that needs the current update to finish, such as focus restoration after changing the UI tree. [`window.on_next_frame(...)`](https://docs.rs/crate/gpui-pre/{{gpui_pre_version}}/source/src/window.rs) instead queues a callback for the next platform frame request and wakes the frame source. The callback runs before any drawing for that request; registering it does not mark the window dirty or cause a render. If its work changes visible Entity state, call `cx.notify()` from the callback. Use `window.request_animation_frame()` when the intent is to request a redraw on the next frame. A closed window or released View may prevent deferred work from running, so do not use it as a durable job queue. ## Async work @@ -292,6 +292,6 @@ A parent can use `cx.subscribe(&draft, ...)` to handle `SaveRequested`; the even GPUI convention names every context parameter `cx`, regardless of its concrete type, and names the Window parameter `window`. -[Entity]: /docs/entity -[Event]: /docs/event -[Global]: /docs/global +[Entity]: ./entity.md +[Event]: ./event.md +[Global]: ./global.md diff --git a/website/docs/element.md b/website/docs/element.md index e1d10140db..4d6a3a8f64 100644 --- a/website/docs/element.md +++ b/website/docs/element.md @@ -539,6 +539,6 @@ When an existing interactive element such as `div()` already provides the behavi `Element::id()` returning an `ElementId` does more than label pixels: it creates stable identity across frames. Keep IDs unique within their nearest keyed ancestor, and do not add an ID unless the element or an attached behavior needs identity. ::: -[Element]: https://docs.rs/gpui-pre/0.3.6/gpui/trait.Element.html -[IntoElement]: https://docs.rs/gpui-pre/0.3.6/gpui/trait.IntoElement.html -[AnyElement]: https://docs.rs/gpui-pre/0.3.6/gpui/struct.AnyElement.html +[Element]: https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/trait.Element.html +[IntoElement]: https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/trait.IntoElement.html +[AnyElement]: https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.AnyElement.html diff --git a/website/docs/element_id.md b/website/docs/element_id.md index c7c2537ea3..137e5d1083 100644 --- a/website/docs/element_id.md +++ b/website/docs/element_id.md @@ -253,5 +253,5 @@ assert!(archive.visible()); See [Element](./element) for the layout, prepaint, and paint lifecycle, and [Entity](./entity) for state that must outlive an element's presence in the tree. -[Element]: /docs/element -[Entity]: /docs/entity +[Element]: ./element.md +[Entity]: ./entity.md diff --git a/website/docs/entity.md b/website/docs/entity.md index e42b18da42..4333534f7f 100644 --- a/website/docs/entity.md +++ b/website/docs/entity.md @@ -353,7 +353,7 @@ An Entity can coordinate with another Entity in two related ways: They are separate signals: `notify()` does not emit an Event, and `emit(event)` does not by itself notify renderers. A state change that needs both a redraw and a semantic event can do both deliberately, usually once each. An observer can inspect the observed Entity with the handle it receives; it must still avoid re-entering an Entity already borrowed by the callback chain. GPUI delivers these callbacks through its effect cycle, after the current update's borrow has ended; do not rely on the callback having run inside the `update` closure. -Store the [`Subscription`](https://docs.rs/gpui-pre/0.3.6/gpui/struct.Subscription.html) returned by `observe` or `subscribe` on the subscribing Entity, in a `_subscription` field or a `_subscriptions: Vec` field: +Store the [`Subscription`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Subscription.html) returned by `observe` or `subscribe` on the subscribing Entity, in a `_subscription` field or a `_subscriptions: Vec` field: ```rs enum ChatEvent { @@ -465,6 +465,6 @@ An `Entity` can be embedded directly as a child View. Its `EntityId` For an expensive child that often stays unchanged while its parent redraws, GPUI also exposes `child.clone().cached(style)` and the equivalent `AnyView::cached(style)`. The parent must retain the same child Entity, and `style` must provide a definite outer size because GPUI can skip rendering the contents during layout. A clean cached child may replay its previous subtree; notifications, changed bounds or inherited drawing context cause a rebuild. See [View Cache](./view-cache) for the exact boundary and how it differs from element state and virtualization. -[Entity]: https://docs.rs/gpui-pre/0.3.6/gpui/struct.Entity.html -[WeakEntity]: https://docs.rs/gpui-pre/0.3.6/gpui/struct.WeakEntity.html -[Event]: /docs/event +[Entity]: https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Entity.html +[WeakEntity]: https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.WeakEntity.html +[Event]: ./event.md diff --git a/website/docs/focus.md b/website/docs/focus.md index 4a344f4d08..009bfcbce2 100644 --- a/website/docs/focus.md +++ b/website/docs/focus.md @@ -6,7 +6,7 @@ order: -2.4 # Focus -**Focus** identifies the target for keyboard input in one [Window](./window). GPUI uses the focused element's path through the rendered tree to route [Actions and key bindings](./action). A pointer press may move Focus, but drawing a control or giving it an `ElementId` does not. This guide uses the GPUI API published as `gpui-pre` 0.3.6 through `gpui-kit`; `gpui-pre` is the snapshot publishing and version alignment name, not a different rendering engine. +**Focus** identifies the target for keyboard input in one [Window](./window). GPUI uses the focused element's path through the rendered tree to route [Actions and key bindings](./action). A pointer press may move Focus, but drawing a control or giving it an `ElementId` does not. This guide uses the GPUI API published as `gpui-pre` {{gpui_pre_version}} through `gpui-kit`; `gpui-pre` is the snapshot publishing and version alignment name, not a different rendering engine. ## Try the existing example diff --git a/website/docs/fonts.md b/website/docs/fonts.md index 6bdee19c06..f593c4196a 100644 --- a/website/docs/fonts.md +++ b/website/docs/fonts.md @@ -105,7 +105,7 @@ div() .font_weight(FontWeight::BOLD) ``` -These are ordinary [`Styled`](https://docs.rs/gpui-pre/0.3.6/gpui/trait.Styled.html) +These are ordinary [`Styled`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/trait.Styled.html) methods, so they compose with the rest of the style chain. ## Bundling custom fonts diff --git a/website/docs/fps.md b/website/docs/fps.md index 085b5bbb3c..6a0b25829a 100644 --- a/website/docs/fps.md +++ b/website/docs/fps.md @@ -114,7 +114,7 @@ This distinction matters: a 5 ms `FRAME` says that GPUI's draw completed in took 5 ms, or that the GPU and compositor displayed it within 5 ms. The profiler also exposes `dirty_to_draw_duration()` and `PresentTiming` for custom instrumentation, but those are separate measurements from this HUD's -`FRAME`. See GPUI's [frame timing definitions](https://docs.rs/gpui-pre/0.3.6/gpui/profiler/struct.FrameTiming.html) +`FRAME`. See GPUI's [frame timing definitions](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/profiler/struct.FrameTiming.html) and the [sampler implementation](https://github.com/longbridge/gpui-kit/blob/main/crates/fps/src/sampler.rs). ## The headline @@ -241,7 +241,7 @@ The HUD manages this switch while visible. These raw records are useful when you need to correlate a frame with an app event, including the first invalidation timestamp (`dirty_at`) and present submission. They do not by themselves measure GPU completion or photons on screen. See GPUI's -[collector API](https://docs.rs/gpui-pre/0.3.6/gpui/profiler/struct.FrameTimingCollector.html) +[collector API](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/profiler/struct.FrameTimingCollector.html) and [GPUI Kit's monitor](https://github.com/longbridge/gpui-kit/blob/main/crates/fps/src/monitor.rs). ## The first frames are not measured @@ -277,4 +277,4 @@ frame trace unless something else is holding it. The next render starts it all again from an empty sampler: the trace buffer was cleared with the switch, and the frames the window drew meanwhile were nobody's to report. -[`Entity::cached`]: https://docs.rs/gpui-pre/0.3.6/gpui/struct.Entity.html#method.cached +[`Entity::cached`]: https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Entity.html#method.cached diff --git a/website/docs/geometry.md b/website/docs/geometry.md index 52414805e8..11efa847cd 100644 --- a/website/docs/geometry.md +++ b/website/docs/geometry.md @@ -197,7 +197,7 @@ The width remains relative until layout knows the parent. A rem needs the root r ## HSLA and RGBA -GPUI's [`Hsla`](https://docs.rs/gpui-pre/0.3.6/gpui/struct.Hsla.html) stores hue, saturation, lightness, and alpha as values from 0 to 1. Its `hsla(0.6, 0.8, 0.5, 1.)` constructor uses a hue fraction, not degrees, and clamps its four inputs to that range. Use `Hsla` as the default representation for theme colors and their interaction states. [`Rgba`](https://docs.rs/gpui-pre/0.3.6/gpui/struct.Rgba.html) stores red, green, blue, and alpha channels in the same range. `rgb(0x3366CC)` reads a six digit RGB hex value and sets alpha to 1; `rgba(0x3366CC80)` reads eight digits in **RRGGBBAA** order, with alpha `128 / 255` (about 0.502). Convert to `Rgba` when an RGB channel API or hex color is the natural input. +GPUI's [`Hsla`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Hsla.html) stores hue, saturation, lightness, and alpha as values from 0 to 1. Its `hsla(0.6, 0.8, 0.5, 1.)` constructor uses a hue fraction, not degrees, and clamps its four inputs to that range. Use `Hsla` as the default representation for theme colors and their interaction states. [`Rgba`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Rgba.html) stores red, green, blue, and alpha channels in the same range. `rgb(0x3366CC)` reads a six digit RGB hex value and sets alpha to 1; `rgba(0x3366CC80)` reads eight digits in **RRGGBBAA** order, with alpha `128 / 255` (about 0.502). Convert to `Rgba` when an RGB channel API or hex color is the natural input. ```rust use gpui_kit::*; diff --git a/website/docs/index.md b/website/docs/index.md index 63d2e8afc1..10b7c1fa8d 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -29,8 +29,8 @@ extensions; it remains part of the framework's core architecture. Use `gpui-component` for polished controls with one coherent visual language, or build your own design system on the reusable behavior and infrastructure in `gpui-base`. This section covers GPUI Kit setup, shared design and coding guides, and -application development. For library APIs, see [GPUI Component](/component), -[GPUI Base](/base), and [GPUI Shell](/shell). +application development. For library APIs, see [GPUI Component](../component/index.md), +[GPUI Base](../base/index.md), and [GPUI Shell](../shell/index.md). Read [Focus](./focus) for `FocusHandle`, Tab order, and the keyboard target, then [Action](./action) for command dispatch. [KeyBinding](./keybinding) explains how to bind actions and @@ -61,7 +61,7 @@ architecture conventions once the first window works. ## Features - **75+ Components and Primitives**: Forms, navigation, overlays, data display, editing, feedback, layout, and more. -- **Production Ready**: Refined through production desktop applications and continuously tested across GPUI Kit's components and examples. +- **Production Ready**: Refined through production desktop applications and continuously tested across GPUI Kit's components and examples. Capabilities outside that desktop path are labeled by [maturity](#maturity). - **WebAssembly**: Applications and component showcases run on the web through `wasm32-unknown-unknown`. - **Accessibility**: AccessKit roles, names, states, relationships, and actions are built into the interaction layer. - **UI Integration Testing**: Headless windows exercise real pointer, keyboard, focus, layout, and accessibility behavior. @@ -76,6 +76,20 @@ architecture conventions once the first window works. - **Typed Motion**: CSS-aligned easing, timing, keyframes, springs, presence, and measured reveal with allocation-free steady sampling. - **Cross Platform**: Ship one Rust codebase to macOS, Windows, and Linux. +## Maturity + +GPUI Kit's desktop components run in production applications, including Longbridge's. Other capabilities have a shorter track record, so their pages carry a label under the title. A page without a label is Stable. + +| Label | Meaning | +| --- | --- | +| **Stable** | Used by production desktop applications on macOS, Windows, and Linux. | +| **Preview** | Usable and documented. The API and edge-case behavior may still change between releases. | +| **Experimental** | Works with known gaps. Validate it for your product before depending on it. | +| **Showcase only** | Currently used to demonstrate components in a browser, not to ship applications. | +| **Platform-dependent** | Availability or behavior differs by operating system or target. The page lists the differences. | + +A label describes the capability, not the quality of its documentation. WebAssembly, for example, runs the same components as the desktop, but for now it serves the component showcases; shipping an application in a browser is not a supported path yet. + ## Quick Example After preparing the platform libraries in [Installation](./installation), create a Rust project with `cargo new gpui-hello` and enter it with `cd gpui-hello`. Add `gpui-kit` to its `Cargo.toml`: @@ -130,7 +144,7 @@ Run `cargo run` from the project directory. The window shows a label and button; ## Community & Support -Learn how to build interruptible animation in the [GPUI Base Motion guide](/base/motion). +Learn how to build interruptible animation in the [GPUI Base Motion guide](../base/motion.md). - [GitHub Repository](https://github.com/longbridge/gpui-kit) - [Issue Tracker](https://github.com/longbridge/gpui-kit/issues) diff --git a/website/docs/installation.md b/website/docs/installation.md index 0dff4b650c..e66d343d17 100644 --- a/website/docs/installation.md +++ b/website/docs/installation.md @@ -58,7 +58,7 @@ The `0.6` requirement selects a compatible 0.6.x Kit release; this repository cu ### Why the dependency is named `gpui-pre` -Throughout these docs, **GPUI** means [Zed's GPUI](https://github.com/zed-industries/zed/tree/main/crates/gpui). `gpui-pre` is the crates.io package name used to publish a snapshot of GPUI from a recorded Zed commit, alongside its related GPUI crates. It provides a reproducible publication and version alignment path for GPUI Kit; it is not another rendering implementation. The publication process adjusts package names and dependency manifests for crates.io, so API references in this manual target the GPUI version pinned by this Kit release. In this repository that is `gpui-pre = 0.3.6`; application code normally depends only on `gpui-kit` and imports GPUI through `gpui_kit::*`. A newer `gpui-pre` snapshot does not by itself mean that the current GPUI Kit release supports it. +Throughout these docs, **GPUI** means [Zed's GPUI](https://github.com/zed-industries/zed/tree/main/crates/gpui). `gpui-pre` is the crates.io package name used to publish a snapshot of GPUI from a recorded Zed commit, alongside its related GPUI crates. It provides a reproducible publication and version alignment path for GPUI Kit; it is not another rendering implementation. The publication process adjusts package names and dependency manifests for crates.io, so API references in this manual target the GPUI version pinned by this Kit release. In this repository that is `gpui-pre = {{gpui_pre_version}}`; application code normally depends only on `gpui-kit` and imports GPUI through `gpui_kit::*`. A newer `gpui-pre` snapshot does not by itself mean that the current GPUI Kit release supports it. ## Verify the installation diff --git a/website/docs/mobile.md b/website/docs/mobile.md index c0ace79307..d96dd302ad 100644 --- a/website/docs/mobile.md +++ b/website/docs/mobile.md @@ -2,15 +2,22 @@ title: Mobile description: Build an iOS application or embed GPUI Kit in a Swift UIKit container with the experimental gpui-pre-mobile platform. order: -10 +maturity: [experimental, platform-dependent] --- # Mobile +:::info Current scope +GPUI Kit's primary target remains the desktop. Mobile support exists so that some components can be reused inside iOS and Android applications, for example rendering rich content natively with TextView inside a native screen. GPUI Kit does not currently plan to make mobile a primary target or to become a full mobile application framework in the way Flutter is. +::: + Mobile support builds on [gpui-mobile](https://github.com/itsbalamurali/gpui-mobile), created by [itsbalamurali](https://github.com/itsbalamurali) and developed with the community. Credit for the original mobile platform belongs to that project and its contributors. The platform supplies the [Window](./window), touch input, [text system](./text-system), and GPU surface; GPUI and GPUI Kit still own the Rust view tree and components. GPUI Kit currently uses `gpui-pre-mobile`, a temporary compatibility package maintained in a [compatibility fork](https://github.com/longbridge/gpui-mobile). It adapts the original project for crate packaging and publication alongside `gpui-pre`, and tracks newer GPUI versions to keep the integration compatible. Once the community `gpui-mobile` completes the integration and GPUI is published as a crate, we plan to switch this guide and its dependencies to the community `gpui-mobile`. -The current integration is experimental. The Swift-hosted iOS example has been built and exercised in the iOS simulator. The fork also has an Android activity example and a build script, but that is a different host path; GPUI Kit integration has not been validated there or on a physical iPhone. Treat the iOS simulator path below as the demonstrated target, not a general mobile support guarantee. +The current integration is experimental. The Swift-hosted iOS example has been built and exercised in the iOS simulator. Beyond this guide, GPUI Kit has been validated on iOS and Android in a limited scope: an AI chat area built from TextView, Button, Menu, Popover, Scrollbar, Input, Textarea and text selection passed functional and performance testing on both platforms, and the fixes from that work are in GPUI Kit. TextView is covered completely in that scenario. Other components and complete application layouts have not been validated on mobile yet. The fork's Android activity example is a different host path that this guide does not cover. Treat the iOS simulator path below as the documented target, not a general mobile support guarantee. + +Native UI and GPUI can share one screen on both iOS and Android. The native side keeps the parts users expect to behave like the platform, such as the navigation bar and the bottom input field, and GPUI renders as one view between them. Each side keeps its own layout and input; the host places the GPUI view like any other native view. ## Run the iOS example @@ -49,7 +56,7 @@ gpui = { package = "gpui-pre", version = "=0.3.4", default-features = false } gpui-kit = { git = "https://github.com/longbridge/gpui-kit", rev = "7d9efcd2069f9eaa6eb3ba6345aac4aa7d87c9f7", default-features = false, features = ["component"] } ``` -These revisions reproduce the example's dependency baseline. The Kit revision includes mobile platform gating but predates mobile tooltip suppression. The current GPUI Kit checkout uses `gpui-pre 0.3.6`, while this pinned mobile platform and renderer use `0.3.4`. Cargo can select both versions, producing incompatible GPUI types; replacing the Kit dependency with a local path is **not** a working upgrade by itself. First update the mobile platform and renderer to the same GPUI version as Kit and validate that combination. Only then can you use a path dependency such as: +These revisions reproduce the example's dependency baseline. The Kit revision includes mobile platform gating but predates mobile tooltip suppression. The current GPUI Kit checkout uses `gpui-pre {{gpui_pre_version}}`, while this pinned mobile platform and renderer use `0.3.4`. Cargo can select both versions, producing incompatible GPUI types; replacing the Kit dependency with a local path is **not** a working upgrade by itself. First update the mobile platform and renderer to the same GPUI version as Kit and validate that combination. Only then can you use a path dependency such as: ```toml gpui-kit = { path = "../gpui-kit/crates/kit", default-features = false, features = ["component"] } @@ -57,7 +64,7 @@ gpui-kit = { path = "../gpui-kit/crates/kit", default-features = false, features Adjust the path relative to your application's manifest. Keep the GPUI core, renderer, platform, and Kit on one compatible release. -Unlike the desktop [Getting Started](/docs/getting-started) setup, mobile does not use `gpui_kit::application()` or `gpui_kit::platform`. Those desktop platform exports are excluded on iOS and Android. The mobile host initializes GPUI, calls `gpui_kit::init(cx)`, and mounts a single `component::Root` around the application's content. +Unlike the desktop [Getting Started](./getting-started.md) setup, mobile does not use `gpui_kit::application()` or `gpui_kit::platform`. Those desktop platform exports are excluded on iOS and Android. The mobile host initializes GPUI, calls `gpui_kit::init(cx)`, and mounts a single `component::Root` around the application's content. ## Embed a view in UIKit @@ -146,7 +153,7 @@ For an application integration, check launch and return from the background, key Measure rendering on a physical device with a release build and Xcode Instruments before making performance claims. Simulator results are useful for layout and interaction, but are not device frame-time measurements. -Android uses a separate activity and surface lifecycle. The repository contains an Android example, but this guide does not establish Android Kit compatibility or native Android `View` embedding. Validate those paths separately before depending on them. +Android uses a separate activity and surface lifecycle. The repository contains an Android example. GPUI can be embedded as an Android `View` in a native layout, as described above, but this guide documents only the iOS steps. Outside the chat scenario validated above, Android Kit compatibility is not established. Validate the Android host path separately before depending on it. ## Troubleshooting @@ -154,6 +161,6 @@ Android uses a separate activity and surface lifecycle. The repository contains | --- | --- | | Xcode cannot find the simulator destination, or the app launches on another simulator | The pinned `build.sh` builds for iOS 18.6 on an iPhone 16 Pro. Install that runtime or edit its Xcode destination to match `xcodebuild -showdestinations`. Then run `xcrun simctl list devices available`: the script's `_ios_run_simulator` installs on the first available iPhone, independently of the build destination. If that is a different device, change its `sim_id` selection to the intended UUID. | | Device build fails signing or install | Replace the example development team in `example/ios/project.yml`, regenerate the Xcode project, and confirm the device appears in Xcode. The default script target is a physical device; pass `--simulator` explicitly for the documented simulator path. | -| Rust reports two versions of GPUI or mismatched `App`/`Window` types | Check the resolved `gpui-pre` packages. The pinned mobile fork uses `0.3.4`; this checkout uses `0.3.6`. Align the entire mobile platform and Kit dependency set before using the local Kit path. | +| Rust reports two versions of GPUI or mismatched `App`/`Window` types | Check the resolved `gpui-pre` packages. The pinned mobile fork uses `0.3.4`; this checkout uses `{{gpui_pre_version}}`. Align the entire mobile platform and Kit dependency set before using the local Kit path. | | App launches with a blank or stale GPUI view | Check that the Rust callback opens a window, the child controller is attached, `layoutSubviews` publishes nonzero bounds, and visible frames are requested. Inspect the Xcode console; the example sends Rust logs and panics to `NSLog`. | | Text, icons, or images are missing | Verify the font family and glyph coverage, registered `AssetSource` and exact icon keys, or the image's packaged path and HTTP client. A desktop asset or font configuration does not automatically carry into the mobile host. | diff --git a/website/docs/native-extension.md b/website/docs/native-extension.md index f5e5d19c83..9e7041afdb 100644 --- a/website/docs/native-extension.md +++ b/website/docs/native-extension.md @@ -2,13 +2,14 @@ title: Native Extensions description: Integrate native menus and child views with GPUI Kit, including handles, layout, input, lifetime, and platform limits. order: -9.1 +maturity: [platform-dependent] --- # Native Extensions A native extension attaches an OS control or view to a GPUI window. The two concrete integrations in this repository show different paths: [NativeMenu](https://github.com/longbridge/gpui-kit/tree/main/crates/component/src/native_menu) uses the operating system's menu API, while [gpui-wry](https://github.com/longbridge/gpui-kit/tree/main/crates/webview) embeds a native WebView. Both cross the GPUI/OS boundary; neither is implemented by launching another application. -Use `NativeMenu` when a popup must escape a small window's bounds or follow the system menu appearance. Use the GPUI `PopupMenu` when the menu needs to participate in GPUI's own overlay composition. Use `gpui-wry` only when the screen needs an actual browser engine and can reserve a rectangle for a native child view; [TextView HTML](/component/text-view#html) handles document display, and [`cx.open_url`](./context#open-a-url-in-the-default-browser) opens the user's browser. `gpui-wry` is experimental. +Use `NativeMenu` when a popup must escape a small window's bounds or follow the system menu appearance. Use the GPUI `PopupMenu` when the menu needs to participate in GPUI's own overlay composition. Use `gpui-wry` only when the screen needs an actual browser engine and can reserve a rectangle for a native child view; [TextView HTML](../component/text-view.md#html) handles document display, and [`cx.open_url`](./context#open-a-url-in-the-default-browser) opens the user's browser. `gpui-wry` is experimental. | Platform | NativeMenu | Embedded WebView in this repository | | --- | --- | --- | @@ -20,7 +21,7 @@ The Linux menu fallback preserves the `NativeMenu` API, but it is GPUI content r ## Source and build map -The workspace pins `gpui-pre` to `=0.3.6` in the root `Cargo.toml`. Check the source in this checkout before copying an adapter to a different GPUI version. The relevant paths are: +The workspace pins `gpui-pre` to `={{gpui_pre_version}}` in the root `Cargo.toml`. Check the source in this checkout before copying an adapter to a different GPUI version. The relevant paths are: | Concern | Source in this repository | Build or exercise from the repository root | | --- | --- | --- | diff --git a/website/docs/system-notification.md b/website/docs/system-notification.md index 1d0740a507..8615939225 100644 --- a/website/docs/system-notification.md +++ b/website/docs/system-notification.md @@ -2,6 +2,7 @@ title: SystemNotification description: Send OS notifications with GPUI, handle activation, and understand GPUI Kit's Notification integration and platform limits. order: -9.05 +maturity: [platform-dependent] --- # SystemNotification @@ -61,7 +62,7 @@ Treat permission as part of the product flow: keep the task result in applicatio ## Post with the raw GPUI API -The project pins `gpui-pre 0.3.6`. Its [`App::show_system_notification`](https://docs.rs/gpui-pre/0.3.6/gpui/struct.App.html#method.show_system_notification) accepts a `SystemNotification` with `tag`, `title`, `body`, and `actions`: +The project pins `gpui-pre {{gpui_pre_version}}`. Its [`App::show_system_notification`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.App.html#method.show_system_notification) accepts a `SystemNotification` with `tag`, `title`, `body`, and `actions`: ```rust use gpui_kit::SystemNotification; diff --git a/website/docs/task.md b/website/docs/task.md index 246bdf61d4..9c40fc23e9 100644 --- a/website/docs/task.md +++ b/website/docs/task.md @@ -6,7 +6,7 @@ order: -2.631 # Task -In GPUI, a [`Task`](https://docs.rs/gpui-pre/0.3.6/gpui/struct.Task.html) is the handle to work scheduled by a GPUI executor. Its most important property is **ownership**: dropping the handle cancels unfinished work. A task runs only while its handle is stored, awaited, or explicitly detached. This makes the task's lifetime part of the View's state design, not just a detail of Rust's `Future` trait. +In GPUI, a [`Task`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Task.html) is the handle to work scheduled by a GPUI executor. Its most important property is **ownership**: dropping the handle cancels unfinished work. A task runs only while its handle is stored, awaited, or explicitly detached. This makes the task's lifetime part of the View's state design, not just a detail of Rust's `Future` trait. The **spawn API** chooses where work runs; the returned `Task` controls its lifetime. A foreground task can re-enter GPUI through an async [Context](./context) and update an [Entity]. A background task runs away from the UI thread and returns owned data; it cannot mutate Entity state there. @@ -355,4 +355,4 @@ self._update_task = cx.background_executor().spawn(async move { The excerpt shows the ownership and update points; see the linked source for setup and rendering. The example uses `std::thread::sleep` in its background producer to simulate pacing and an **unbounded channel** with `try_send` only for this demonstration. An unbounded channel does not provide backpressure and can grow when a producer outpaces the UI. Its producer can continue sending after replacement until the synchronous loop returns; the replay ID keeps those chunks out of the UI. For a real stream, use asynchronous waiting and a bounded channel with backpressure, handle send and update errors, and end the receiver when its View is gone. `WeakEntity` protects the View lifetime, and the channel crosses executors. See [Entity](./entity) for Entity ownership and updates. -[Entity]: /docs/entity +[Entity]: ./entity.md diff --git a/website/docs/text-system.md b/website/docs/text-system.md index 887810153c..28af80d0aa 100644 --- a/website/docs/text-system.md +++ b/website/docs/text-system.md @@ -6,7 +6,7 @@ order: -2.76 # TextSystem -This page follows the `gpui-pre` 0.3.6 API pinned by this repository. `gpui-pre` is the published snapshot and version-alignment mechanism for GPUI, not a separate text engine. Start with a text element; use `TextSystem` directly when your element needs the glyph geometry that GPUI normally manages. +This page follows the `gpui-pre` {{gpui_pre_version}} API pinned by this repository. `gpui-pre` is the published snapshot and version-alignment mechanism for GPUI, not a separate text engine. Start with a text element; use `TextSystem` directly when your element needs the glyph geometry that GPUI normally manages. GPUI's `TextSystem` resolves [fonts](./fonts) and supplies font metrics. Each [Window](./window) has a `WindowTextSystem` that adds a line-layout cache to the shared text system. Ordinary text elements and GPUI Kit controls use these services for you. Reach for `window.text_system()` when writing custom text geometry, a chart label, an editor, or another element that must use shaped glyph positions directly. diff --git a/website/docs/webassembly.md b/website/docs/webassembly.md index 927ce0cb97..f17e20873e 100644 --- a/website/docs/webassembly.md +++ b/website/docs/webassembly.md @@ -2,6 +2,7 @@ title: WebAssembly description: Build and run GPUI Kit applications in a browser with the repository's WebAssembly examples. order: -3.5 +maturity: [showcase-only] --- # WebAssembly @@ -12,7 +13,7 @@ GPUI and GPUI Kit WebAssembly support is used primarily to **showcase and try co GPUI Kit can render the same Rust views and components in a browser. The web target is `wasm32-unknown-unknown`: Rust produces a WebAssembly module, `wasm-bindgen` produces its JavaScript bindings, and a web page loads and starts the application. The browser supplies the canvas, input and network environment, so a desktop `main` function alone is not a web entry point. -In this workspace, [`gpui_web` is the Cargo alias for `gpui-pre-web` 0.3.6](https://github.com/longbridge/gpui-kit/blob/main/Cargo.toml). [`gpui-kit` includes it as a WASM-only dependency](https://github.com/longbridge/gpui-kit/blob/main/crates/kit/Cargo.toml) and re-exports it as `gpui_kit::web`; [the gallery crate](https://github.com/longbridge/gpui-kit/blob/main/crates/story-web/Cargo.toml) depends on `gpui-kit`, not directly on `gpui-pre-web`. Its `cdylib`, exported `run(...)`, web platform initialization and JavaScript loader provide the browser entry path that the desktop `main` cannot provide. +In this workspace, [`gpui_web` is the Cargo alias for `gpui-pre-web` {{gpui_pre_version}}](https://github.com/longbridge/gpui-kit/blob/main/Cargo.toml). [`gpui-kit` includes it as a WASM-only dependency](https://github.com/longbridge/gpui-kit/blob/main/crates/kit/Cargo.toml) and re-exports it as `gpui_kit::web`; [the gallery crate](https://github.com/longbridge/gpui-kit/blob/main/crates/story-web/Cargo.toml) depends on `gpui-kit`, not directly on `gpui-pre-web`. Its `cdylib`, exported `run(...)`, web platform initialization and JavaScript loader provide the browser entry path that the desktop `main` cannot provide. The [component gallery](https://gpui-kit.com/gallery/) is the quickest working example. Its [Rust entry point](https://github.com/longbridge/gpui-kit/blob/main/crates/story-web/src/lib.rs), [build script](https://github.com/longbridge/gpui-kit/blob/main/crates/story-web/scripts/build-wasm.sh) and [JavaScript loader](https://github.com/longbridge/gpui-kit/blob/main/crates/story-web/www/src/main.js) show the complete path from a GPUI Kit view to a browser page. @@ -66,7 +67,7 @@ The [subset script](https://github.com/longbridge/gpui-kit/blob/main/crates/stor For eligible missing emoji and horizontal Han, kana and modern Hangul graphemes, `CanvasFontFallback::EmojiAndCjk` can let the browser measure and draw from its local fonts. GPUI's loaded fonts remain preferred. This fallback works on individual graphemes, so browser font coverage, spacing and typography can differ; it is not a full CJK font replacement. The default policy covers emoji only, while `Disabled` uses loaded fonts alone. The policy is chosen when constructing `WebPlatform` and cannot be changed later. -GPUI also supports **loading a font after startup**. In the GPUI version pinned by this repository (`gpui-pre` 0.3.6), `TextSystem::add_fonts` accepts downloaded font bytes through `Cow::Owned`; it clears font resolution and line-layout caches. After an asynchronous fetch has produced a valid raw font file, install it on the application context and redraw: +GPUI also supports **loading a font after startup**. In the GPUI version pinned by this repository (`gpui-pre` {{gpui_pre_version}}), `TextSystem::add_fonts` accepts downloaded font bytes through `Cow::Owned`; it clears font resolution and line-layout caches. After an asynchronous fetch has produced a valid raw font file, install it on the application context and redraw: ```rust use std::borrow::Cow; diff --git a/website/docs/webview.md b/website/docs/webview.md index f9f380f93c..990ef2bad7 100644 --- a/website/docs/webview.md +++ b/website/docs/webview.md @@ -2,11 +2,12 @@ title: WebView description: Embed a native Wry WebView in a GPUI Kit window, with the current platform and overlay limitations. order: -9.125 +maturity: [experimental, platform-dependent] --- # WebView -[`gpui-wry`](https://github.com/longbridge/gpui-kit/tree/main/crates/webview) is GPUI Kit's **experimental** integration with [Wry](https://github.com/tauri-apps/wry). Use it when a screen needs browser behavior; [TextView HTML](/component/text-view#html) renders document content but is not a browser. To open a URL in the user's default external browser, use [`cx.open_url`](./context#open-a-url-in-the-default-browser). The integration currently supports macOS and Windows. The Linux path in the repository's example is unfinished. +[`gpui-wry`](https://github.com/longbridge/gpui-kit/tree/main/crates/webview) is GPUI Kit's **experimental** integration with [Wry](https://github.com/tauri-apps/wry). Use it when a screen needs browser behavior; [TextView HTML](../component/text-view.md#html) renders document content but is not a browser. To open a URL in the user's default external browser, use [`cx.open_url`](./context#open-a-url-in-the-default-browser). The integration currently supports macOS and Windows. The Linux path in the repository's example is unfinished. ## Run the example diff --git a/website/docs/window.md b/website/docs/window.md index bbceb0b1b1..af41e16dc0 100644 --- a/website/docs/window.md +++ b/website/docs/window.md @@ -6,7 +6,7 @@ order: -2.3 # Window -GPUI provides [`Window`](https://docs.rs/gpui-pre/0.3.6/gpui/struct.Window.html) as the context for one platform window. It connects the rendered Element tree to [platform input](./event#pointer-and-keyboard-input-are-also-events), Focus, Action dispatch, drawing, and window controls. A View receives it only while GPUI is updating or rendering that window: +GPUI provides [`Window`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.Window.html) as the context for one platform window. It connects the rendered Element tree to [platform input](./event#pointer-and-keyboard-input-are-also-events), Focus, Action dispatch, drawing, and window controls. A View receives it only while GPUI is updating or rendering that window: ```rust impl Render for Chat { @@ -45,7 +45,7 @@ application().run(|cx| { }); ``` -[`WindowOptions`](https://docs.rs/gpui-pre/0.3.6/gpui/struct.WindowOptions.html) controls initial bounds, focus, visibility, window kind, minimum size, and other platform-facing choices. The builder receives the `Window` only for construction. A window handle lets later code request an update, but handle-based updates can fail after the window closes. In a multi-window app, use the handle for the particular window whose focus or geometry you mean; an Entity handle alone does not select a window. +[`WindowOptions`](https://docs.rs/gpui-pre/{{gpui_pre_version}}/gpui/struct.WindowOptions.html) controls initial bounds, focus, visibility, window kind, minimum size, and other platform-facing choices. The builder receives the `Window` only for construction. A window handle lets later code request an update, but handle-based updates can fail after the window closes. In a multi-window app, use the handle for the particular window whose focus or geometry you mean; an Entity handle alone does not select a window. `open_window` returns an `AnyWindowHandle` because the actual GPUI root is `gpui_kit::base::Root`, not `Workspace`. From a later callback with `&mut App`, use the handle to enter that window, and check the result before assuming it is still open: @@ -214,7 +214,7 @@ Use `window.on_next_frame(...)` only when the operation specifically belongs to An Entity mutation followed by `cx.notify()` marks that Entity for rendering. `window.refresh()` marks the **whole window** dirty for its next draw; use it for window-local changes that lack an Entity notification, such as a platform or overlay state change. Neither belongs in an unconditional render path. -`window.on_next_frame(callback)` runs the callback at the next platform frame tick, before that tick's optional draw. It creates frame demand but does not itself mark the window dirty. `window.request_animation_frame()` captures the currently rendering View and notifies it on the next tick. In the pinned `gpui-pre` 0.3.6 implementation, it calls `current_view()` immediately, so use it only while GPUI has a current View; outside that render path, use `on_next_frame` and explicitly notify an Entity or call `window.refresh()`. Call it only while the motion still needs another sample. GPUI's `AnimationExt::with_animation` and [Base Motion](./animation) already manage frame requests and reduced motion for their animations. +`window.on_next_frame(callback)` runs the callback at the next platform frame tick, before that tick's optional draw. It creates frame demand but does not itself mark the window dirty. `window.request_animation_frame()` captures the currently rendering View and notifies it on the next tick. In the pinned `gpui-pre` {{gpui_pre_version}} implementation, it calls `current_view()` immediately, so use it only while GPUI has a current View; outside that render path, use `on_next_frame` and explicitly notify an Entity or call `window.refresh()`. Call it only while the motion still needs another sample. GPUI's `AnimationExt::with_animation` and [Base Motion](./animation) already manage frame requests and reduced motion for their animations. For a frame callback that changes external window state, call `window.refresh()` in that callback so the change reaches a draw. For Entity state, update the Entity and call `cx.notify()` in its update callback. A frame tick alone does not redraw an unchanged window. diff --git a/website/package.json b/website/package.json index 4bb6b008b6..261bb909e9 100644 --- a/website/package.json +++ b/website/package.json @@ -7,7 +7,9 @@ "build": "bunx --bun astro build && bunx pagefind --site ${SITE_OUT_DIR:-dist}", "test:showcases": "bun test tests/showcases.test.ts tests/showcase-browser.test.ts", "test:seo": "bun test tests/seo.test.ts", - "test:versioned-examples": "PUBLIC_SITE_BASE=/versions/test/ SITE_OUT_DIR=dist/versions/test bunx --bun astro build && SITE_TEST_DIST=../dist/versions/test/ bun test tests/seo.test.ts --test-name-pattern 'versioned documentation examples'", + "test:docs": "bun test tests/doc-sources.test.ts", + "test:links": "bun test tests/links.test.ts", + "test:versioned-examples": "PUBLIC_SITE_BASE=/versions/test/ SITE_OUT_DIR=dist/versions/test bunx --bun astro build && SITE_TEST_DIST=../dist/versions/test/ bun test tests/seo.test.ts --test-name-pattern 'versioned documentation examples' && SITE_TEST_DIST=../dist/versions/test/ SITE_TEST_BASE=/versions/test/ bun test tests/links.test.ts", "preview": "bunx --bun astro preview", "astro": "bunx --bun astro" }, @@ -21,10 +23,15 @@ "astro-pagefind": "^1.0.0", "lucide-vue-next": "^0.546.0", "rehype-mathjax": "^7.1.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", "tailwindcss": "^4.1.15", - "vue": "^3.5.0", - "twitter-text": "^3.1.0" + "twitter-text": "^3.1.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", + "vue": "^3.5.0" }, "devDependencies": { "@types/bun": "^1.3.2", diff --git a/website/shell/api.md b/website/shell/api.md index fd363ea48e..217d0f03e4 100644 --- a/website/shell/api.md +++ b/website/shell/api.md @@ -2,6 +2,7 @@ title: API Reference description: Every name a script can import or reach — the four built-in modules, the cx and window globals, and the element methods that are not styles. order: 10 +maturity: [preview] --- # API Reference diff --git a/website/shell/capabilities.md b/website/shell/capabilities.md index 47f8b500c6..07d7d62649 100644 --- a/website/shell/capabilities.md +++ b/website/shell/capabilities.md @@ -2,6 +2,7 @@ title: Capabilities description: The default-deny model, the fs / storage / clipboard / process surface, where storage lives, and what the sandbox withholds. order: 8 +maturity: [preview] --- # Capabilities diff --git a/website/shell/dependencies.md b/website/shell/dependencies.md index f967c65fcb..bb07a83597 100644 --- a/website/shell/dependencies.md +++ b/website/shell/dependencies.md @@ -2,6 +2,7 @@ title: Dependencies description: Shell packages — what makes a Git repository one, and how a manifest names, selects, fetches and imports it, down to what an editor sees. order: 9 +maturity: [preview] --- # Dependencies diff --git a/website/shell/dock.md b/website/shell/dock.md index d42ede4d53..f383e9a2c0 100644 --- a/website/shell/dock.md +++ b/website/shell/dock.md @@ -2,6 +2,7 @@ title: Dock and Panels description: A dockable layout drawn entirely by script — panels that survive a restart, chrome you draw yourself, and commands instead of callbacks. order: 13 +maturity: [preview] --- # Dock and Panels diff --git a/website/shell/elements.md b/website/shell/elements.md index f05b274a16..c68d713464 100644 --- a/website/shell/elements.md +++ b/website/shell/elements.md @@ -2,6 +2,7 @@ title: Elements description: Constructors, composition with child / children / when, and why an element description can only be used once. order: 4 +maturity: [preview] --- # Elements diff --git a/website/shell/engine.md b/website/shell/engine.md index 6c9d64099b..944076e11b 100644 --- a/website/shell/engine.md +++ b/website/shell/engine.md @@ -2,6 +2,7 @@ title: The Engine Seam description: QuickJS behind one internal interface, why the seam exists, and the three measurements that tell script cost apart from frame cost. order: 15 +maturity: [preview] --- # The Engine Seam diff --git a/website/shell/examples.md b/website/shell/examples.md index 05be843ac1..ef1fedd3ea 100644 --- a/website/shell/examples.md +++ b/website/shell/examples.md @@ -3,6 +3,7 @@ title: Examples description: Complete standalone and embedded applications, including retained state, HostModule registrations, and native motion. order: 3 pageClass: shell-examples-page +maturity: [preview] --- # Examples diff --git a/website/shell/getting-started.md b/website/shell/getting-started.md index 0835ff5c4d..90745023cf 100644 --- a/website/shell/getting-started.md +++ b/website/shell/getting-started.md @@ -2,6 +2,7 @@ title: Getting Started description: Add the runtime to a Rust application, write the script it loads, and check that script without opening a window. order: 2 +maturity: [preview] --- # Getting Started diff --git a/website/shell/host-module.md b/website/shell/host-module.md index 0dfa8aa776..56804de7cd 100644 --- a/website/shell/host-module.md +++ b/website/shell/host-module.md @@ -2,6 +2,7 @@ title: HostModule description: How a host lends its own Rust to a script — registration, the import that reaches it, the plain-data boundary, and the rules a Host function runs under. order: 12 +maturity: [preview] --- # HostModule diff --git a/website/shell/hosting.md b/website/shell/hosting.md index 40925c2fd4..d21911b9b7 100644 --- a/website/shell/hosting.md +++ b/website/shell/hosting.md @@ -2,6 +2,7 @@ title: Hosting description: The Rust side in full — runtime lifetime, mounting script Views, refreshing them from host state, metrics, exit requests and hot-reload. order: 11 +maturity: [preview] --- # Hosting diff --git a/website/shell/index.md b/website/shell/index.md index 60fab01108..a600066fd5 100644 --- a/website/shell/index.md +++ b/website/shell/index.md @@ -2,6 +2,7 @@ title: GPUI Shell description: Makes a Rust GPUI application extensible in JavaScript, rendered by GPUI itself — no WebView, no DOM. Plugins first, standalone script applications second. order: 1 +maturity: [preview] --- # GPUI Shell @@ -14,7 +15,7 @@ order: 1 **It is not an Electron or a Tauri.** There is no WebView, no DOM, no HTML or CSS, no browser engine, and no Node.js. A script View describes an interface when invalidated; GPUI can reuse that description on later frames without rerunning its script `render`. Those frames use the same element model and renderer as a Rust application on `gpui-base`. This does not mean an idle window continuously draws, or that all frame work is free of JavaScript: virtual-list item and dock chrome callbacks are exceptions. Taking the whole runtime costs [+13.5 MiB of binary in the measured build](./engine.md#what-linking-it-costs). -Both goals rest on the same split. `gpui-shell` is built directly on [`gpui-base`](/base), with [QuickJS](https://github.com/quickjs-ng/quickjs) running on the host's own thread. The host builds the runtime and grants what a script may reach; the script draws real interface inside the same process. Rust keeps rendering, layout, text editing, virtualization, focus, overlays and every system capability; the script owns composition, presentation and business logic. +Both goals rest on the same split. `gpui-shell` is built directly on [`gpui-base`](../base/index.md), with [QuickJS](https://github.com/quickjs-ng/quickjs) running on the host's own thread. The host builds the runtime and grants what a script may reach; the script draws real interface inside the same process. Rust keeps rendering, layout, text editing, virtualization, focus, overlays and every system capability; the script owns composition, presentation and business logic. ```js import { View } from "gpui-kit"; diff --git a/website/shell/overlays.md b/website/shell/overlays.md index 519415e385..6efdd0a073 100644 --- a/website/shell/overlays.md +++ b/website/shell/overlays.md @@ -2,6 +2,7 @@ title: Overlays description: Dialogs, the sheet and toasts, their stacking and dismissal order, and why they may only be opened from an event. order: 7 +maturity: [preview] --- # Overlays diff --git a/website/shell/performance.md b/website/shell/performance.md index f9a0767f23..11410460d6 100644 --- a/website/shell/performance.md +++ b/website/shell/performance.md @@ -2,6 +2,7 @@ title: Performance description: What a script costs once frame rate stops being the variable — invalidation against description size, the View as the boundary that bounds both, and the two failures FPS cannot tell apart. order: 14 +maturity: [preview] --- # Performance diff --git a/website/shell/state.md b/website/shell/state.md index 6e0bf1e351..8f8506e9e6 100644 --- a/website/shell/state.md +++ b/website/shell/state.md @@ -2,6 +2,7 @@ title: State and Views description: Views, init and render, cx.notify(), retained input state, and asynchronous work. order: 6 +maturity: [preview] --- # State and Views diff --git a/website/shell/styling.md b/website/shell/styling.md index 038262776d..c138352229 100644 --- a/website/shell/styling.md +++ b/website/shell/styling.md @@ -2,6 +2,7 @@ title: Styling description: The fluent style surface, length and colour grammars, semantic theme tokens, and hover / active / focus styles. order: 5 +maturity: [preview] --- # Styling diff --git a/website/src/components/Nav.astro b/website/src/components/Nav.astro index 1995c5c88d..a7f26c29cf 100644 --- a/website/src/components/Nav.astro +++ b/website/src/components/Nav.astro @@ -71,8 +71,9 @@ const zhRoot = `${siteRoot}/zh-CN`; function swapLocale(path: string) { if (isZh) { - const rest = path.startsWith(zhRoot) ? path.slice(zhRoot.length) : path; - return rest || siteRoot || '/'; + if (!path.startsWith(zhRoot)) return path; + // Keep the version prefix: `/versions/main/zh-CN/docs` → `/versions/main/docs`. + return `${siteRoot}${path.slice(zhRoot.length)}` || '/'; } const rest = path.startsWith(siteRoot) ? path.slice(siteRoot.length) : path; return `${zhRoot}${rest === '/' ? '' : rest}`; @@ -137,6 +138,7 @@ const currentVersionLabel = siteVersions.find((version) => version.id === curren {siteVersions.map((version) => ( diff --git a/website/src/content.config.ts b/website/src/content.config.ts index 50cc6b903c..cb3426767a 100644 --- a/website/src/content.config.ts +++ b/website/src/content.config.ts @@ -7,6 +7,7 @@ const pageSchema = z.object({ order: z.number().optional(), example: z.union([z.string(), z.literal(false)]).optional(), exampleKind: z.enum(['base', 'component']).optional(), + maturity: z.array(z.enum(['stable', 'preview', 'experimental', 'showcase-only', 'platform-dependent'])).optional(), }); const docs = defineCollection({ diff --git a/website/src/lib/doc-sources.js b/website/src/lib/doc-sources.js new file mode 100644 index 0000000000..8df4a47a2a --- /dev/null +++ b/website/src/lib/doc-sources.js @@ -0,0 +1,59 @@ +import { existsSync, readdirSync, statSync } from 'node:fs'; +import { join, relative, sep } from 'node:path'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import remarkGfm from 'remark-gfm'; +import remarkFrontmatter from 'remark-frontmatter'; +import remarkMath from 'remark-math'; +import { visit } from 'unist-util-visit'; + +// Directories whose Markdown is published as versioned documentation. Every +// versioned build renders these below its own base (`/versions//...`). +export const CONTENT_DIRS = [ + 'docs', 'component', 'base', 'shell', + 'zh-CN/docs', 'zh-CN/component', 'zh-CN/base', 'zh-CN/shell', +]; + +// A link written from the site root to versioned content. It points at the +// default version from every other version, so the source must link to the +// file instead and let the build place it inside the current version. +export const ROOT_CONTENT_LINK = /^\/(?:zh-CN(?:\/|$|#)|(?:zh-CN\/)?(?:docs|component|base|shell)(?:[/#]|$)|$|#)/; + +/** Every published Markdown file, as paths relative to `root`. */ +export function contentFiles(root) { + const files = []; + const walk = (dir) => { + for (const name of readdirSync(dir)) { + const path = join(dir, name); + if (statSync(path).isDirectory()) walk(path); + else if (name.endsWith('.md')) files.push(relative(root, path).split(sep).join('/')); + } + }; + for (const dir of CONTENT_DIRS) { + const path = join(root, dir); + if (existsSync(path)) walk(path); + } + // `zh-CN/docs` is walked on its own; keep one entry per file. + return [...new Set(files)].sort(); +} + +const parser = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter).use(remarkMath); + +/** + * Link destinations in a Markdown source, with the source offsets of the node + * that holds each one. Code blocks and inline code are not links. + */ +export function markdownLinks(source) { + const links = []; + visit(parser.parse(source), (node) => { + if ((node.type === 'link' || node.type === 'definition') && node.url) { + links.push({ + url: node.url, + line: node.position.start.line, + start: node.position.start.offset, + end: node.position.end.offset, + }); + } + }); + return links; +} diff --git a/website/src/lib/doc-variables.js b/website/src/lib/doc-variables.js new file mode 100644 index 0000000000..b12cba6d32 --- /dev/null +++ b/website/src/lib/doc-variables.js @@ -0,0 +1,61 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { visit } from 'unist-util-visit'; + +// Values that change with a release but appear throughout the documentation. +// Pages write `{{gpui_pre_version}}` instead of a literal, so the prose, the +// docs.rs links and both locales always name the GPUI snapshot that the +// documented GPUI Kit revision builds against. +// +// The workspace `Cargo.toml` is the single source. A versioned build copies +// the website out of the checkout and sets `GPUI_PRE_VERSION` from the +// `Cargo.toml` of the revision it documents. + +const VARIABLE = /\{\{\s*([a-z_]+)\s*\}\}/g; + +function readGpuiPreVersion() { + if (process.env.GPUI_PRE_VERSION) return process.env.GPUI_PRE_VERSION; + const manifest = resolve(process.cwd(), '..', 'Cargo.toml'); + if (!existsSync(manifest)) return undefined; + const pin = readFileSync(manifest, 'utf8') + .match(/^gpui\s*=\s*\{[^}\n]*package\s*=\s*"gpui-pre"[^}\n]*version\s*=\s*"=?([^"]+)"/m); + return pin?.[1]; +} + +const resolvers = { + gpui_pre_version: readGpuiPreVersion, +}; + +const cache = new Map(); + +/** The value of one documentation variable; throws for an unknown name. */ +export function docVariable(name) { + if (!cache.has(name)) { + const resolver = resolvers[name]; + if (!resolver) throw new Error(`Unknown documentation variable {{${name}}}`); + const value = resolver(); + if (!value) throw new Error(`Cannot resolve documentation variable {{${name}}}`); + cache.set(name, value); + } + return cache.get(name); +} + +export const DOC_VARIABLE_NAMES = Object.keys(resolvers); + +/** Replace every `{{name}}` in a Markdown source or text fragment. */ +export function expandDocVariables(text) { + return text.includes('{{') ? text.replace(VARIABLE, (_, name) => docVariable(name)) : text; +} + +/** Expands variables in prose, code, raw HTML and link destinations. */ +export function remarkDocVariables() { + return (tree) => { + visit(tree, (node) => { + if (typeof node.value === 'string') node.value = expandDocVariables(node.value); + if (typeof node.url === 'string' && /\{\{|%7B%7B/i.test(node.url)) { + node.url = expandDocVariables(node.url.replace(/%7B/gi, '{').replace(/%7D/gi, '}')); + } + if (typeof node.title === 'string') node.title = expandDocVariables(node.title); + }); + }; +} diff --git a/website/src/lib/llms.ts b/website/src/lib/llms.ts index d98fcb5272..3ab178dba0 100644 --- a/website/src/lib/llms.ts +++ b/website/src/lib/llms.ts @@ -1,5 +1,6 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'; import { extname, join, relative, resolve } from 'node:path'; +import { expandDocVariables } from './doc-variables.js'; const SITE_TITLE = 'GPUI Kit'; const SITE_DESCRIPTION = @@ -166,7 +167,7 @@ function scanDir( .replace(/\.md$/, '') .replace(/index$/, ''); const url = `${BASE_URL}/${urlPrefix}/${relPath}`.replace(/\/+/g, '/').replace(/\/$/, ''); - const body = expandSnippets(bodyWithoutFrontmatter(content), dir); + const body = expandDocVariables(expandSnippets(bodyWithoutFrontmatter(content), dir)); try { results.push({ diff --git a/website/src/lib/markdown-endpoint.ts b/website/src/lib/markdown-endpoint.ts index 3ae7be763b..6d0bc5dab0 100644 --- a/website/src/lib/markdown-endpoint.ts +++ b/website/src/lib/markdown-endpoint.ts @@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { bodyWithoutFrontmatter, documentationLicenseNotice, expandSnippets } from './llms'; import { isLatestVersion } from './versions'; +import { expandDocVariables } from './doc-variables.js'; /** * Serves a page's markdown at its own `.md` address, the way the published site @@ -22,7 +23,7 @@ export function markdownResponse(options: { const front = [`url: ${options.route}.md`]; if (options.description) front.push(`description: ${options.description}`); - const body = expandSnippets(bodyWithoutFrontmatter(source), dirname(absolute)); + const body = expandDocVariables(expandSnippets(bodyWithoutFrontmatter(source), dirname(absolute))); const lang = options.route.startsWith('/zh-CN/') ? 'zh-CN' : 'en'; const notice = documentationLicenseNotice(lang, `https://gpui-kit.com${options.route}`); const text = `---\n${front.join('\n')}\n---\n\n${body.trimEnd()}\n\n> ${notice}\n`; diff --git a/website/src/lib/remark-doc-links.js b/website/src/lib/remark-doc-links.js index ecb5d393cf..3159467e8c 100644 --- a/website/src/lib/remark-doc-links.js +++ b/website/src/lib/remark-doc-links.js @@ -1,4 +1,5 @@ import { dirname, relative, resolve, sep } from 'node:path'; +import { ROOT_CONTENT_LINK } from './doc-sources.js'; // VitePress rewrote in-repo links like `./assets.md` or `../base/index.md` to // their published route. Astro leaves them as written, so every one of the 400+ @@ -29,7 +30,15 @@ export function remarkDocLinks({ base = '/' } = {}) { const rewrite = (node) => { const url = node.url; - if (!url || isExternal(url) || url.startsWith('#') || url.startsWith('/')) return; + if (!url || isExternal(url) || url.startsWith('#')) return; + // Sources are checked for site-root links to documentation, but released + // snapshots predate that check. Keep their links inside the version being + // built instead of sending the reader to the default version. + if (ROOT_CONTENT_LINK.test(url)) { + node.url = `${prefix}${url}` || '/'; + return; + } + if (url.startsWith('/')) return; const hashAt = url.indexOf('#'); const target = hashAt === -1 ? url : url.slice(0, hashAt); diff --git a/website/src/lib/remark-maturity.js b/website/src/lib/remark-maturity.js new file mode 100644 index 0000000000..70b2dbe425 --- /dev/null +++ b/website/src/lib/remark-maturity.js @@ -0,0 +1,63 @@ +// Renders a page's `maturity` frontmatter as labels under its title. GPUI Kit +// is used in production, but its capabilities do not share one track record; +// a label on the page states that where the reader decides whether to use it. +// Each label links to the definitions on the documentation home. + +export const MATURITY_LEVELS = ['stable', 'preview', 'experimental', 'showcase-only', 'platform-dependent']; + +const labels = { + en: { + stable: ['Stable', 'Used by production desktop applications.'], + preview: ['Preview', 'Usable and documented; the API and edge-case behavior may still change.'], + experimental: ['Experimental', 'Works with known gaps; validate it for your product before depending on it.'], + 'showcase-only': ['Showcase only', 'Currently used to demonstrate components, not to ship applications.'], + 'platform-dependent': ['Platform-dependent', 'Availability or behavior differs by platform; this page lists the differences.'], + }, + 'zh-CN': { + stable: ['稳定', '已用于生产环境的桌面应用。'], + preview: ['预览', '可以使用且有文档,但 API 与边界行为仍可能调整。'], + experimental: ['实验性', '可以运行但存在已知缺口,依赖前请针对你的产品验证。'], + 'showcase-only': ['仅用于演示', '目前用于 showcase 演示组件,不用于交付应用。'], + 'platform-dependent': ['依赖平台', '可用性或行为因平台而异,本页列出差异。'], + }, +}; + +const definitions = { + en: '/docs#maturity', + 'zh-CN': '/zh-CN/docs#成熟度', +}; + +export function remarkMaturity() { + return (tree, file) => { + const levels = file.data?.astro?.frontmatter?.maturity; + if (!levels) return; + + const path = file.path ?? file.history?.[0] ?? ''; + const lang = /(?:^|[/\\])zh-CN[/\\]/.test(path) ? 'zh-CN' : 'en'; + const list = Array.isArray(levels) ? levels : [levels]; + for (const level of list) { + if (!MATURITY_LEVELS.includes(level)) { + throw new Error(`${path}: unknown maturity "${level}"; use one of ${MATURITY_LEVELS.join(', ')}`); + } + } + + const heading = tree.children.findIndex((node) => node.type === 'heading' && node.depth === 1); + const children = list.map((level) => { + const [text, title] = labels[lang][level]; + return { + type: 'link', + // A site-root path: remarkDocLinks, which runs after this plugin, + // moves it inside the version being built. + url: definitions[lang], + title, + data: { hProperties: { className: ['doc-maturity__label'], dataMaturity: level } }, + children: [{ type: 'text', value: text }], + }; + }); + tree.children.splice(heading + 1, 0, { + type: 'paragraph', + data: { hName: 'p', hProperties: { className: ['doc-maturity'] } }, + children, + }); + }; +} diff --git a/website/src/pages/404.astro b/website/src/pages/404.astro index fc6ae9ed02..f0e3957675 100644 --- a/website/src/pages/404.astro +++ b/website/src/pages/404.astro @@ -2,20 +2,74 @@ import BaseLayout from '../layouts/BaseLayout.astro'; import Nav from '../components/Nav.astro'; import { fetchStarCount } from '../lib/github'; +import { latestVersion, siteVersions } from '../lib/versions'; let starCount = 0; try { starCount = await fetchStarCount(); } catch {} const base = `${import.meta.env.BASE_URL.replace(/\/+$/, '')}/`; +// GitHub Pages serves the root build's 404 for every missing path, including +// ones below /versions//. The script below uses these to find the same +// page in another documentation version. +const versions = siteVersions.map((version) => ({ + id: version.id, + label: version.label, + latest: version.id === latestVersion, + prefix: version.id === latestVersion ? '' : `/versions/${encodeURIComponent(version.id)}`, +})); ---