This is an automated email from the ASF dual-hosted git repository.

github-actions[bot] pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/tvm-site.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new eeab9ce358e Build at Mon Jun 22 03:55:58 UTC 2026
eeab9ce358e is described below

commit eeab9ce358e7b43fe1815fb46ec9ec2d08c602af
Author: tvm-bot <[email protected]>
AuthorDate: Mon Jun 22 03:55:59 2026 +0000

    Build at Mon Jun 22 03:55:58 UTC 2026
---
 2026/06/22/tirx.html                   |  17 +-
 _static/downloads/acm%3Adesktopcta     |   6 +-
 assets/tirx-layout-demo/index.html     | 167 -------
 assets/tirx-layout-demo/layout-demo.js | 805 ---------------------------------
 assets/tirx-layout-demo/viz-base.css   | 113 -----
 assets/tirx-layout-demo/viz-base.js    |  72 ---
 atom.xml                               |  19 +-
 feed.xml                               |  19 +-
 rss.xml                                |  21 +-
 9 files changed, 19 insertions(+), 1220 deletions(-)

diff --git a/2026/06/22/tirx.html b/2026/06/22/tirx.html
index f4ba7797adf..dc39cf29872 100644
--- a/2026/06/22/tirx.html
+++ b/2026/06/22/tirx.html
@@ -262,20 +262,9 @@ L(i,j)_{(8,16)} &amp;= L(i\cdot 16 + j) &amp;&amp; 
\text{(flatten)} \\
   <li>owners (×2 via replica): { warpid=6 laneid=12 }, { warpid=10 laneid=12 
}</li>
 </ul>
 
-<p><em>(Click element 57 in the interactive demo below to see exactly these 
owners.)</em></p>
-
-<details>
-<summary>Unfold to see the interactive layout demo</summary>
-<iframe id="tirx-layout-demo" 
src="/assets/tirx-layout-demo/index.html?preset=tensor-core&amp;notitle&amp;lock"
 style="width:100%; height:560px; border:1px solid #dfe1e6; border-radius:10px; 
margin:12px 0;" title="TIRx interactive layout demo: tensor-core tile" 
loading="lazy"></iframe>
-<script>
-window.addEventListener('message', function (e) {
-  var h = e.data && e.data.tirxLayoutDemoHeight;
-  if (!h) return;
-  var f = document.getElementById('tirx-layout-demo');
-  if (f) f.style.height = h + 'px';
-});
-</script>
-</details>
+<p><em>(Open the interactive demo and click element 57 to see exactly these 
owners.)</em></p>
+
+<p><a 
href="https://mlc.ai/modern-gpu-programming-for-mlsys/_static/tirx-layout-demo/index.html?preset=tensor-core";
 target="_blank" rel="noopener" style="display:inline-block; padding:10px 18px; 
background:#3b82f6; color:#fff !important; font-weight:700; border-radius:8px; 
text-decoration:none;">▶ Open the interactive layout demo ↗</a></p>
 
 <p>TIRx’s layout interface is built around four design choices.</p>
 
diff --git a/_static/downloads/acm%3Adesktopcta 
b/_static/downloads/acm%3Adesktopcta
index 217fd5be47f..9e7fc197211 100644
--- a/_static/downloads/acm%3Adesktopcta
+++ b/_static/downloads/acm%3Adesktopcta
@@ -64,12 +64,12 @@
 
       <div class="cf-error-footer cf-wrapper w-240 lg:w-full py-10 sm:py-4 
sm:px-8 mx-auto text-center sm:text-left border-solid border-0 border-t 
border-gray-300">
     <p class="text-13">
-      <span class="cf-footer-item sm:block sm:mb-1">Cloudflare Ray ID: <strong 
class="font-semibold">a0f849d9edef6173</strong></span>
+      <span class="cf-footer-item sm:block sm:mb-1">Cloudflare Ray ID: <strong 
class="font-semibold">a0f8513d3c060568</strong></span>
       <span class="cf-footer-separator sm:hidden">&bull;</span>
       <span id="cf-footer-item-ip" class="cf-footer-item hidden sm:block 
sm:mb-1">
         Your IP:
         <button type="button" id="cf-footer-ip-reveal" 
class="cf-footer-ip-reveal-btn">Click to reveal</button>
-        <span class="hidden" id="cf-footer-ip">172.214.155.177</span>
+        <span class="hidden" id="cf-footer-ip">20.81.183.81</span>
         <span class="cf-footer-separator sm:hidden">&bull;</span>
       </span>
       <span class="cf-footer-item sm:block sm:mb-1"><span>Performance &amp; 
security by</span> <a rel="noopener noreferrer" 
href="https://www.cloudflare.com/5xx-error-landing"; id="brand_link" 
target="_blank">Cloudflare</a></span>
@@ -86,5 +86,5 @@
     
     
   </script>
