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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .agents/docs/website.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The landing page speaks for TaffyJS as a family and presents the intended comple

The home page is only a landing page and uses VitePress's default theme. It contains a concise performance-led introduction, short summaries of the Rust/Taffy foundation, native Node.js performance and portable WebAssembly support, and direct routes to real package documentation and source. Tutorials, complete examples, runtime details and API reference belong to Guide or package documentation rather than the home page. The home page does not use invented testimonials, unsupported benchmark numbers or generic feature text to fill space.

The landing page includes one code-to-layout example below the default Home content. It shows ordinary direct TaffyJS JavaScript rather than JSX or a second wrapper API, and the adjacent rectangles come from that exact source through `@taffyjs/wasm` and `getLayout`. Variable names label the corresponding boxes. One `availableWidth` control demonstrates recomputation; the home page does not grow into an arbitrary code editor or a multi-control playground.
The landing page includes one code-to-layout example below the default Home content. It shows ordinary direct TaffyJS JavaScript rather than JSX or a second wrapper API, and the adjacent rectangles come from that exact source through `@taffyjs/wasm` and `getLayout`. Variable names label the corresponding boxes. Controls expose only inputs whose effects are immediately visible in those rectangles: available width, sidebar width, header height, and the horizontal and vertical gaps. The home page does not grow into an arbitrary code editor or a general-purpose playground.

## Package family

Expand All @@ -24,15 +24,15 @@ No planned package receives empty reference pages or documentation navigation th

## Documentation structure

The documentation is arranged in three layers: Guide, Essentials, and package sections. Guide contains the Introduction and Getting Started. Essentials teaches the shared layout-engine model in learning order. Package sections hold complete runtime and platform support, initialization, binding-specific behavior, compatibility, migration, and exact public API details. The top navigation links to Guide and a package menu containing the implemented Node and Wasm packages. Empty package sections are not reserved in advance.
The documentation is arranged in three layers: Guide, Essentials, and package sections. Guide contains the Introduction and Getting Started. Essentials teaches the shared layout-engine model in learning order. Package sections hold complete runtime and platform support, initialization, binding-specific behavior, compatibility, migration, and exact public API details. One documentation sidebar lists Guide, Essentials, and every implemented package with its articles, so readers can see the complete documentation structure from any documentation page. The top navigation links to that documentation once as Guide rather than repeating packages in a separate menu. Empty package sections are not reserved in advance.

Getting Started and Essentials use `@taffyjs/node` code by default. At points where runtime choice matters, a short callout sends browser or WebAssembly users to `@taffyjs/wasm` and users seeking Yoga compatibility to `@taffyjs/yoga`. Shared teaching is not repeated once per package.

The Introduction first explains what a layout engine does, then why TaffyJS exists, then the package family's feature scope, and finally credits the upstream work it builds on. Its feature-scope section introduces each package by purpose and distinguishing characteristic without adding installation instructions or empty documentation for unfinished packages.

Getting Started owns the shortest installation command, the minimum Node.js version needed to run it, and one smallest complete `@taffyjs/node` example. It begins with the equivalent familiar HTML and CSS, keeps one Flexbox relationship stable while translating it into TaffyJS, and explicitly names where the browser analogy stops. Its job is to establish the basic layout-engine model: a tree of nodes, styles, available space, an explicit computation, and stored layout results. Changing only the available width must produce an observable second result so the reader can distinguish a node's relative style from the outside constraint. It does not branch into layout-mode tutorials, caching, error cases, or API reference details.

Getting Started keeps the runnable Node example as the canonical code path and uses `@taffyjs/wasm` for one in-page control that runs the same tree in the browser. The control varies available space while keeping the tree and styles fixed, revealing the same relationship without replacing or forking the Node example.
Getting Started keeps the runnable Node example as the canonical code path and uses `@taffyjs/wasm` for one in-page example that runs the same tree in the browser. The example shows its fixed, read-only source on the left and its available-space control and computed result on the right; the displayed source is the code being executed. The control varies available space while keeping the tree and styles fixed, revealing the same relationship without replacing or forking the Node example.

Essentials continues directly from that model. It explains what each shared concept is, the role it plays, how it affects a computation, and how to use it through focused `@taffyjs/node` examples. The sequence moves from the tree-compute-read lifecycle and styles and values into Block, Flexbox, Grid, and measuring text and images. The measurement page first establishes that Taffy only lays out boxes and relies on application-owned text and image systems for intrinsic sizes, then teaches the callback boundary. These pages teach the model rather than enumerate every method or field.

