@ashley-shrok/viewmodel-shell 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/tui.js +39 -6
  2. package/package.json +1 -1
package/dist/tui.js CHANGED
@@ -12,6 +12,7 @@ const NO_CTX = {
12
12
  copiedKey: null,
13
13
  focusKey: () => undefined,
14
14
  interactive: false,
15
+ fill: false,
15
16
  draft: () => undefined,
16
17
  };
17
18
  /** OSC 52 clipboard write — the terminal-native analog of the clipboard API.
@@ -987,6 +988,8 @@ function App(props) {
987
988
  copiedKey,
988
989
  focusKey: (o) => map.get(o),
989
990
  interactive,
991
+ fill: fillViewport,
992
+ fillCols: fillViewport ? vp.cols : undefined,
990
993
  draft: (k) => draftFor(k),
991
994
  onFieldChange: (k, v) => setDraft((s) => ({ ...s, [k]: v })),
992
995
  onFieldSubmit,
@@ -1242,25 +1245,55 @@ export class TuiAdapter {
1242
1245
  }
1243
1246
  }
1244
1247
  /** page & section share the 4 layout presets. Information-honest, not pixel. */
1245
- layoutContainer(layout, children, density, rctx) {
1248
+ layoutContainer(layout, children, density, rctx, topLevel = false) {
1246
1249
  const sp = this.spacing(density);
1247
1250
  const kids = (children ?? []).map((c, i) => this.renderNode(c, this.keyOf(c, i), density, undefined, rctx));
1251
+ // 0.4.7 — numeric width ONLY at the page's TOP layout container (it IS
1252
+ // the full terminal width). Everything below fills via Yoga align-stretch
1253
+ // from this numeric anchor (proven on the real-adapter oracle). NESTED
1254
+ // layoutContainer calls (a section's own children) pass topLevel=false ⇒
1255
+ // no width ⇒ they get the correct PANE-relative width via stretch (a
1256
+ // global numeric cols here would overflow a narrower pane). `cards` never
1257
+ // gets it (uniform small-tile grid by design). Not top-level / not
1258
+ // filling ⇒ {} ⇒ byte-identical.
1259
+ const fillW = topLevel && rctx.fillCols ? { width: rctx.fillCols } : {};
1248
1260
  switch (layout) {
1249
1261
  case "split":
1250
- return (_jsx(Box, { flexDirection: "row", gap: sp.gap || 1, children: kids.map((el, i) => (_jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, children: el }, i))) }));
1262
+ return (_jsx(Box, { flexDirection: "row", gap: sp.gap || 1, ...fillW, children: kids.map((el, i) => (_jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, children: el }, i))) }));
1251
1263
  case "cards":
1252
1264
  return (_jsx(Box, { flexDirection: "row", flexWrap: "wrap", gap: sp.gap || 1, children: kids.map((el, i) => (_jsx(Box, { flexGrow: 0, flexShrink: 1, flexBasis: 28, minWidth: 20, children: el }, i))) }));
1253
1265
  case "sidebar": {
1254
1266
  if (kids.length === 0)
1255
1267
  return _jsx(Box, {});
1256
1268
  const [rail, ...rest] = kids;
1269
+ // Deterministic NUMERIC split when filling. `flexGrow` does NOT
1270
+ // distribute reliably past a flexShrink:0 fixed-basis rail (real-
1271
+ // adapter oracle: sidebar stayed 34 at cols 80 AND 160, while `split`
1272
+ // — symmetric flexGrow — scaled). So when filling: fixed numeric rail
1273
+ // + the exact remaining width on the main pane (it then carries the
1274
+ // terminal width to its section via align-stretch). Not filling ⇒ the
1275
+ // ORIGINAL flex props, byte-identical.
1276
+ const cols = topLevel && rctx.fillCols ? rctx.fillCols : 0;
1277
+ const filling = cols > 0;
1278
+ const RAIL = 24;
1279
+ const g = sp.gap || 1;
1280
+ const mw = Math.max(1, cols - RAIL - g);
1257
1281
  if (rest.length === 0) {
1258
- return (_jsx(Box, { flexShrink: 0, flexBasis: 24, minWidth: 18, children: rail }));
1282
+ return filling ? (
1283
+ // flexDirection:column ⇒ the lone section align-stretches to the
1284
+ // numeric width (a default-row box would not width-stretch it).
1285
+ _jsx(Box, { flexDirection: "column", flexShrink: 0, ...fillW, children: rail })) : (_jsx(Box, { flexShrink: 0, flexBasis: 24, minWidth: 18, children: rail }));
1259
1286
  }
1260
- return (_jsxs(Box, { flexDirection: "row", gap: sp.gap || 1, children: [_jsx(Box, { flexShrink: 0, flexBasis: 24, minWidth: 18, children: rail }), _jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, children: _jsx(Box, { flexDirection: "column", gap: sp.gap, children: rest }) })] }));
1287
+ return (_jsxs(Box, { flexDirection: "row", gap: g, ...fillW, children: [filling ? (_jsx(Box, { flexShrink: 0, width: RAIL, children: rail })) : (_jsx(Box, { flexShrink: 0, flexBasis: 24, minWidth: 18, children: rail })), filling ? (
1288
+ // Single numeric-width column directly holding the detail
1289
+ // sections — exactly the `stack` shape the oracle proves scales
1290
+ // (a card section align-stretches to a numeric-width parent).
1291
+ // The extra auto-width wrapper that used to sit here broke that
1292
+ // align-stretch chain (oracle: card detail stuck at 38).
1293
+ _jsx(Box, { flexDirection: "column", gap: sp.gap, flexShrink: 1, width: mw, children: rest })) : (_jsx(Box, { flexGrow: 1, flexShrink: 1, flexBasis: 0, children: _jsx(Box, { flexDirection: "column", gap: sp.gap, children: rest }) }))] }));
1261
1294
  }
1262
1295
  default: // "stack" / undefined
1263
- return (_jsx(Box, { flexDirection: "column", gap: sp.gap, children: kids }));
1296
+ return (_jsx(Box, { flexDirection: "column", gap: sp.gap, ...fillW, children: kids }));
1264
1297
  }
1265
1298
  }
1266
1299
  // ── the node switch ──────────────────────────────────────────────────────
@@ -1269,7 +1302,7 @@ export class TuiAdapter {
1269
1302
  case "page": {
1270
1303
  const d = node.density === "compact" ? "compact" : "comfortable";
1271
1304
  const sp = this.spacing(d);
1272
- return (_jsxs(Box, { flexDirection: "column", gap: sp.gap, children: [node.title ? (_jsx(Box, { children: _jsx(Text, { bold: true, underline: true, children: node.title }) })) : null, this.layoutContainer(node.layout, node.children ?? [], d, rctx)] }, key));
1305
+ return (_jsxs(Box, { flexDirection: "column", gap: sp.gap, ...(rctx.fillCols ? { width: rctx.fillCols, flexGrow: 1 } : {}), children: [node.title ? (_jsx(Box, { children: _jsx(Text, { bold: true, underline: true, children: node.title }) })) : null, this.layoutContainer(node.layout, node.children ?? [], d, rctx, true)] }, key));
1273
1306
  }
1274
1307
  case "section": {
1275
1308
  const sp = this.spacing(density);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashley-shrok/viewmodel-shell",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "A server-driven UI framework where the wire format is structured enough that agents can build full-stack apps without ever opening a browser and all UI tests are pure unit tests with no browser runtime. Server returns a JSON tree of typed nodes; a thin TypeScript adapter renders it to DOM. Backend-agnostic — a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
5
5
  "type": "module",
6
6
  "license": "MIT",