-<script>(function(){function c(){var 
b=a.contentDocument||a.contentWindow.document;if(b){var 
d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a0f849d9edef6173',t:'MTc4MjEwMDIyMw=='};var
 
a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var
 a=document.createElement('iframe');a.height=1;a.width=1;a.style.pos [...]
+<script>(function(){function c(){var 
b=a.contentDocument||a.contentWindow.document;if(b){var 
d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a0f8513d3c060568',t:'MTc4MjEwMDUyNQ=='};var
 
a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var
 a=document.createElement('iframe');a.height=1;a.width=1;a.style.pos [...]
 </html>
\ No newline at end of file
diff --git a/assets/tirx-layout-demo/index.html 
b/assets/tirx-layout-demo/index.html
deleted file mode 100644
index 38d5c05ce20..00000000000
--- a/assets/tirx-layout-demo/index.html
+++ /dev/null
@@ -1,167 +0,0 @@
-<!DOCTYPE html>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one
-  or more contributor license agreements.  See the NOTICE file
-  distributed with this work for additional information
-  regarding copyright ownership.  The ASF licenses this file
-  to you under the Apache License, Version 2.0 (the
-  "License"); you may not use this file except in compliance
-  with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing,
-  software distributed under the License is distributed on an
-  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-  KIND, either express or implied.  See the License for the
-  specific language governing permissions and limitations
-  under the License.
-
-  Page structure and interaction pattern adapted from the team's own course
-  material (mlsyscourse/slides-modern-gpu-programming,
-  data-layout/site/demo/tile_distributed.html). TIRx layout logic in
-  layout-demo.js is original. Not derived from any third-party demo.
--->
-<html lang="en">
-<head>
-<meta charset="UTF-8">
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<title>TIRx Tensor Layout — Interactive Demo</title>
-<link rel="stylesheet" href="viz-base.css">
-<script src="viz-base.js"></script>
-<style>
-  /* embed mode (?notitle / ?lock) */
-  body.lock .preset-row, body.lock .main-controls, body.lock #status { 
display:none; }
-  body.notitle, body.lock { padding:10px; }
-  /* lock: lay panels at natural size; JS scales the whole block to fit the 
width */
-  body.lock #panels { display:flex; flex-direction:column; 
align-items:flex-start; gap:20px; grid-template-columns:none; 
width:max-content; max-width:none; margin:0; }
-
-  /* Demo-specific overrides */
-  .ti {
-    padding:5px 9px; border:1px solid var(--border); border-radius:5px;
-    font-family:'SF Mono','Fira Code',monospace; font-size:13px; 
color:var(--text);
-    background:var(--surface);
-  }
-  .ti.expr { min-width:440px; max-width:70vw; }
-  .ti.shape { width:84px; }
-  select.ti { font-family:inherit; cursor:pointer; }
-
-  /* preset = secondary helper (just loads an example into the two fields) */
-  .preset-row { display:flex; align-items:center; gap:8px; 
justify-content:center;
-    margin-bottom:8px; font-size:12px; }
-  .preset-row .lbl { font-size:12px; }
-  .preset-hint { color:var(--dim); font-size:12px; font-style:italic; }
-
-  /* shape + layout = the first-class inputs */
-  .main-controls { display:flex; gap:20px; justify-content:center; 
align-items:flex-end;
-    flex-wrap:wrap; padding:16px 20px; border:1px solid #c7d6f0; 
border-radius:10px;
-    background:#f6f9ff; max-width:960px; margin:0 auto 10px; }
-  .field { display:flex; flex-direction:column; gap:5px; }
-  .field.grow { flex:1; min-width:300px; }
-  .flbl { font-size:13px; font-weight:700; color:var(--text); }
-  .flbl-sub { font-weight:500; color:var(--dim); font-size:11px;
-    font-family:'SF Mono','Fira Code',monospace; }
-  .field .ti { font-size:14px; padding:8px 11px; }
-  .field .ti.expr { width:100%; min-width:0; max-width:none; }
-  .sw-group { display:flex; gap:6px; }
-  .sw-group .ti { font-size:13px; padding:8px 8px; }
-
-  #status { font-size:12px; font-family:'SF Mono','Fira Code',monospace; 
color:var(--dim);
-    text-align:center; margin-bottom:14px; }
-
-  /* Stack the two panels vertically (logical on top, physical below) */
-  .panels { grid-template-columns:1fr; max-width:1280px; align-items:start; }
-  .grid { gap:2px; }
-  .cell { aspect-ratio:1; font-size:21px; border-radius:5px; border-width:2px; 
}
-  .cell.dm { opacity:.18; }
-
-  /* Right panel: physical-coordinate view (2D table or 1D wrapped tiles) */
-  #phys { max-height:580px; overflow:auto; padding:2px; }
-  .phys-table { display:grid; gap:3px; }
-  .ax-hdr { font-size:18px; color:var(--dim); font-weight:600;
-    font-family:'SF Mono','Fira Code',monospace; display:flex; 
align-items:center; justify-content:center; }
-  .ax-hdr.row { justify-content:flex-end; padding-right:4px; 
white-space:nowrap; }
-  .pcell { border:1px solid var(--border); border-radius:4px; min-height:30px; 
padding:2px;
-    display:flex; flex-wrap:wrap; gap:2px; align-content:flex-start; 
justify-content:center; transition:all .15s; }
-  .pcell.hov-cell { border-color:#222; box-shadow:0 0 7px rgba(59,130,246,.4); 
}
-  .pcell.dm-cell { opacity:.16; }
-  /* bank mode: pack the elements of one bank word side by side (same size as
-     the logical-tensor cells), not stacked */
-  .phys-table.bank-mode .pcell { flex-wrap:nowrap; gap:2px; padding:2px; }
-
-  .phys-1d { display:flex; flex-wrap:wrap; gap:8px; }
-  .thread-tile { border:2px solid var(--border); border-radius:8px; 
padding:5px 6px 6px;
-    background:var(--bg); min-width:54px; transition:all .15s; }
-  .thread-tile.hov-tile { box-shadow:0 0 8px rgba(59,130,246,.35); 
border-color:#222; }
-  .thread-tile.dm-tile { opacity:.2; }
-  .thread-lbl { font-size:18px; font-weight:700; color:var(--dim);
-    font-family:'SF Mono','Fira Code',monospace; text-align:center; 
margin-bottom:4px; white-space:nowrap; }
-  .thread-slots { display:flex; flex-wrap:wrap; gap:2px; 
justify-content:center; }
-
-  .gcell {
-    border-radius:3px; display:flex; align-items:center; 
justify-content:center;
-    font-size:21px; font-weight:700; width:46px; height:46px; flex:0 0 auto; 
cursor:pointer;
-    border:2px solid transparent; transition:all .15s;
-  }
-  .gcell.hov { border-color:#222; box-shadow:0 0 6px rgba(59,130,246,.45); }
-  .gcell.dm  { opacity:.18; }
-
-  .formula-bar { max-width:1280px; }
-  /* Larger labels/headers/legend for embedded (scaled) readability */
-  .panel h2 { font-size:22px; }
-  .panel .nota { font-size:19px; }
-  .hdr { font-size:18px; }
-  .rl { font-size:18px; }
-  .li { font-size:14px; }
-</style>
-</head>
-<body>
-
-<h1>TIRx Tensor Layout</h1>
-<div class="sub">how a <code>TileLayout</code> (shard / replica / offset) maps 
logical tensor elements to physical threads</div>
-
-<div class="preset-row">
-  <span class="lbl">load an example</span>
-  <select id="preset" class="ti"></select>
-  <span class="preset-hint">↓ fills the two fields below</span>
-</div>
-
-<div class="main-controls">
-  <div class="field">
-    <label class="flbl">Logical shape</label>
-    <input id="shape" class="ti shape" value="4, 8" spellcheck="false">
-  </div>
-  <div class="field">
-    <label class="flbl">Swizzle (SMEM)</label>
-    <div class="sw-group">
-      <select id="dtype" class="ti"></select>
-      <select id="swmode" class="ti"></select>
-    </div>
-  </div>
-  <div class="field grow">
-    <label class="flbl">Tile layout&nbsp;&nbsp;<span class="flbl-sub">S[shard] 
+ R[replica] + offset</span></label>
-    <input id="expr" class="ti expr" value="S[(4,8):(8@laneid,1@laneid)]" 
spellcheck="false">
-  </div>
-</div>
-<div id="status"></div>
-
-<div class="panels" id="panels">
-  <div class="panel">
-    <h2>Logical tensor</h2>
-    <div class="nota" id="n0"></div>
-    <div class="grid" id="g0"></div>
-  </div>
-  <div class="panel">
-    <h2>Physical threads</h2>
-    <div class="nota" id="nphys"></div>
-    <div id="phys"></div>
-  </div>
-  <svg class="arrow-svg" id="arrow"></svg>
-</div>
-
-<div class="formula-bar" id="fb"></div>
-<div class="leg" id="lg"></div>
-
-<script src="layout-demo.js"></script>
-</body>
-</html>
diff --git a/assets/tirx-layout-demo/layout-demo.js 
b/assets/tirx-layout-demo/layout-demo.js
deleted file mode 100644
index ec117accaf3..00000000000
--- a/assets/tirx-layout-demo/layout-demo.js
+++ /dev/null
@@ -1,805 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *
- * TIRx tensor-layout visualizer.
- *
- * The page shell, CSS vocabulary, and the draw()/hover/arrow interaction
- * pattern are adapted from the team's own course material
- * (mlsyscourse/slides-modern-gpu-programming, data-layout/site/demo/
- * tile_distributed.html). The TIRx S/R/O parser and the logical->physical
- * mapper below are original and mirror tvm/python/tvm/tirx/layout.py
- * (_flatten_coord / _split_coord and the TileLayout forward mapping).
- * This file is NOT derived from any third-party layout demo.
- */
-
-'use strict';
-
-// ── TIRx named axes (from tvm/python/tvm/tirx/layout.py _AXIS_NAMES, plus the
-// device axis `pid` used by distributed layouts) 
──────────────────────────────
-const AXIS_ORDER = [
-  'pid', 'bx', 'by', 'bz', 'cbx', 'cby', 'cbz', 'tx',
-  'warpid', 'laneid', 'wgid', 'tid_in_wg', 'wid_in_wg', 'tid',
-  'm', 'P', 'F', 'Bank', 'TCol', 'TLane',
-];
-// "Owner" axes name a physical unit that owns data (threads, devices); the 
rest
-// (m, P, F, Bank, TCol, TLane) are storage/memory coordinates within an owner.
-const OWNER_AXES = new Set([
-  'pid', 'bx', 'by', 'bz', 'cbx', 'cby', 'cbz', 'tx',
-  'warpid', 'laneid', 'wgid', 'tid_in_wg', 'wid_in_wg', 'tid',
-]);
-const KNOWN_AXES = new Set(AXIS_ORDER);
-const MAX_ELEMENTS = 1024;  // render cap
-
-function isOwnerAxis(a) { return OWNER_AXES.has(a); }
-function product(arr) { return arr.reduce((a, b) => a * b, 1); }
-
-// ── Parser 
───────────────────────────────────────────────────────────────────
-// Grammar mirrors layout.py: S[shape:stride] + R[shape:stride] + offset,
-// stride/offset terms are "n@axis" (a bare int defaults to axis "m").
-
-function splitTopLevel(s, sep) {
-  const out = [];
-  let depth = 0, cur = '';
-  for (const ch of s) {
-    if (ch === '(' || ch === '[') depth++;
-    else if (ch === ')' || ch === ']') {
-      depth--;
-      if (depth < 0) throw new Error('unmatched closing bracket or 
parenthesis');
-    }
-    if (ch === sep && depth === 0) { out.push(cur); cur = ''; }
-    else cur += ch;
-  }
-  if (depth !== 0) throw new Error('unmatched opening bracket or parenthesis');
-  out.push(cur);
-  return out;
-}
-
-function stripParens(s) {
-  s = s.trim();
-  if (s.startsWith('(') && s.endsWith(')')) return s.slice(1, -1);
-  return s;
-}
-
-function parseIntStrict(s) {
-  const t = s.trim();
-  if (!/^-?\d+$/.test(t)) throw new Error(`expected integer, got "${t}"`);
-  return parseInt(t, 10);
-}
-
-function parseTerm(tok) {
-  const t = tok.trim();
-  if (t.includes('@')) {
-    const parts = t.split('@');
-    if (parts.length !== 2) throw new Error(`bad term "${t}"`);
-    const numPart = parts[0].trim();
-    const axis = parts[1].trim();
-    if (!KNOWN_AXES.has(axis)) throw new Error(`unknown axis "${axis}"`);
-    const stride = numPart === '' ? 1 : parseIntStrict(numPart);
-    return { stride, axis };
-  }
-  if (/^-?\d+$/.test(t)) return { stride: parseInt(t, 10), axis: 'm' };
-  if (KNOWN_AXES.has(t)) return { stride: 1, axis: t };
-  throw new Error(`bad term "${t}"`);
-}
-
-function defaultStrides(extents) {
-  const n = extents.length;
-  const strides = new Array(n).fill(1);
-  for (let i = n - 2; i >= 0; i--) strides[i] = strides[i + 1] * extents[i + 
1];
-  return strides;
-}
-
-function parseExtents(s) {
-  // Parse a comma-separated extent list and reject non-positive / oversized
-  // extents, so bad input (e.g. R[1000000:...] or a 0/negative extent) can't
-  // NaN-propagate or freeze the tab with huge loops in physOwners().
-  const extents = splitTopLevel(stripParens(s), ',').map(parseIntStrict);
-  for (const e of extents) {
-    if (e <= 0) throw new Error(`extent must be positive, got ${e}`);
-    if (e > MAX_ELEMENTS) throw new Error(`extent ${e} exceeds maximum 
${MAX_ELEMENTS}`);
-  }
-  return extents;
-}
-
-function parseBracket(inner) {
-  const parts = splitTopLevel(inner, ':');
-  if (parts.length === 1) {
-    const extents = parseExtents(parts[0]);
-    const strides = defaultStrides(extents);
-    return extents.map((e, i) => ({ extent: e, stride: strides[i], axis: 'm' 
}));
-  }
-  if (parts.length !== 2) throw new Error('layout bracket must be "shape : 
stride"');
-  const extents = parseExtents(parts[0]);
-  const terms = splitTopLevel(stripParens(parts[1]), ',').map(parseTerm);
-  if (extents.length !== terms.length) {
-    throw new Error(`shape has ${extents.length} dims but stride has 
${terms.length}`);
-  }
-  return extents.map((e, i) => ({ extent: e, stride: terms[i].stride, axis: 
terms[i].axis }));
-}
-
-function bracketBody(piece, prefix) {
-  const open = piece.indexOf('[');
-  const close = piece.lastIndexOf(']');
-  if (open < 0 || close < 0 || close < open) throw new Error(`malformed 
${prefix}[...]`);
-  return piece.slice(open + 1, close);
-}
-
-function parseSwizzlePrefix(src) {
-  // Optional "Swizzle(per_element, swizzle_len, atom_len[, inner]) [∘|o|*] 
<layout>".
-  const m = src.match(/^Swizzle\s*\(([^)]*)\)\s*(?:∘|o|\.|\*)?\s*([\s\S]*)$/i);
-  if (!m) return { swizzle: null, rest: src };
-  const a = m[1].split(',').map((s) => s.trim()).filter((s) => s.length);
-  if (a.length < 3) throw new Error('Swizzle needs (per_element, swizzle_len, 
atom_len)');
-  const per_element = parseIntStrict(a[0]);
-  const swizzle_len = parseIntStrict(a[1]);
-  const atom_len = parseIntStrict(a[2]);
-  if (per_element < 0 || swizzle_len < 0 || atom_len < swizzle_len
-      || per_element >= 31 || atom_len >= 31) {
-    // atom_len/per_element feed 32-bit bitwise shifts in swizzleAddr; cap < 
31.
-    throw new Error('swizzle requires 0≤per_element<31, swizzle_len≥0, 
swizzle_len≤atom_len<31');
-  }
-  const inner = a[3] === undefined ? true : (a[3] === 'true' || a[3] === '1');
-  return { swizzle: { per_element, swizzle_len, atom_len, inner }, rest: 
m[2].trim() };
-}
-
-function parseLayout(srcRaw) {
-  const { swizzle, rest } = parseSwizzlePrefix(srcRaw.trim());
-  const src = rest;
-  const layout = { shard: [], replica: [], offset: {}, swizzle };
-  let sawShard = false;
-  for (let piece of splitTopLevel(src, '+')) {
-    piece = piece.trim();
-    if (piece === '') continue;
-    if (piece.startsWith('S[')) {
-      layout.shard = parseBracket(bracketBody(piece, 'S'));
-      sawShard = true;
-    } else if (piece.startsWith('R[')) {
-      layout.replica = parseBracket(bracketBody(piece, 'R'));
-    } else {
-      const t = parseTerm(piece);
-      layout.offset[t.axis] = (layout.offset[t.axis] || 0) + t.stride;
-    }
-  }
-  if (!sawShard) throw new Error('layout needs a shard term, e.g. S[...]');
-  return layout;
-}
-
-// ── Mapper (mirrors layout.py _flatten_coord / _split_coord + forward map) 
─────
-
-function flattenCoord(coord, shape) {
-  let flat = 0;
-  for (let i = 0; i < shape.length; i++) flat = flat * shape[i] + coord[i];
-  return flat;
-}
-
-function splitCoord(flat, extents) {
-  const n = extents.length;
-  const res = new Array(n);
-  let remaining = flat;
-  for (let i = n - 1; i >= 0; i--) {
-    if (i === 0) res[0] = remaining;
-    else { res[i] = remaining % extents[i]; remaining = Math.floor(remaining / 
extents[i]); }
-  }
-  return res;
-}
-
-function coordFromFlat(flat, shape) { return splitCoord(flat, shape); }
-
-function forwardBase(coord, shape, layout) {
-  const flat = flattenCoord(coord, shape);
-  const comps = splitCoord(flat, layout.shard.map((it) => it.extent));
-  const phys = {};
-  for (let k = 0; k < layout.shard.length; k++) {
-    const it = layout.shard[k];
-    phys[it.axis] = (phys[it.axis] || 0) + comps[k] * it.stride;
-  }
-  for (const axis of Object.keys(layout.offset)) {
-    phys[axis] = (phys[axis] || 0) + layout.offset[axis];
-  }
-  return phys;
-}
-
-// Replica broadcasts the same logical element onto multiple physical owners:
-// L(x) = { D(x) + r + O | r in R }.
-function physOwners(coord, shape, layout) {
-  let owners = [forwardBase(coord, shape, layout)];
-  for (const rep of layout.replica) {
-    const next = [];
-    for (const o of owners) {
-      for (let k = 0; k < rep.extent; k++) {
-        const o2 = Object.assign({}, o);
-        o2[rep.axis] = (o2[rep.axis] || 0) + k * rep.stride;
-        next.push(o2);
-      }
-    }
-    owners = next;
-  }
-  return owners;
-}
-
-function axesUsed(layout) {
-  const s = new Set();
-  for (const it of layout.shard) s.add(it.axis);
-  for (const it of layout.replica) s.add(it.axis);
-  for (const a of Object.keys(layout.offset)) s.add(a);
-  return AXIS_ORDER.filter((a) => s.has(a));
-}
-
-function coordStr(phys, axes) {
-  return axes.map((a) => `${a}=${phys[a] || 0}`).join(' ');
-}
-
-// Swizzle a linear memory address (mirrors 
src/tirx/ir/layout/swizzle_layout.cc
-// SwizzleLayoutNode::Apply): low `per_element` bits are kept; above them, the
-// swizzle bits are XOR'd to scatter bank conflicts.
-function swizzleAddr(m, sw) {
-  const base = 1 << sw.per_element;
-  const innerMask = (1 << sw.swizzle_len) - 1;
-  const outerMask = innerMask << sw.atom_len;
-  const x = Math.floor(m / base);
-  const fx = sw.inner ? (x ^ ((x & outerMask) >> sw.atom_len))
-                      : (x ^ ((x & innerMask) << sw.atom_len));
-  return fx * base + (m % base);
-}
-
-// Resolve the swizzle from the dtype + mode dropdowns (mirrors
-// tma_utils.mma_atom_layout): per_element = bit_length(128//bits) - 1,
-// swizzle_len = mode, atom_len = 3. Falls back to a typed Swizzle(...) prefix.
-const SWIZZLE_LEN = { none: 0, '32': 1, '64': 2, '128': 3 };
-function computeSwizzle() {
-  const mode = swmodeSel ? swmodeSel.value : 'off';
-  if (mode === 'off') return (ST.layout && ST.layout.swizzle) ? 
ST.layout.swizzle : null;
-  const bits = +(dtypeSel ? dtypeSel.value : 16) || 16;
-  const per_element = Math.floor(128 / bits).toString(2).length - 1;
-  return {
-    per_element, swizzle_len: SWIZZLE_LEN[mode] || 0, atom_len: 3, inner: true,
-    bits, mode,
-  };
-}
-
-// ── State + recompute 
──────────────────────────────────────────────────────--
-function getComputedStyleVar(name) {
-  return 
getComputedStyle(document.documentElement).getPropertyValue(name).trim();
-}
-const PALETTE = Array.from({ length: 8 }, (_, i) =>
-  getComputedStyleVar(`--color-group-${i}`) || '#5b9bd5');
-function paletteColor(v) { const n = PALETTE.length; return PALETTE[((v % n) + 
n) % n]; }
-
-const ST = {
-  shape: [4, 8],
-  layout: null,
-  error: null,
-  tooBig: false,
-  banks: 32,
-  swizzle: null,
-  gridAxes: [], yAxis: null, xAxis: null, cellAxes: [],
-  yVals: [], xVals: [],
-  byFlat: [],     // flat -> { owners:[phys], keys:[gridKey], color }
-  byCell: new Map(),   // "y#x" -> [{flat, slot}]
-};
-let hovFlat = null;
-let drawing = false;
-
-function mk(t, c) { const d = document.createElement(t); if (c) d.className = 
c; return d; }
-function gridKey(phys, axes) { return axes.map((a) => phys[a] || 0).join(','); 
}
-
-function recompute() {
-  ST.error = null; ST.tooBig = false;
-  try {
-    ST.shape = splitTopLevel(stripParens(shapeInput.value), 
',').map(parseIntStrict);
-    if (ST.shape.length === 0 || ST.shape.some((x) => x <= 0)) throw new 
Error('shape must be positive ints');
-    ST.layout = parseLayout(exprInput.value);
-  } catch (e) { ST.error = e.message; return; }
-
-  const total = product(ST.shape);
-  if (total > MAX_ELEMENTS) { ST.tooBig = true; return; }
-
-  ST.shapeTotal = total;
-  ST.shardTotal = product(ST.layout.shard.map((it) => it.extent));
-  ST.mismatch = ST.shardTotal !== total;
-  ST.swizzle = computeSwizzle();
-  // elements that share one 4-byte bank word (e.g. 2 fp16, 4 fp8, 1 fp32)
-  ST.elemsPerBank = ST.swizzle ? Math.max(1, Math.round(4 / ((ST.swizzle.bits 
|| 32) / 8))) : 1;
-
-  // 1) owners per element; in swizzle mode, also map the memory address 
through
-  //    the swizzle and derive synthetic line/bank coordinates.
-  ST.byFlat = new Array(total);
-  for (let flat = 0; flat < total; flat++) {
-    const owners = physOwners(coordFromFlat(flat, ST.shape), ST.shape, 
ST.layout);
-    if (ST.swizzle) {
-      // A shared-memory bank is 4 bytes; an element occupies dtype_bytes, so 
the
-      // bank word index is floor(element_addr * dtype_bytes / 4). 32 banks 
per line.
-      const bytes = (ST.swizzle.bits || 32) / 8;
-      for (const o of owners) {
-        const sm = swizzleAddr(o.m || 0, ST.swizzle);
-        const word = Math.floor((sm * bytes) / 4);
-        o.__sm = sm; o.__word = word; o.bank = word % ST.banks; o.line = 
Math.floor(word / ST.banks);
-      }
-    }
-    ST.byFlat[flat] = { owners };
-  }
-
-  // 2) choose grid + color axes
-  if (ST.swizzle) {
-    ST.gridAxes = ['line', 'bank']; ST.yAxis = 'line'; ST.xAxis = 'bank';
-    ST.cellAxes = []; ST.colorAxis = 'bank';
-  } else {
-    const used = axesUsed(ST.layout);
-    const owners = used.filter(isOwnerAxis);
-    ST.gridAxes = owners.length ? owners : used.filter((a) => !isOwnerAxis(a));
-    ST.yAxis = ST.gridAxes[0] || null;
-    ST.xAxis = ST.gridAxes[1] || null;
-    ST.cellAxes = used.filter((a) => !ST.gridAxes.includes(a));
-    // color axis = first grid axis from shard/offset, so a replica-only row 
axis
-    // doesn't collapse every element to one color.
-    const shardOffsetAxes = new Set(ST.layout.shard.map((it) => it.axis));
-    for (const a of Object.keys(ST.layout.offset)) shardOffsetAxes.add(a);
-    ST.colorAxis = ST.gridAxes.find((a) => shardOffsetAxes.has(a)) || 
ST.gridAxes[0] || null;
-  }
-
-  // 3) build cells, hover keys, colors
-  ST.byCell = new Map();
-  const yset = new Set(), xset = new Set(), cset = new Set();
-  for (let flat = 0; flat < total; flat++) {
-    const rec = ST.byFlat[flat];
-    const keys = [];
-    for (const o of rec.owners) {
-      const y = ST.yAxis ? (o[ST.yAxis] || 0) : 0;
-      const x = ST.xAxis ? (o[ST.xAxis] || 0) : 0;
-      yset.add(y); xset.add(x);
-      keys.push(gridKey(o, ST.gridAxes));
-      const ck = y + '#' + x;
-      if (!ST.byCell.has(ck)) ST.byCell.set(ck, []);
-      ST.byCell.get(ck).push({ flat, slot: ST.swizzle ? ('addr ' + o.__sm) : 
coordStr(o, ST.cellAxes) });
-    }
-    rec.keys = keys;
-    const cv = ST.colorAxis ? (rec.owners[0][ST.colorAxis] || 0) : 0;
-    rec.color = paletteColor(cv);
-    cset.add(cv);
-  }
-  ST.yVals = [...yset].sort((a, b) => a - b);
-  ST.xVals = [...xset].sort((a, b) => a - b);
-  ST.colorVals = [...cset].sort((a, b) => a - b);
-}
-
-// ── Display geometry for the logical grid 
──────────────────────────────────--
-function logicalGridDims() {
-  if (ST.shape.length === 2) return { rows: ST.shape[0], cols: ST.shape[1] };
-  if (ST.shape.length === 1) return { rows: 1, cols: ST.shape[0] };
-  return { rows: 1, cols: product(ST.shape) };  // N-D -> flat strip
-}
-
-// ── Draw ────────────────────────────────────────────────────────────────────
-function resetFit() {
-  const p = document.getElementById('panels');
-  if (p) { p.style.transform = 'none'; p.style.marginBottom = ''; }
-}
-function fitEmbed() {
-  if (!document.body.classList.contains('lock')) return;
-  const p = document.getElementById('panels');
-  if (!p) return;
-  const natural = p.offsetWidth;
-  const pad = 2 * parseFloat(getComputedStyle(document.body).paddingLeft || 
'0');
-  const avail = document.documentElement.clientWidth - pad;
-  if (avail > 0 && natural > avail) {
-    const sc = avail / natural;
-    p.style.transformOrigin = 'top left';
-    p.style.transform = 'scale(' + sc + ')';
-    p.style.marginBottom = (-(p.offsetHeight * (1 - sc))) + 'px';
-  }
-}
-function postHeight() {
-  if (window.parent === window) return;
-  const h = Math.ceil(document.body.scrollHeight);
-  window.parent.postMessage({ tirxLayoutDemoHeight: h + 4 }, '*');
-}
-function draw() {
-  drawing = true;
-  resetFit();
-  const status = document.getElementById('status');
-  const g0 = document.getElementById('g0');
-  const phys = document.getElementById('phys');
-  const fb = document.getElementById('fb');
-  const lg = document.getElementById('lg');
-
-  if (ST.error) {
-    status.innerHTML = `<span style="color:var(--color-bad)">parse error: 
${escapeHtml(ST.error)}</span>`;
-    g0.innerHTML = ''; phys.innerHTML = ''; lg.innerHTML = '';
-    fb.innerHTML = '<div class="ftitle">Fix the layout expression to 
continue.</div>';
-    setTimeout(() => { drawing = false; }, 0); return;
-  }
-  if (ST.tooBig) {
-    status.innerHTML = `<span 
style="color:var(--color-bad)">${product(ST.shape)} elements exceeds the 
${MAX_ELEMENTS} render cap — use a smaller shape.</span>`;
-    g0.innerHTML = ''; phys.innerHTML = ''; lg.innerHTML = '';
-    fb.innerHTML = '<div class="ftitle">Shape too large to visualize.</div>';
-    setTimeout(() => { drawing = false; }, 0); return;
-  }
-
-  status.innerHTML = `<span style="color:var(--color-good)">ok</span> &nbsp; ` 
+
-    `${product(ST.shape)} logical elements &nbsp;|&nbsp; ` +
-    `${ST.yVals.length * (ST.xVals.length || 1)} physical cells`;
-  if (ST.mismatch) {
-    status.innerHTML += ` &nbsp;<span style="color:#b45309;font-weight:600">` +
-      `⚠ shard total ${ST.shardTotal} ≠ shape total ${ST.shapeTotal} — mapping 
may be ill-formed</span>`;
-  }
-  if (ST.swizzle) {
-    const s = ST.swizzle;
-    const label = s.mode ? (s.mode === 'none' ? 'no swizzle' : s.mode + 'B 
swizzle') : 'swizzle';
-    status.innerHTML += ` &nbsp;<span style="color:var(--dim)">` +
-      `${label}${s.bits ? ', ' + s.bits + '-bit' : ''} → 
Swizzle(${s.per_element},${s.swizzle_len},${s.atom_len})</span>`;
-  }
-  document.getElementById('n0').textContent = `logical shape 
(${ST.shape.join(', ')})`;
-  document.getElementById('nphys').textContent =
-    (ST.xAxis ? `rows = ${ST.yAxis}, cols = ${ST.xAxis}` : `tiles = ${ST.yAxis 
|| '(none)'}`) +
-    (ST.cellAxes.length ? '   ·   in-cell: ' + ST.cellAxes.join(', ') : '');
-
-  const hovKeys = hovFlat !== null ? new Set(ST.byFlat[hovFlat].keys) : null;
-  drawLogical(hovKeys);
-  drawPhysical(hovKeys);
-  drawFormula();
-  drawArrow();
-  drawLegend();
-  fitEmbed();
-  postHeight();
-  setTimeout(() => { drawing = false; }, 0);
-}
-
-function sharesOwner(flat, hovKeys) {
-  if (!hovKeys) return false;
-  return ST.byFlat[flat].keys.some((k) => hovKeys.has(k));
-}
-
-function drawLogical(hovKeys) {
-  const g = document.getElementById('g0');
-  g.innerHTML = '';
-  const { rows, cols } = logicalGridDims();
-  g.style.gridTemplateColumns = '30px repeat(' + cols + ', 46px)';
-  g.appendChild(mk('div', 'hdr'));
-  for (let c = 0; c < cols; c++) { const h = mk('div', 'hdr'); h.textContent = 
'c' + c; g.appendChild(h); }
-  for (let r = 0; r < rows; r++) {
-    const rl = mk('div', 'rl'); rl.textContent = (rows > 1) ? ('r' + r) : ''; 
g.appendChild(rl);
-    for (let c = 0; c < cols; c++) {
-      const flat = r * cols + c;
-      const d = mk('div', 'cell');
-      d.dataset.flat = flat;
-      d.textContent = flat;
-      d.style.background = ST.byFlat[flat].color;
-      d.style.color = '#fff';
-      if (hovFlat !== null) {
-        if (flat === hovFlat) d.classList.add('hov');
-        else if (!sharesOwner(flat, hovKeys)) d.classList.add('dm');
-      }
-      g.appendChild(d);
-    }
-  }
-}
-
-function cellEntries(y, x) { return ST.byCell.get(y + '#' + x) || []; }
-
-function makeSlot(entry) {
-  const s = mk('div', 'gcell');
-  s.style.background = ST.byFlat[entry.flat].color;
-  s.style.color = '#fff';
-  s.dataset.flat = entry.flat;
-  s.textContent = entry.flat;
-  s.title = entry.slot ? `element ${entry.flat} @ ${entry.slot}` : `element 
${entry.flat}`;
-  if (hovFlat !== null) {
-    if (entry.flat === hovFlat) s.classList.add('hov');
-    else s.classList.add('dm');
-  }
-  return s;
-}
-
-function drawPhysical(hovKeys) {
-  const wrap = document.getElementById('phys');
-  wrap.innerHTML = '';
-  if (!ST.yAxis) { wrap.textContent = '(no physical axes)'; return; }
-
-  if (ST.xAxis) {
-    // 2D table: rows = yAxis values, cols = xAxis values.
-    const table = mk('div', 'phys-table' + (ST.swizzle ? ' bank-mode' : ''));
-    // In bank mode a cell is one 4-byte bank word holding elemsPerBank 
elements
-    // laid out horizontally; otherwise a fixed 54px cell.
-    const colW = ST.swizzle ? (ST.elemsPerBank * 48 + 8) : 54;
-    table.style.gridTemplateColumns = '44px repeat(' + ST.xVals.length + ', ' 
+ colW + 'px)';
-    table.appendChild(corner());
-    for (const x of ST.xVals) table.appendChild(axHdr(String(x), false, 
`${ST.xAxis}=${x}`));
-    for (const y of ST.yVals) {
-      table.appendChild(axHdr(String(y), true, `${ST.yAxis}=${y}`));
-      for (const x of ST.xVals) {
-        const cell = mk('div', 'pcell');
-        const entries = cellEntries(y, x);
-        if (hovFlat !== null && entries.some((e) => e.flat === hovFlat)) 
cell.classList.add('hov-cell');
-        else if (hovFlat !== null && entries.length) 
cell.classList.add('dm-cell');
-        for (const e of entries) cell.appendChild(makeSlot(e));
-        table.appendChild(cell);
-      }
-    }
-    wrap.appendChild(table);
-  } else {
-    // 1D: wrapped list of owner tiles, one per yAxis value.
-    const list = mk('div', 'phys-1d');
-    for (const y of ST.yVals) {
-      const tile = mk('div', 'thread-tile');
-      const lbl = mk('div', 'thread-lbl'); lbl.textContent = 
`${ST.yAxis}=${y}`; tile.appendChild(lbl);
-      const slots = mk('div', 'thread-slots');
-      const entries = cellEntries(y, 0).slice().sort((a, b) => a.flat - 
b.flat);
-      if (hovFlat !== null && entries.some((e) => e.flat === hovFlat)) 
tile.classList.add('hov-tile');
-      else if (hovFlat !== null && entries.length) 
tile.classList.add('dm-tile');
-      for (const e of entries) slots.appendChild(makeSlot(e));
-      tile.appendChild(slots);
-      list.appendChild(tile);
-    }
-    wrap.appendChild(list);
-  }
-}
-
-function corner() {
-  const d = mk('div', 'ax-hdr corner');
-  d.textContent = '↘';
-  if (ST.yAxis) d.title = `rows = ${ST.yAxis}` + (ST.xAxis ? `, cols = 
${ST.xAxis}` : '');
-  return d;
-}
-function axHdr(text, isRow, title) {
-  const d = mk('div', 'ax-hdr' + (isRow ? ' row' : ''));
-  d.textContent = text;
-  if (title) d.title = title;
-  return d;
-}
-
-function drawFormula() {
-  const fb = document.getElementById('fb');
-  if (hovFlat === null) { fb.innerHTML = '<div class="ftitle">Click a logical 
element to see its mapping.</div>'; return; }
-  const flat = hovFlat;
-  const coord = coordFromFlat(flat, ST.shape);
-  const comps = splitCoord(flat, ST.layout.shard.map((it) => it.extent));
-  const perAxis = {};
-  const termStrings = [];
-  for (let k = 0; k < ST.layout.shard.length; k++) {
-    const it = ST.layout.shard[k];
-    perAxis[it.axis] = (perAxis[it.axis] || 0) + comps[k] * it.stride;
-    termStrings.push(`${comps[k]}·${it.stride}@${it.axis}`);
-  }
-  const offStrings = [];
-  for (const axis of Object.keys(ST.layout.offset)) {
-    perAxis[axis] = (perAxis[axis] || 0) + ST.layout.offset[axis];
-    offStrings.push(`${ST.layout.offset[axis]}@${axis}`);
-  }
-  const owners = ST.byFlat[flat].owners;
-  let html = `<div class="ftitle">element ${flat} at logical (${coord.join(', 
')})` +
-    ` &nbsp;→&nbsp; shard split = (${comps.join(', ')})</div>`;
-  html += '<div class="fcontent">';
-  html += `terms: ${termStrings.join('  +  ')}` +
-    (offStrings.length ? `  +  offset[${offStrings.join(', ')}]` : '') + 
'<br>';
-  const baseParts = AXIS_ORDER.filter((a) => perAxis[a] !== undefined).map((a) 
=> `<b>${perAxis[a]}</b>@${a}`);
-  html += `base location: ${baseParts.join(' , ')}`;
-  if (ST.swizzle) {
-    const o0 = owners[0];
-    const sw = ST.swizzle;
-    const bytes = (sw.bits || 32) / 8;
-    html += `<br>swizzle(${sw.per_element},${sw.swizzle_len},${sw.atom_len}): 
` +
-      `m=${o0.m || 0} → elem ${o0.__sm} → byte ${o0.__sm * bytes} → ` +
-      `<b>bank ${o0.bank}</b>, line ${o0.line} (${bytes}-byte dtype, 4-byte 
banks ×32)`;
-  }
-  if (owners.length > 1) {
-    html += `<br>owners (×${owners.length} via replica): ` +
-      owners.map((o) => '{ ' + coordStr(o, ST.gridAxes) + ' }').join('  ,  ');
-  }
-  html += '</div>';
-  fb.innerHTML = html;
-}
-
-// ── Arrow overlay (adapted from the course draw pattern) 
───────────────────--
-const panels = document.getElementById('panels');
-const arrowSvg = document.getElementById('arrow');
-
-function drawArrow() {
-  arrowSvg.innerHTML = '';
-  if (hovFlat === null) return;
-  const leftCell = document.querySelector('#g0 .cell.hov');
-  const rightCells = document.querySelectorAll('#phys .gcell.hov');
-  if (!leftCell || rightCells.length === 0) return;
-  const pr = panels.getBoundingClientRect();
-  const ns = 'http://www.w3.org/2000/svg';
-  const defs = document.createElementNS(ns, 'defs');
-  const marker = document.createElementNS(ns, 'marker');
-  marker.setAttribute('id', 'ah'); marker.setAttribute('markerWidth', '8'); 
marker.setAttribute('markerHeight', '6');
-  marker.setAttribute('refX', '7'); marker.setAttribute('refY', '3'); 
marker.setAttribute('orient', 'auto');
-  const poly = document.createElementNS(ns, 'polygon');
-  poly.setAttribute('points', '0 0, 8 3, 0 6'); poly.setAttribute('fill', 
'#222');
-  marker.appendChild(poly); defs.appendChild(marker); 
arrowSvg.appendChild(defs);
-  const a = leftCell.getBoundingClientRect();
-  const x1 = a.left + a.width / 2 - pr.left, y1 = a.top + a.height / 2 - 
pr.top;
-  rightCells.forEach((rc) => {
-    const b = rc.getBoundingClientRect();
-    const x2 = b.left + b.width / 2 - pr.left, y2 = b.top + b.height / 2 - 
pr.top;
-    const mx = (x1 + x2) / 2, my = Math.min(y1, y2) - 24;
-    const path = document.createElementNS(ns, 'path');
-    path.setAttribute('d', `M${x1},${y1} Q${mx},${my} ${x2},${y2}`);
-    path.setAttribute('marker-end', 'url(#ah)');
-    arrowSvg.appendChild(path);
-  });
-}
-
-function clearHov() {
-  document.querySelectorAll('.cell.hov, .gcell.hov').forEach((d) => 
d.classList.remove('hov'));
-  arrowSvg.innerHTML = '';
-  hovFlat = null;
-}
-
-panels.addEventListener('click', (e) => {
-  const cell = e.target.closest('.cell') || e.target.closest('.gcell');
-  if (!cell || cell.dataset.flat === undefined) return;
-  const flat = +cell.dataset.flat;
-  if (hovFlat === flat) { clearHov(); draw(); return; }
-  clearHov();
-  hovFlat = flat;
-  draw();
-});
-
-// ── Legend ──────────────────────────────────────────────────────────────────
-function swatchEl(color) { const w = mk('div', 'swtch'); w.style.background = 
color; return w; }
-
-function drawLegend() {
-  const lg = document.getElementById('lg');
-  lg.innerHTML = '';
-
-  // Color key: an actual swatch-per-value table using the same palette as the 
grid.
-  const r0 = mk('div', 'leg-row');
-  const lead = mk('div', 'li');
-  lead.innerHTML = `<b>color = ${ST.colorAxis || 'physical'} value:</b>`;
-  r0.appendChild(lead);
-  const vals = ST.colorVals || [];
-  if (vals.length <= 8) {
-    for (const v of vals) {
-      const li = mk('div', 'li');
-      li.appendChild(swatchEl(paletteColor(v)));
-      li.appendChild(document.createTextNode(String(v)));
-      r0.appendChild(li);
-    }
-  } else {
-    for (let k = 0; k < 8; k++) {
-      const li = mk('div', 'li');
-      li.appendChild(swatchEl(PALETTE[k]));
-      li.appendChild(document.createTextNode('≡' + k));
-      r0.appendChild(li);
-    }
-    const note = mk('div', 'li');
-    note.textContent = `(${ST.colorAxis} mod 8; ${vals.length} values)`;
-    r0.appendChild(note);
-  }
-  lg.appendChild(r0);
-
-  const r1 = mk('div', 'leg-row');
-  const c2 = mk('div', 'li'); c2.textContent = 'number = logical element 
index'; r1.appendChild(c2);
-  if (ST.layout.replica.length) {
-    r1.appendChild(mk('div', 'leg-sep'));
-    const c3 = mk('div', 'li');
-    c3.textContent = 'replica → identical copies: the same color appears in 
multiple physical cells';
-    r1.appendChild(c3);
-  }
-  lg.appendChild(r1);
-
-  const r2 = mk('div', 'leg-row');
-  const m = mk('div', 'li');
-  const owners = axesUsed(ST.layout).filter(isOwnerAxis);
-  const mem = axesUsed(ST.layout).filter((a) => !isOwnerAxis(a));
-  m.textContent = 'owner axes: ' + (owners.join(', ') || '(none — pure memory 
layout)') +
-    '   ·   memory axes: ' + (mem.join(', ') || '(none)');
-  r2.appendChild(m);
-  lg.appendChild(r2);
-}
-
-function escapeHtml(s) {
-  return s.replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': 
'&gt;', '"': '&quot;' }[c]));
-}
-
-// ── Presets + controls 
───────────────────────────────────────────────────────
-// Case-study presets use scaled-down shapes so every element renders; the
-// mapping semantics match the full-size examples in the docs.
-const PRESETS = [
-  { label: 'Shard → lanes (intro)', shape: '4, 8', expr: 
'S[(4,8):(8@laneid,1@laneid)]' },
-  { label: 'Shard + registers', shape: '4, 8', expr: 
'S[(4,2,4):(8@laneid,1@m,1@laneid)]' },
-  { label: 'Shard + replica', shape: '4, 8', expr: 
'S[(4,8):(8@laneid,1@laneid)] + R[2:1@warpid]' },
-  {
-    label: 'Tensor-core tile (doc example)', shape: '8, 16',
-    expr: 'S[(8,2,4,2):(4@laneid,1@warpid,1@laneid,1)] + R[2:4@warpid] + 
5@warpid',
-  },
-  { label: 'Distributed 2×2 GPU mesh (pid)', shape: '4, 4', expr: 
'S[(2,2,2,2):(1@pid,2@m,2@pid,1@m)]' },
-  { label: 'Mesh + replica (pid)', shape: '4, 4', expr: 
'S[(2,2,4):(1@pid,2@m,1@m)] + R[2:2@pid]' },
-  { label: 'Accelerator scratchpad (P/F)', shape: '4, 8', expr: 
'S[(2,4,4):(4@F,1@P,1@F)]' },
-  { label: 'Blackwell tensor memory (TLane/TCol)', shape: '4, 8', expr: 
'S[(2,4,4):(4@TCol,1@TLane,1@TCol)]' },
-  { label: 'SMEM, no swizzle (bank conflicts)', shape: '8, 64', expr: 
'S[(8,64):(64@m,1@m)]', dtype: 16, mode: 'none' },
-  { label: 'SMEM swizzle 128B (fp16)', shape: '8, 64', expr: 
'S[(8,64):(64@m,1@m)]', dtype: 16, mode: '128' },
-  { label: '1-D shard', shape: '8', expr: 'S[8:4@laneid]' },
-  { label: 'Extents only (default strides)', shape: '8, 4', expr: 'S[(8,4)]' },
-];
-
-const DTYPES = [
-  { label: 'float16 (16-bit)', bits: 16 },
-  { label: 'bfloat16 (16-bit)', bits: 16 },
-  { label: 'float8 (8-bit)', bits: 8 },
-  { label: 'float32 (32-bit)', bits: 32 },
-  { label: 'tfloat32 (32-bit)', bits: 32 },
-  { label: 'float64 (64-bit)', bits: 64 },
-];
-const SWMODES = [
-  { label: 'off (general layout)', value: 'off' },
-  { label: 'none — raw banks', value: 'none' },
-  { label: '32B swizzle', value: '32' },
-  { label: '64B swizzle', value: '64' },
-  { label: '128B swizzle', value: '128' },
-];
-
-const shapeInput = document.getElementById('shape');
-const exprInput = document.getElementById('expr');
-const presetSel = document.getElementById('preset');
-const dtypeSel = document.getElementById('dtype');
-const swmodeSel = document.getElementById('swmode');
-
-function applyPreset(i) {
-  const p = PRESETS[i];
-  shapeInput.value = p.shape;
-  exprInput.value = p.expr;
-  if (dtypeSel) dtypeSel.value = String(p.dtype || 16);
-  if (swmodeSel) swmodeSel.value = p.mode || 'off';
-  refresh();
-}
-function refresh() { clearHov(); recompute(); draw(); }
-
-function init() {
-  PRESETS.forEach((p, i) => {
-    const o = document.createElement('option');
-    o.value = i; o.textContent = p.label; presetSel.appendChild(o);
-  });
-  DTYPES.forEach((d) => {
-    const o = document.createElement('option');
-    o.value = d.bits; o.textContent = d.label; dtypeSel.appendChild(o);
-  });
-  SWMODES.forEach((s) => {
-    const o = document.createElement('option');
-    o.value = s.value; o.textContent = s.label; swmodeSel.appendChild(o);
-  });
-  presetSel.addEventListener('change', () => applyPreset(+presetSel.value));
-  shapeInput.addEventListener('input', refresh);
-  exprInput.addEventListener('input', refresh);
-  dtypeSel.addEventListener('change', refresh);
-  swmodeSel.addEventListener('change', refresh);
-  window.addEventListener('resize', () => { resetFit(); fitEmbed(); 
postHeight(); });
-  window.addEventListener('load', () => { resetFit(); fitEmbed(); 
postHeight(); });
-  // Deep-linking for embeds: ?preset=<index|label-slug>&notitle
-  const params = new URLSearchParams(location.search);
-  let presetIdx = 0;
-  const want = params.get('preset');
-  if (want !== null) {
-    const slug = (x) => x.toLowerCase().replace(/[^a-z0-9]+/g, 
'-').replace(/^-|-$/g, '');
-    if (/^\d+$/.test(want)) {
-      presetIdx = Math.min(PRESETS.length - 1, Math.max(0, parseInt(want, 
10)));
-    } else {
-      const w = slug(want);
-      const found = PRESETS.findIndex((p) => slug(p.label).includes(w));
-      if (found >= 0) presetIdx = found;
-    }
-  }
-  presetSel.value = presetIdx;
-  if (params.has('notitle')) document.body.classList.add('notitle');
-  if (params.has('lock')) document.body.classList.add('lock');
-  applyPreset(presetIdx);
-}
-
-init();
diff --git a/assets/tirx-layout-demo/viz-base.css 
b/assets/tirx-layout-demo/viz-base.css
deleted file mode 100644
index 81ffadc19cd..00000000000
--- a/assets/tirx-layout-demo/viz-base.css
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *
- * Derived from mlsyscourse/slides-modern-gpu-programming
- * (data-layout/site/viz-base.css). Shared base styles for the
- * TIRx layout visualization; not derived from any third-party demo.
- */
-
-/* Shared base styles for all viz HTMLs
- * Colors: --bg gray, --accent blue, --surface white
- * Fonts: Inter (body), SF Mono (code/notation)
- */
-
-:root {
-  --bg:#fff; --surface:#fff; --border:#dfe1e6; --text:#222; --dim:#888; 
--accent:#3b82f6;
-
-  /* ── Group palette (tiles, lanes, banks, sectors) ─────── */
-  --color-group-0: #5b9bd5;
-  --color-group-1: #ed9a3c;
-  --color-group-2: #d95555;
-  --color-group-3: #45b5a5;
-  --color-group-4: #5fb85f;
-  --color-group-5: #e0b828;
-  --color-group-6: #9d6eb5;
-  --color-group-7: #e87888;
-
-  /* ── Interaction ──────────────────────────────────────── */
-  --color-hover-bg: #dbeafe;
-  --color-hover-text: #1e40af;
-
-  /* ── Status ───────────────────────────────────────────── */
-  --color-good: #2e7d32;
-  --color-bad: #c62828;
-
-  /* ── Boundaries & neutrals ────────────────────────────── */
-  --color-boundary: #8C1515;
-  --color-cell-bg: #f0f1f3;
-  --color-cell-bg-alt: #e8eaed;
-}
-* { box-sizing:border-box; margin:0; padding:0; }
-body { background:var(--bg); color:var(--text); font-family:'Inter','SF 
Pro','Segoe UI',system-ui,sans-serif; padding:24px; }
-body.figure h1, body.figure .sub { display:none; }
-body.notitle h1, body.notitle .sub { display:none; }
-h1 { text-align:center; font-size:21px; font-weight:700; margin-bottom:2px; }
-.sub { text-align:center; color:var(--dim); font-size:14px; 
margin-bottom:20px; font-family:'SF Mono','Fira Code',monospace; }
-
-/* Controls */
-.controls { display:flex; gap:18px; justify-content:center; 
align-items:center; margin-bottom:10px; flex-wrap:wrap; }
-.lbl { font-size:13px; color:var(--dim); margin-right:4px; font-weight:600; }
-.bg { display:inline-flex; gap:2px; }
-.btn {
-  padding:5px 11px; border:1px solid var(--border); background:var(--surface); 
color:var(--text);
-  cursor:pointer; border-radius:5px; font-size:13px; font-family:inherit; 
font-weight:500; transition:all .12s;
-}
-.btn:hover { background:#eef0f4; }
-.btn.on { background:var(--accent); border-color:var(--accent); color:#fff; }
-.btn.on:hover { background:#2563eb; }
-
-/* Side-by-side panels */
-.panels { display:grid; grid-template-columns:1fr 1fr; gap:20px; 
max-width:1100px; margin:0 auto; position:relative; }
-.panel { background:var(--surface); border-radius:10px; padding:16px 14px; 
border:1px solid var(--border);
-  box-shadow:0 1px 3px rgba(0,0,0,.06); }
-.panel h2 { text-align:center; font-size:14px; font-weight:700; 
margin-bottom:2px; }
-.panel .nota { text-align:center; font-size:11px; color:var(--accent); 
font-family:'SF Mono','Fira Code',monospace;
-  margin-bottom:8px; font-weight:600; }
-
-/* Grid cells */
-.grid { display:grid; gap:3px; }
-.hdr { font-size:10px; color:var(--dim); text-align:center; padding:2px 0; 
font-weight:600; }
-.rl { font-size:10px; color:var(--dim); display:flex; align-items:center; 
justify-content:flex-end; padding-right:3px; font-weight:600; }
-.cell {
-  aspect-ratio:1; border-radius:5px; display:flex; flex-direction:column; 
align-items:center; justify-content:center;
-  font-size:13px; font-weight:700; border:2.5px solid transparent; 
transition:all .18s;
-  min-width:0; cursor:pointer; line-height:1.15;
-}
-.cell.hov { border-color:#222; border-width:3px; z-index:2; box-shadow:0 0 8px 
rgba(59,130,246,.4); }
-.cell.dm { opacity:.25; }
-
-/* Arrow SVG overlay */
-.arrow-svg { position:absolute; top:0; left:0; width:100%; height:100%; 
pointer-events:none; z-index:10; overflow:visible; }
-.arrow-svg path { fill:none; stroke:#222; stroke-width:1.5; }
-.arrow-svg text { font-size:11px; font-weight:600; fill:#222; 
font-family:inherit; }
-
-/* Formula bar */
-.formula-bar { max-width:1100px; margin:18px auto 0; 
background:var(--surface); border-radius:10px;
-  padding:12px 18px; border:1px solid var(--border); box-shadow:0 1px 3px 
rgba(0,0,0,.06); }
-.formula-bar .ftitle { font-size:13px; font-weight:700; margin-bottom:6px; }
-.formula-bar .fcontent { font-size:13px; font-family:'SF Mono','Fira 
Code',monospace; color:var(--accent); line-height:1.6; }
-
-/* Legend */
-.leg { display:flex; flex-direction:column; align-items:center; gap:4px; 
margin-top:14px; }
-.leg-row { display:flex; gap:12px; justify-content:center; flex-wrap:wrap; }
-.leg-group { display:flex; gap:10px; align-items:center; }
-.leg-sep { width:1px; height:16px; background:var(--border); }
-.li { display:flex; align-items:center; gap:4px; font-size:12px; 
color:var(--dim); }
-.swtch { width:14px; height:14px; border-radius:3px; }
-
-@media(max-width:700px) { .panels { grid-template-columns:1fr; } }
diff --git a/assets/tirx-layout-demo/viz-base.js 
b/assets/tirx-layout-demo/viz-base.js
deleted file mode 100644
index c5ef10f13db..00000000000
--- a/assets/tirx-layout-demo/viz-base.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *
- * Derived from mlsyscourse/slides-modern-gpu-programming
- * (data-layout/site/viz-base.js). Shared behavior for the TIRx
- * layout visualization; not derived from any third-party demo.
- */
-
-// Shared behavior for all viz HTMLs
-document.addEventListener('DOMContentLoaded', function() {
-  var p = new URLSearchParams(location.search);
-  if (p.has('notitle')) document.body.classList.add('notitle');
-
-  // Forward arrow keys to parent (reveal.js) when embedded
-  if (window.parent !== window) {
-    document.addEventListener('keydown', function(e) {
-      if ([37, 38, 39, 40, 27, 32].indexOf(e.keyCode) !== -1) {
-        // Left, Up, Right, Down, Escape, Space
-        window.parent.postMessage({ type: 'revealKey', keyCode: e.keyCode }, 
'*');
-      }
-    });
-  }
-});
-
-// Auto-height: when embedded in the book, measure our own content height and 
post
-// it to the parent so it can size the iframe to fit (no inner scrollbar). This
-// demo is responsive (it fills the iframe width), so only the HEIGHT needs to
-// follow content. Push-based, so it catches our own DOM changes (editing the
-// layout, clicking a cell, switching presets) that an outside observer can 
miss.
-(function () {
-  if (window.parent === window) return;
-  var lastH = 0;
-  function report() {
-    var b = document.body, de = document.documentElement;
-    var h = (b ? b.scrollHeight : 0) || (de ? de.scrollHeight : 0) || 0;
-    if (h && Math.abs(h - lastH) > 1) {
-      lastH = h;
-      window.parent.postMessage({ type: 'demoHeight', height: h }, '*');
-    }
-  }
-  var scheduled = false;
-  function schedule() {
-    if (scheduled) return;
-    scheduled = true;
-    requestAnimationFrame(function () { scheduled = false; report(); });
-  }
-  try { new ResizeObserver(schedule).observe(document.documentElement); } 
catch (e) {}
-  try {
-    new MutationObserver(schedule).observe(document.documentElement, {
-      subtree: true, childList: true, attributes: true, characterData: true
-    });
-  } catch (e) {}
-  document.addEventListener('DOMContentLoaded', schedule);
-  window.addEventListener('load', schedule);
-  window.addEventListener('click', function () { setTimeout(schedule, 0); }, 
true);
-  [100, 300, 600, 1200].forEach(function (t) { setTimeout(schedule, t); });
-})();
diff --git a/atom.xml b/atom.xml
index 3ff8cc01cfe..872ca5c164f 100644
--- a/atom.xml
+++ b/atom.xml
@@ -4,7 +4,7 @@
  <title>TVM</title>
  <link href="https://tvm.apache.org"; rel="self"/>
  <link href="https://tvm.apache.org"/>
- <updated>2026-06-22T03:50:20+00:00</updated>
+ <updated>2026-06-22T03:55:23+00:00</updated>
  <id>https://tvm.apache.org</id>
  <author>
    <name></name>
@@ -130,20 +130,9 @@ L(i,j)_{(8,16)} &amp;amp;= L(i\cdot 16 + j) 
&amp;amp;&amp;amp; \text{(flatten)}
   &lt;li&gt;owners (×2 via replica): { warpid=6 laneid=12 }, { warpid=10 
laneid=12 }&lt;/li&gt;
 &lt;/ul&gt;
 
-&lt;p&gt;&lt;em&gt;(Click element 57 in the interactive demo below to see 
exactly these owners.)&lt;/em&gt;&lt;/p&gt;
-
-&lt;details&gt;
-&lt;summary&gt;Unfold to see the interactive layout demo&lt;/summary&gt;
-&lt;iframe id=&quot;tirx-layout-demo&quot; 
src=&quot;/assets/tirx-layout-demo/index.html?preset=tensor-core&amp;amp;notitle&amp;amp;lock&quot;
 style=&quot;width:100%; height:560px; border:1px solid #dfe1e6; 
border-radius:10px; margin:12px 0;&quot; title=&quot;TIRx interactive layout 
demo: tensor-core tile&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
-&lt;script&gt;
-window.addEventListener(&apos;message&apos;, function (e) {
-  var h = e.data &amp;&amp; e.data.tirxLayoutDemoHeight;
-  if (!h) return;
-  var f = document.getElementById(&apos;tirx-layout-demo&apos;);
-  if (f) f.style.height = h + &apos;px&apos;;
-});
-&lt;/script&gt;
-&lt;/details&gt;
+&lt;p&gt;&lt;em&gt;(Open the interactive demo and click element 57 to see 
exactly these owners.)&lt;/em&gt;&lt;/p&gt;
+
+&lt;p&gt;&lt;a 
href=&quot;https://mlc.ai/modern-gpu-programming-for-mlsys/_static/tirx-layout-demo/index.html?preset=tensor-core&quot;
 target=&quot;_blank&quot; rel=&quot;noopener&quot; 
style=&quot;display:inline-block; padding:10px 18px; background:#3b82f6; 
color:#fff !important; font-weight:700; border-radius:8px; 
text-decoration:none;&quot;&gt;▶ Open the interactive layout demo 
↗&lt;/a&gt;&lt;/p&gt;
 
 &lt;p&gt;TIRx’s layout interface is built around four design choices.&lt;/p&gt;
 
diff --git a/feed.xml b/feed.xml
index 18cc9a8338b..158f569915d 100644
--- a/feed.xml
+++ b/feed.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?><feed 
xmlns="http://www.w3.org/2005/Atom"; ><generator uri="https://jekyllrb.com/"; 
version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" 
type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" 
/><updated>2026-06-22T03:50:20+00:00</updated><id>/feed.xml</id><title 
type="html">TVM</title><author><name>{&quot;name&quot; =&gt; 
nil}</name></author><entry><title type="html">TIRx: An Open Compiler Stack for 
Evolving Fronti [...]
+<?xml version="1.0" encoding="utf-8"?><feed 
xmlns="http://www.w3.org/2005/Atom"; ><generator uri="https://jekyllrb.com/"; 
version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" 
type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" 
/><updated>2026-06-22T03:55:23+00:00</updated><id>/feed.xml</id><title 
type="html">TVM</title><author><name>{&quot;name&quot; =&gt; 
nil}</name></author><entry><title type="html">TIRx: An Open Compiler Stack for 
Evolving Fronti [...]
 /* Theme has h3=38px but no h2 rule; size both down a notch and keep h2 > h3. 
*/
 .post-content h2 { font-size: 38px; line-height: 1.3; }
 .post-content h3 { font-size: 30px; line-height: 1.3; }
@@ -111,20 +111,9 @@ L(i,j)_{(8,16)} &amp;= L(i\cdot 16 + j) &amp;&amp; 
\text{(flatten)} \\
   <li>owners (×2 via replica): { warpid=6 laneid=12 }, { warpid=10 laneid=12 
}</li>
 </ul>
 
-<p><em>(Click element 57 in the interactive demo below to see exactly these 
owners.)</em></p>
-
-<details>
-<summary>Unfold to see the interactive layout demo</summary>
-<iframe id="tirx-layout-demo" 
src="/assets/tirx-layout-demo/index.html?preset=tensor-core&amp;notitle&amp;lock"
 style="width:100%; height:560px; border:1px solid #dfe1e6; border-radius:10px; 
margin:12px 0;" title="TIRx interactive layout demo: tensor-core tile" 
loading="lazy"></iframe>
-<script>
-window.addEventListener('message', function (e) {
-  var h = e.data && e.data.tirxLayoutDemoHeight;
-  if (!h) return;
-  var f = document.getElementById('tirx-layout-demo');
-  if (f) f.style.height = h + 'px';
-});
-</script>
-</details>
+<p><em>(Open the interactive demo and click element 57 to see exactly these 
owners.)</em></p>
+
+<p><a 
href="https://mlc.ai/modern-gpu-programming-for-mlsys/_static/tirx-layout-demo/index.html?preset=tensor-core";
 target="_blank" rel="noopener" style="display:inline-block; padding:10px 18px; 
background:#3b82f6; color:#fff !important; font-weight:700; border-radius:8px; 
text-decoration:none;">▶ Open the interactive layout demo ↗</a></p>
 
 <p>TIRx’s layout interface is built around four design choices.</p>
 
diff --git a/rss.xml b/rss.xml
index 5cadcf703ee..793332f4a39 100644
--- a/rss.xml
+++ b/rss.xml
@@ -5,8 +5,8 @@
         <description>TVM - </description>
         <link>https://tvm.apache.org</link>
         <atom:link href="https://tvm.apache.org"; rel="self" 
type="application/rss+xml" />
-        <lastBuildDate>Mon, 22 Jun 2026 03:50:20 +0000</lastBuildDate>
-        <pubDate>Mon, 22 Jun 2026 03:50:20 +0000</pubDate>
+        <lastBuildDate>Mon, 22 Jun 2026 03:55:23 +0000</lastBuildDate>
+        <pubDate>Mon, 22 Jun 2026 03:55:23 +0000</pubDate>
         <ttl>60</ttl>
 
 
@@ -125,20 +125,9 @@ L(i,j)_{(8,16)} &amp;amp;= L(i\cdot 16 + j) 
&amp;amp;&amp;amp; \text{(flatten)}
   &lt;li&gt;owners (×2 via replica): { warpid=6 laneid=12 }, { warpid=10 
laneid=12 }&lt;/li&gt;
 &lt;/ul&gt;
 
-&lt;p&gt;&lt;em&gt;(Click element 57 in the interactive demo below to see 
exactly these owners.)&lt;/em&gt;&lt;/p&gt;
-
-&lt;details&gt;
-&lt;summary&gt;Unfold to see the interactive layout demo&lt;/summary&gt;
-&lt;iframe id=&quot;tirx-layout-demo&quot; 
src=&quot;/assets/tirx-layout-demo/index.html?preset=tensor-core&amp;amp;notitle&amp;amp;lock&quot;
 style=&quot;width:100%; height:560px; border:1px solid #dfe1e6; 
border-radius:10px; margin:12px 0;&quot; title=&quot;TIRx interactive layout 
demo: tensor-core tile&quot; loading=&quot;lazy&quot;&gt;&lt;/iframe&gt;
-&lt;script&gt;
-window.addEventListener(&apos;message&apos;, function (e) {
-  var h = e.data &amp;&amp; e.data.tirxLayoutDemoHeight;
-  if (!h) return;
-  var f = document.getElementById(&apos;tirx-layout-demo&apos;);
-  if (f) f.style.height = h + &apos;px&apos;;
-});
-&lt;/script&gt;
-&lt;/details&gt;
+&lt;p&gt;&lt;em&gt;(Open the interactive demo and click element 57 to see 
exactly these owners.)&lt;/em&gt;&lt;/p&gt;
+
+&lt;p&gt;&lt;a 
href=&quot;https://mlc.ai/modern-gpu-programming-for-mlsys/_static/tirx-layout-demo/index.html?preset=tensor-core&quot;
 target=&quot;_blank&quot; rel=&quot;noopener&quot; 
style=&quot;display:inline-block; padding:10px 18px; background:#3b82f6; 
color:#fff !important; font-weight:700; border-radius:8px; 
text-decoration:none;&quot;&gt;▶ Open the interactive layout demo 
↗&lt;/a&gt;&lt;/p&gt;
 
 &lt;p&gt;TIRx’s layout interface is built around four design choices.&lt;/p&gt;
 

Reply via email to