Expand All @@ -46,7 +46,7 @@ The target page groups are:
- `@taffyjs/wasm`: package overview and setup, supported environments, initialization and deployment, observable differences from Node, and a link to the shared direct API reference.
- `@taffyjs/yoga`: package overview and installation, compatibility scope, migration from Yoga, Yoga-facing API reference, and documented differences from the direct Taffy API.

The global site navigation and the documentation sidebar serve different purposes. The landing page and later real Blog or Showcase sections are site-level content; Guide and package sections are documentation. Do not create empty Blog, Showcase, version switcher or package pages merely to make the site appear complete.
The global site navigation and the documentation sidebar serve different purposes. The landing page and later real Blog or Showcase sections are site-level content; Guide, Essentials, and package sections all belong to the same documentation sidebar. Do not create empty Blog, Showcase, version switcher or package pages merely to make the site appear complete.

## Content ownership

Expand Down
130 changes: 111 additions & 19 deletions apps/website/.vitepress/components/HomeLayoutShowcase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import { computed, onMounted, ref, shallowRef } from "vue";

const availableWidth = ref(380);
const sidebarWidth = ref(88);
const headerHeight = ref(40);
const horizontalGap = ref(12);
const verticalGap = ref(12);
const layout = shallowRef(null);
const loadError = ref("");

Expand Down Expand Up @@ -46,7 +50,13 @@ const visual = computed(() => {

function updateLayout() {
if (!computeHomeLayout) return;
layout.value = computeHomeLayout(availableWidth.value);
layout.value = computeHomeLayout({
availableWidth: availableWidth.value,
sidebarWidth: sidebarWidth.value,
headerHeight: headerHeight.value,
horizontalGap: horizontalGap.value,
verticalGap: verticalGap.value,
});
}

onMounted(async () => {
Expand Down Expand Up @@ -76,20 +86,92 @@ onMounted(async () => {
</div>

<div class="home-layout-result">
<label for="home-layout-width">
<code>availableWidth</code>
<output>{{ availableWidth }}</output>
</label>
<input
id="home-layout-width"
v-model.number="availableWidth"
type="range"
min="300"
max="520"
step="20"
:disabled="!visual"
@input="updateLayout"
/>
<div class="home-layout-controls">
<div class="home-layout-control">
<label for="home-layout-width">
<code>availableWidth</code>
<output>{{ availableWidth }}</output>
</label>
<input
id="home-layout-width"
v-model.number="availableWidth"
type="range"
min="300"
max="520"
step="20"
:disabled="!visual"
@input="updateLayout"
/>
</div>

<div class="home-layout-control">
<label for="home-layout-sidebar-width">
<code>sidebarWidth</code>
<output>{{ sidebarWidth }}</output>
</label>
<input
id="home-layout-sidebar-width"
v-model.number="sidebarWidth"
type="range"
min="72"
max="136"
step="8"
:disabled="!visual"
@input="updateLayout"
/>
</div>

<div class="home-layout-control">
<label for="home-layout-header-height">
<code>headerHeight</code>
<output>{{ headerHeight }}</output>
</label>
<input
id="home-layout-header-height"
v-model.number="headerHeight"
type="range"
min="32"
max="72"
step="4"
:disabled="!visual"
@input="updateLayout"
/>
</div>

<div class="home-layout-control">
<label for="home-layout-horizontal-gap">
<code>horizontalGap</code>
<output>{{ horizontalGap }}</output>
</label>
<input
id="home-layout-horizontal-gap"
v-model.number="horizontalGap"
type="range"
min="0"
max="32"
step="4"
:disabled="!visual"
@input="updateLayout"
/>
</div>

<div class="home-layout-control">
<label for="home-layout-vertical-gap">
<code>verticalGap</code>
<output>{{ verticalGap }}</output>
</label>
<input
id="home-layout-vertical-gap"
v-model.number="verticalGap"
type="range"
min="0"
max="32"
step="4"
:disabled="!visual"
@input="updateLayout"
/>
</div>
</div>

<div v-if="visual" class="home-layout-canvas">
<div class="home-layout-root-label">
Expand Down Expand Up @@ -235,21 +317,31 @@ onMounted(async () => {
background: var(--vp-c-bg-soft);
}

.home-layout-result label {
.home-layout-controls {
display: grid;
gap: 14px;
}

.home-layout-control {
display: grid;
gap: 6px;
}

.home-layout-control label {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 16px;
}

.home-layout-result output {
.home-layout-control output {
color: var(--vp-c-text-2);
font-variant-numeric: tabular-nums;
}

.home-layout-result input {
.home-layout-control input {
width: 100%;
margin: 12px 0 24px;
margin: 0;
}

.home-layout-canvas {
Expand Down
139 changes: 79 additions & 60 deletions apps/website/.vitepress/components/LayoutWidthDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,20 @@ const secondX = ref(null);
const loadError = ref("");
const ready = ref(false);

let tree;
let first;
let second;
let root;
let computeWidthLayout;

function compute() {
if (!tree) return;
if (!computeWidthLayout) return;

tree.computeLayout({
root,
availableSpace: { width: availableWidth.value, height: 20 },
});

firstWidth.value = tree.getLayout(first).size.width;
const secondLayout = tree.getLayout(second);
secondWidth.value = secondLayout.size.width;
secondX.value = secondLayout.location.x;
const { first, second } = computeWidthLayout(availableWidth.value);
firstWidth.value = first.size.width;
secondWidth.value = second.size.width;
secondX.value = second.location.x;
}

onMounted(async () => {
try {
const { Dimension, Display, TaffyTree } = await import("@taffyjs/wasm");

tree = new TaffyTree();
first = tree.newLeaf({ flexGrow: 1 });
second = tree.newLeaf({ flexGrow: 1 });
root = tree.newWithChildren(
{
display: Display.Flex,
size: { width: Dimension.Percent(100), height: 20 },
},
[first, second],
);

({ computeWidthLayout } = await import("../../examples/getting-started-width.js"));
compute();
ready.value = true;
} catch (error) {
Expand All @@ -52,48 +32,81 @@ onMounted(async () => {

<template>
<div class="layout-demo">
<label for="layout-demo-width">
Available width: <strong>{{ availableWidth }}</strong>
</label>
<input
id="layout-demo-width"
v-model.number="availableWidth"
type="range"
min="100"
max="300"
step="10"
:disabled="!ready"
@input="compute"
/>

<div v-if="ready" class="layout-demo-stage">
<div class="layout-demo-row" :style="{ width: `${availableWidth}px` }">
<div class="layout-demo-item" :style="{ width: `${firstWidth}px` }">{{ firstWidth }}</div>
<div
class="layout-demo-item second"
:style="{ left: `${secondX}px`, width: `${secondWidth}px` }"
>
{{ secondWidth }}
<div class="layout-demo-code" aria-label="Read-only TaffyJS example code">
<slot />
</div>
Comment on lines +35 to +37

<div class="layout-demo-result">
<label for="layout-demo-width">
Available width: <strong>{{ availableWidth }}</strong>
</label>
<input
id="layout-demo-width"
v-model.number="availableWidth"
type="range"
min="100"
max="300"
step="10"
:disabled="!ready"
@input="compute"
/>

<div v-if="ready" class="layout-demo-stage">
<div class="layout-demo-row" :style="{ width: `${availableWidth}px` }">
<div class="layout-demo-item" :style="{ width: `${firstWidth}px` }">
{{ firstWidth }}
</div>
<div
class="layout-demo-item second"
:style="{ left: `${secondX}px`, width: `${secondWidth}px` }"
>
{{ secondWidth }}
</div>
</div>
</div>
</div>

<p v-if="!ready && !loadError" class="layout-demo-status" aria-live="polite">
Loading <code>@taffyjs/wasm</code>…
</p>
<output v-else-if="ready" aria-live="polite">
First item width: {{ firstWidth }}; second item x: {{ secondX }}
</output>
<p v-else class="layout-demo-error" role="alert">
The WebAssembly example could not start: {{ loadError }}
</p>
<p v-if="!ready && !loadError" class="layout-demo-status" aria-live="polite">
Loading <code>@taffyjs/wasm</code>…
</p>
<output v-else-if="ready" aria-live="polite">
First item width: {{ firstWidth }}; second item x: {{ secondX }}
</output>
<p v-else class="layout-demo-error" role="alert">
The WebAssembly example could not start: {{ loadError }}
</p>
</div>
</div>
</template>

<style scoped>
.layout-demo {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin: 24px 0;
padding: 20px;
}

.layout-demo-code,
.layout-demo-result {
min-width: 0;
}

.layout-demo-code :deep(div[class*="language-"]) {
height: 100%;
margin: 0;
border: 1px solid var(--vp-c-divider);
}

.layout-demo-code :deep(pre) {
height: 100%;
box-sizing: border-box;
}

.layout-demo-result {
display: flex;
flex-direction: column;
justify-content: center;
padding: 16px;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
Expand Down Expand Up @@ -152,4 +165,10 @@ onMounted(async () => {
margin: 12px 0 0;
color: var(--vp-c-danger-1);
}

@media (max-width: 767px) {
.layout-demo {
grid-template-columns: minmax(0, 1fr);
}
}
</style>
Loading