@cardinal4/opencode-fold-diffs 0.3.9 → 0.3.10

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.
package/README.md CHANGED
@@ -1,31 +1,33 @@
1
1
  # opencode-fold-diffs
2
2
 
3
- > [!NOTE]
4
- > **Unofficial OpenCode V2 port of [Tanner Bruhn](https://github.com/tannerbruhn)'s [`opencode-fold-diffs`](https://github.com/tannerbruhn/opencode-fold-diffs) (npm: `opencode-fold-diffs`) — the OpenCode V1 plugin.** This fork — [`cardin/opencode-fold-diffs`](https://github.com/cardin/opencode-fold-diffs), published as [`@cardinal4/opencode-fold-diffs`](https://www.npmjs.com/package/@cardinal4/opencode-fold-diffs) — targets **OpenCode V2** (`@opencode/cli` 2.x, API-checked against `opencode 2.2.3` and OpenTUI 0.8.x). Install this fork on V2, upstream on V1. Tanner has not reviewed or endorsed it; report bugs here, not upstream.
3
+ > Unofficial OpenCode V2 fork of [opencode-fold-diffs](https://github.com/tannerbruhn/opencode-fold-diffs) by Tanner Bruhn. Install this version on OpenCode V2; use the upstream package on V1.
5
4
 
6
- **Every `write`, `edit` and `apply_patch` dumps the whole file or the whole diff into your transcript, and stays there.** This plugin folds those blocks down to their header line — `# Wrote 40 lines · click to expand src/app.ts` — and opens them again on click, or with a fold/unfold-all key if you configure one.
5
+ OpenCode already collapses read/search groups and trims bash output, but it leaves `write`, `edit` and `apply_patch` blocks fully expanded — the blocks that fill the transcript and makes it hard to navigate.
7
6
 
8
- V2 already collapses the tools it considers noisy: `read` / `glob` / `grep` fold into a one-line exploration group (`Explored: 3 reads, 2 searches`), and a bash **command** trims to two lines with its **output** to ten, all click-to-expand. The three tools that actually fill the scrollback — `write`, `edit`, `apply_patch` — are the ones it leaves alone. Upstream has been asked three times — [#9089](https://github.com/anomalyco/opencode/issues/9089) (minimal diff display), [#14511](https://github.com/anomalyco/opencode/issues/14511) (a toggle keybind, like Claude Code's `ctrl+o`), [#19074](https://github.com/anomalyco/opencode/issues/19074) (collapse tool output) — and all three were closed without a setting. This is that setting, from outside.
7
+ This plugin folds them to one header line and opens them again on click.
9
8
 
10
9
  ## What you see
11
10
 
12
- Before:
11
+ The plugin can fold four kinds of content — three file-changing tool blocks plus the optional bash command:
13
12
 
14
- ```
15
- ← Edit src/session/index.ts
16
- │ 1 import { createMemo } from "solid-js"
17
- │ 2 - const [expanded, setExpanded] = createSignal(false)
18
- │ 3 + const [expanded, setExpanded] = createSignal(props.open)
19
- │ … 40 more lines
20
- ```
13
+ | OpenCode tool | Header in the transcript | What the block contains |
14
+ |---|---|---|
15
+ | `write` | `# Wrote 35 lines · click to expand` | The complete contents of a written file. |
16
+ | `edit` | `← Edit +12 −3 · click to expand` | A unified diff for an existing file. |
17
+ | `apply_patch` | `← Patched +8 −2 · click to expand` (plus `# Created`, `# Deleted` and `# Moved` for add/delete/move patches) | One or more patches, often across several files. |
18
+ | `bash` (optional) | The `$ command` line | Only the command folds, and only with `bash: true`; the output is left to OpenCode. |
21
19
 
22
- After:
20
+ The line/diff count and “click to expand” suffix are added by this plugin, so they disappear when `stats: false`. For Bash there is no stats suffix: only the command can fold, and the Bash output is never touched.
23
21
 
24
- ```
25
- ← Edit +12 −3 · click to expand src/session/index.ts
26
- ```
22
+ Click the header to open the block. Set the optional `key` option to add a shortcut that folds or unfolds every block at once.
27
23
 
28
- Click the row to open it. If you set the `key` option, that binding folds or unfolds every block in the session and sets what newly arriving blocks do — same as a verbose toggle. By default there is no binding, so OpenCode's own shortcuts (including `ctrl+o`) are left alone.
24
+ A `write` block, an `edit` block and an optional `bash` command, all folded:
25
+
26
+ ![Folded write, edit and bash blocks](docs/folded-write-edit-bash.png)
27
+
28
+ Clicking the edit opens the whole diff while the write block above stays folded:
29
+
30
+ ![Expanded edit block showing the diff](docs/expanded-edit-diff.png)
29
31
 
30
32
  ## Install
31
33
 
@@ -33,18 +35,19 @@ Click the row to open it. If you set the `key` option, that binding folds or unf
33
35
  opencode plugin add @cardinal4/opencode-fold-diffs
34
36
  ```
35
37
 
36
- That installs the package and adds it to the global `opencode.json`. The package ships a server entry (`index.js`), a portable RPC bridge (`rpc.js`) and a TUI entry (`tui.js`); the TUI part loads automatically with the server entry, so no second configuration is needed.
37
-
38
- If the terminal connects to a **remote server**, list the package in `~/.config/opencode/cli.json` (or `$XDG_CONFIG_HOME/opencode/cli.json`) instead so the local TUI loads the TUI part. Do not list it in both files:
38
+ Or add it manually to `~/.config/opencode/cli.json`:
39
39
 
40
40
  ```json
41
41
  {
42
- "$schema": "https://opencode.ai/v2/cli.json",
43
42
  "plugins": ["@cardinal4/opencode-fold-diffs"]
44
43
  }
45
44
  ```
46
45
 
47
- Options can go on the plugin entry in either file:
46
+ Use one or the other, then restart OpenCode.
47
+
48
+ ## Options
49
+
50
+ Put options on the same plugin entry that lists the package:
48
51
 
49
52
  ```json
50
53
  {
@@ -61,87 +64,36 @@ Options can go on the plugin entry in either file:
61
64
  }
62
65
  ```
63
66
 
64
- OpenCode 2.2.3 gives an `opencode.json` server plugin its options but drops them from the TUI entry it derives from the same listing. The server part therefore publishes its options over portable RPC, and the TUI asks for them at startup. If that bridge is unavailable (an older host, or a remote server that never registered it), `cli.json` remains the only source the TUI can read; **Fold diffs: diagnose** reports the effective option object either way. When both files set a key, `cli.json` wins.
65
-
66
- Restart OpenCode afterwards; plugins load at startup.
67
-
68
- ### From a local checkout
69
-
70
- Clone this repository and point the plugin entry at the checkout. The package ships a server entry (`index.js`) and a TUI entry (`tui.js`); OpenCode discovers a plugin directory by its server entry and loads the `tui` entry beside it, so the plugin shows up by id instead of as an anonymous entry.
71
-
72
- ```json
73
- {
74
- "plugins": ["/home/me/projects_l/opencode-fold-diffs"]
75
- }
76
- ```
77
-
78
- ## Options
79
-
80
- ```json
81
- "plugins": [{ "package": "@cardinal4/opencode-fold-diffs", "options": { "lines": 3, "min_lines": 10, "key": "ctrl+f" } }]
82
- ```
83
-
84
67
  | Option | Default | Meaning |
85
68
  |---|---|---|
86
- | `lines` | `0` | Lines of the body left showing when folded. `0` is the header only, and also tightens the block's padding so it occupies one row. Any positive number leaves a peek at the top. |
87
- | `min_lines` | `6` | Blocks with fewer changed lines than this are left alone — a two-line edit is already its own summary. |
88
- | `stats` | `true` | Append `+12 −3 · click to expand` to the header. |
89
- | `folded` | `true` | Whether blocks start folded. `false` gives you only the toggle. |
90
- | `key` | `""` | Optional binding for fold/unfold-all. Empty by default so no OpenCode shortcut is overridden; set e.g. `"ctrl+shift+d"` to opt in. |
91
- | `bash` | `false` | Fold long bash commands too. Off by default on V2 because the host already trims them to two lines. |
92
- | `bash_lines` | `1` | Rows of the command left showing when folded. `1` keeps the line that says what the thing was. |
93
- | `dump` | `""` | Where **Fold diffs: diagnose** writes its tree dump. `""` means `/tmp/opencode-fold-diffs-tree.txt`; `false` disables the dump. |
94
-
95
- > `ctrl+o` belongs to OpenCode's **Open recent sessions and projects** (`open.menu`), so this plugin does not bind it. Choose an unused key for the `key` option.
96
-
97
- ## What it does not touch
69
+ | `lines` | `0` | Lines of the body left visible when folded. `0` is the header only; a positive number leaves a peek. |
70
+ | `min_lines` | `6` | Leave blocks with fewer changed lines expanded. Set `0` to fold every file block. |
71
+ | `stats` | `true` | Append the line or diff count and “click to expand” to the header. |
72
+ | `folded` | `true` | Whether new blocks start folded. `false` gives you only the toggle. |
73
+ | `key` | `""` | Optional shortcut for fold/unfold-all. Empty by default so no OpenCode shortcut is overridden. |
74
+ | `bash` | `false` | Also fold long bash commands. Off by default because OpenCode already trims them. |
75
+ | `bash_lines` | `1` | Rows of the command left visible when bash folding is on. Does not affect the output. |
98
76
 
99
- - **Permission dialogs.** The diff you approve renders in full, always. The plugin only ever walks inside the transcript scrollbox, and the permission preview is not in it.
100
- - **Diagnostics and errors.** Only the children carrying a diff or a file body get folded, so an edit that introduced a type error still says so with the block closed.
101
- - **Bash output, and the click that expands it.** Only the *command* is ever folded, and only when `bash` is enabled; the output keeps the host's ten-line collapse. The block's own click handler belongs to the host, so the plugin attaches to the command text instead and calls `stopPropagation()`: clicking the command folds the command, clicking anywhere else in the block does exactly what it did before.
102
- - **Todos, questions, and the generic fallback.** They keep the host's own collapse behaviour.
77
+ ## What stays untouched
103
78
 
104
- ## How it works, for anyone extending it
79
+ - **Small changes.** Blocks below `min_lines` stay expanded on purpose; a two-line edit is already its own summary.
80
+ - **Permission dialogs and diagnostics.** The diff you approve renders in full, and errors from an edit stay visible while the block is folded.
81
+ - **Other tools.** `read`, `glob`, `grep`, todos, questions and the generic fallback keep OpenCode's behaviour.
82
+ - **Bash output.** Only the command can fold when `bash` is enabled; the output and its “earlier lines” summary are untouched.
105
83
 
106
- The V2 TUI plugin API (`@opencode/plugin/tui`) has no slot for message parts, so a plugin cannot render a tool block itself. It has to reach the renderables the host already made:
84
+ ## Troubleshooting
107
85
 
108
- 1. **Find the transcript.** Candidates are every scrollbox with a bottom sticky start, excluding the shell output viewer, diff help and execute detail panels by id. They are scored, not taken first: a `session-part:` descendant is the strongest evidence, a `msg_` id is next, and scroll height breaks ties. A scrolled-up transcript that reports `stickyScroll=false` is still accepted when it has the bottom anchor plus session-part evidence. The cached transcript is dropped when its route/session changes or when it is detached from the renderer root.
109
- 2. **Find the blocks.** A V2 `BlockTool` renders its header first as a row box whose first two children are the label text (`# Wrote`, `← Edit`, `← Patched`, `# Created`, `# Deleted`) and the path value. File blocks are matched on that label; a bash block carries no header, so it is found by shape — the command row's first text node is the `$ `-prefixed command. A running command (a spinner box before any text) is skipped and picked up by a later sweep; output that happens to begin with `$ ` is never mistaken for the command.
110
- 3. **Fold.** Set `visible = false` on the children carrying the bulk. That sets Yoga `display: none`, so the body leaves layout instead of leaving a hole. A zero `max-height` alone is not enough — the box collapses to zero rows but OpenTUI still paints the diff. A positive `lines` value keeps the first body visible and uses `maxHeight` for it instead. The block's own chrome (`gap`, `paddingTop`, `paddingBottom`) is tightened to zero so a folded block occupies one row, and the values read from OpenTUI 0.8's `getGap`/`getPadding` getters are restored exactly when it unfolds. `maxHeight` is captured the same way; bash commands fall back to the host's two-row clamp when the getter is unavailable.
111
- 4. **Toggle.** Assign `onMouseUp` on the block. The solid adapter sets that as a plain property, so a plugin can set it the same way — it replaces `BlockTool`'s own handler, which is why the copy-on-select guard is reimplemented here. For a bash block the host's handler is the output toggle and must be preserved, so the handler goes on the command text with `stopPropagation()` instead. The handler reference is stored and cleared on cleanup, and a `disposed` guard makes a late click a no-op.
112
- 5. **Restate the header.** The stats suffix is appended to the label text node, not the path. If solid will not let go of that node the plugin stops trying; a block whose suffix was already applied still has it stripped on cleanup/unfold even after that global flag is off. Known blocks are re-summarised on each sweep, so a block whose streamed content grew gets fresh stats instead of keeping its first count.
86
+ After restarting, `/plugins` should list `opencode-fold-diffs`, and `Ctrl+P` should show **Fold / unfold file diffs**.
113
87
 
114
- Renderable classes are matched by duck-typing (`typeof node.diff === "string"` for a diff, a string `content` **plus** `getLineSources` for a written file) rather than `instanceof`, because the opentui classes are minified in the shipped binary and their names are not stable. The second half matters in OpenTUI 0.8: `MarkdownRenderable` also has a string `content`, and matching on content alone mistook markdown for a written file. `filetype` is deliberately not required: it was dropped in 2.0.16 and restored in 2.0.27, and keying off it silently stopped write blocks from folding.
88
+ If blocks do not fold, run **Fold diffs: diagnose** from the command palette. The toast reports whether the transcript and blocks were found; include that output in a bug report. Its optional dump can contain prompts or tool output — redact it before sharing, or set `dump: false` to skip it.
115
89
 
116
- Blocks are re-scanned on V2's `session.tool.called`, `session.tool.progress`, `session.tool.success` and `session.tool.failed`, plus `session.step.ended` and `session.execution.succeeded` (`session.shell.ended` joins them when `bash` folding is on). A 5 s fallback sweep catches a session opened from history, whose parts land before any event this plugin sees. The fallback is much slower than the old 2 s timer because it exists only for that initial batch; live updates are event-driven. `session.message.content.updated` is not in the public TUI event union used by the plugin API, so it is not subscribed to.
90
+ ## Development
117
91
 
118
- ## Status
119
-
120
- Written against **opencode v2.0.24**, re-checked against **v2.0.27** and **v2.2.0**, and API-checked against **v2.2.3** / OpenTUI 0.8.x; the `session.*` events it listens to exist from v2.0.10 on, which is the `engines.opencode` floor (`>=2.0.10`). The tree-walking, block matching, fold/unfold and toggle logic run green against a mock renderer tree shaped like V2's (`node --test`), and the fold was checked against a real transcript: `visible = false` (Yoga `display: none`) is what actually hides a body, not `maxHeight`.
121
-
122
- To confirm it loaded, restart (`opencode service restart`) and check `/plugins` lists `opencode-fold-diffs` and `Ctrl+P` → **Fold / unfold file diffs** is in the palette. If nothing folds, run **Fold diffs: diagnose** — the toast reports what the plugin can see:
123
-
124
- | Result | Meaning |
125
- |---|---|
126
- | `transcript: not found` | No session view is open, so there is nothing to fold. |
127
- | `transcript: yes · blocks: 0` | The transcript was found but no file blocks matched — the host render tree differs from what this plugin expects. |
128
- | `transcript: yes · blocks: N · folded: N` | Detection and folding ran; if the blocks still look expanded, the fold did not take on the renderables. |
129
- | `stats: off` | Folding works but the header could not be restated (Solid owns the label node). |
130
- | `options: {...}` | The effective merged options. Compare this with the entry in `opencode.json`/`cli.json`; if it is empty and you configured options in a remote `cli.json`, the local TUI never saw them. |
131
-
132
- The diagnose dump includes up to 80 characters of text from each renderable, so it can contain prompts or tool output. Redact it before sharing; set `dump: false` to skip writing it.
133
-
134
- Edits with fewer than `min_lines` changed lines (default 6) are left expanded on purpose — a one-line change is already its own summary. Set `min_lines: 0` to fold every file block.
135
-
136
- ## V2 port notes
137
-
138
- The port follows V2's new plugin API and render tree, so the V1 code could not be reused:
92
+ ```sh
93
+ node --test
94
+ ```
139
95
 
140
- - Entrypoint is a `{ id, setup(context) }` definition; `setup` returns the cleanup function instead of `api.lifecycle.onDispose`.
141
- - `api.renderer` → `context.renderer`, `api.route.current.name` → `context.ui.router.current().type`, `api.event.on` → `context.data.on`, `api.ui.toast` → `context.ui.toast.show`, `api.keymap.registerLayer` → `context.keymap.layer` (called straight from `setup` on 2.0.23+, from an `app`-slot component before that).
142
- - Event names are V2's `session.*` events; `message.updated` / `message.part.updated` are legacy schemas that never fire.
143
- - Tool headers are a label node plus a path node, so detection and the stats suffix target the label.
144
- - `bash` defaults to `false` because V2 trims long commands itself, and config moves from `tui.json` to `opencode.json` (bridged to the TUI by `rpc.js`) or `cli.json` for a remote TUI (which the TUI reads directly and which wins on conflicts).
96
+ To try a checkout, point a local `opencode.json` plugin entry at this directory and restart OpenCode.
145
97
 
146
98
  ## License
147
99
 
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardinal4/opencode-fold-diffs",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "description": "Unofficial OpenCode V2 fork of opencode-fold-diffs: write, edit and apply_patch blocks render folded to their header line, click to expand.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "index.js",
26
26
  "tui.js",
27
27
  "rpc.js",
28
+ "docs",
28
29
  "README.md",
29
30
  "LICENSE"
30
31
  ],
package/tui.js CHANGED
@@ -89,6 +89,10 @@ const SWEEP_MS = 5000;
89
89
  const EDGE_TOP = 1;
90
90
  const EDGE_BOTTOM = 3;
91
91
  const GUTTER_ROW = 1;
92
+ const UNIT_UNDEFINED = 0;
93
+ const UNIT_POINT = 1;
94
+ const UNIT_PERCENT = 2;
95
+ const UNIT_AUTO = 3;
92
96
 
93
97
  // Scrollboxes that belong to the host's panels and dialogs rather than to the
94
98
  // transcript. The shell viewer is the important one: since V2 it is also a
@@ -162,6 +166,65 @@ function isDiffHeader(line) {
162
166
  return line.slice(4).trim().length > 0;
163
167
  }
164
168
 
169
+ const HUNK_HEADER = /^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/;
170
+
171
+ // Count added/removed lines with hunk ranges. That is what makes a `---` or
172
+ // `+++` line unambiguous: inside a hunk it is content, outside it is a file
173
+ // header. A pair at the top of a diff without hunk headers is still treated as
174
+ // the file header, but later pairs are counted as the content they are.
175
+ function countDiffLines(text) {
176
+ let added = 0;
177
+ let removed = 0;
178
+ let oldLeft = 0;
179
+ let newLeft = 0;
180
+ let headerSeen = false;
181
+ let hunkSeen = false;
182
+ const lines = text.split("\n");
183
+ for (let i = 0; i < lines.length; i++) {
184
+ const line = lines[i];
185
+ const hunk = HUNK_HEADER.exec(line);
186
+ if (hunk) {
187
+ hunkSeen = true;
188
+ oldLeft = hunk[1] === undefined ? 1 : Number(hunk[1]);
189
+ newLeft = hunk[2] === undefined ? 1 : Number(hunk[2]);
190
+ continue;
191
+ }
192
+ if (oldLeft > 0 || newLeft > 0) {
193
+ if (line.startsWith("\\")) continue;
194
+ if (line.startsWith("+")) {
195
+ added++;
196
+ if (newLeft > 0) newLeft--;
197
+ } else if (line.startsWith("-")) {
198
+ removed++;
199
+ if (oldLeft > 0) oldLeft--;
200
+ } else {
201
+ if (oldLeft > 0) oldLeft--;
202
+ if (newLeft > 0) newLeft--;
203
+ }
204
+ continue;
205
+ }
206
+ // Outside a hunk: the first ---/+++ pair is a file header. Later pairs
207
+ // need a completed hunk to prove they are a new file rather than content
208
+ // in a headerless diff.
209
+ const next = lines[i + 1];
210
+ if (
211
+ (!headerSeen || hunkSeen) &&
212
+ line.startsWith("--- ") &&
213
+ isDiffHeader(line) &&
214
+ next &&
215
+ isDiffHeader(next) &&
216
+ next.startsWith("+++ ")
217
+ ) {
218
+ headerSeen = true;
219
+ i++;
220
+ continue;
221
+ }
222
+ if (line.startsWith("+")) added++;
223
+ else if (line.startsWith("-")) removed++;
224
+ }
225
+ return { added, removed };
226
+ }
227
+
165
228
  // The command text of a bash block, or nothing. V2 wraps the command and its
166
229
  // output in one box. The command is the first text-bearing child of that box;
167
230
  // only an empty <Show> placeholder may precede it.
@@ -329,30 +392,9 @@ function summarise(nodes) {
329
392
  if (!node || node.isDestroyed) continue;
330
393
  if (isDiff(node)) {
331
394
  diffs++;
332
- const lines = node.diff.split("\n");
333
- for (let i = 0; i < lines.length; i++) {
334
- const line = lines[i];
335
- // A file header is the marker plus a path, and the --- / +++ pair is
336
- // what distinguishes it from a content line that merely starts with
337
- // the same characters.
338
- if (isDiffHeader(line)) {
339
- const previous = lines[i - 1];
340
- const next = lines[i + 1];
341
- if (line.startsWith("--- ") && next && isDiffHeader(next) && next.startsWith("+++ ")) {
342
- continue;
343
- }
344
- if (
345
- line.startsWith("+++ ") &&
346
- previous &&
347
- isDiffHeader(previous) &&
348
- previous.startsWith("--- ")
349
- ) {
350
- continue;
351
- }
352
- }
353
- if (line.startsWith("+")) added++;
354
- else if (line.startsWith("-")) removed++;
355
- }
395
+ const stats = countDiffLines(node.diff);
396
+ added += stats.added;
397
+ removed += stats.removed;
356
398
  continue;
357
399
  }
358
400
  lines += contentLines(textValue(node) ?? "");
@@ -367,32 +409,58 @@ function summarise(nodes) {
367
409
  // actually set instead of assuming BlockTool's values, then put it back when a
368
410
  // block unfolds or the plugin is disabled.
369
411
 
370
- function readStyle(node, getter, args, property, fallback) {
412
+ const NO_STYLE = Symbol("no-style");
413
+
414
+ // OpenTUI 0.8 getters return a Yoga Value ({ unit, value }); the matching
415
+ // setters accept only a number, a percentage string, or undefined. Assigning
416
+ // the Value object back would be silently ignored, so unwrap it before it is
417
+ // stored as the host's original style.
418
+ function toStyleValue(value) {
419
+ if (value && typeof value === "object" && "unit" in value && "value" in value) {
420
+ switch (value.unit) {
421
+ case UNIT_POINT:
422
+ return value.value;
423
+ case UNIT_PERCENT:
424
+ return `${value.value}%`;
425
+ case UNIT_AUTO:
426
+ return "auto";
427
+ default:
428
+ return undefined;
429
+ }
430
+ }
431
+ return value === null ? undefined : value;
432
+ }
433
+
434
+ function readStyle(node, getter, args, property) {
371
435
  try {
372
436
  if (typeof node?.[getter] === "function") {
373
- const value = node[getter](...args);
374
- if (value !== undefined) return value;
437
+ const raw = node[getter](...args);
438
+ if (raw !== undefined) return toStyleValue(raw);
375
439
  }
376
440
  } catch {}
377
441
  try {
378
- const value = node?.[property];
379
- if (value !== undefined) return value;
442
+ const raw = node?.[property];
443
+ if (raw !== undefined) return toStyleValue(raw);
380
444
  } catch {}
381
- return fallback;
445
+ return NO_STYLE;
382
446
  }
383
447
 
384
448
  function maxHeightOf(node, fallback) {
385
- const value = readStyle(node, "getMaxHeight", [], "maxHeight", fallback);
386
- // "auto" is the unset value in Yoga. For a shell command the host's real
387
- // clamp (two rows) is the better restore point than an explicit auto.
388
- return value === "auto" ? fallback : value;
449
+ const value = readStyle(node, "getMaxHeight", [], "maxHeight");
450
+ // "auto" and an unset Yoga value are both better represented by the caller's
451
+ // restore point than by a value the setter cannot apply back.
452
+ return value === NO_STYLE || value === undefined || value === "auto" ? fallback : value;
389
453
  }
390
454
 
391
455
  function chromeOf(node) {
456
+ const gap = readStyle(node, "getGap", [GUTTER_ROW], "gap");
457
+ const paddingTop = readStyle(node, "getPadding", [EDGE_TOP], "paddingTop");
458
+ const paddingBottom = readStyle(node, "getPadding", [EDGE_BOTTOM], "paddingBottom");
392
459
  return {
393
- gap: readStyle(node, "getGap", [GUTTER_ROW], "gap", 1),
394
- paddingTop: readStyle(node, "getPadding", [EDGE_TOP], "paddingTop", 1),
395
- paddingBottom: readStyle(node, "getPadding", [EDGE_BOTTOM], "paddingBottom", 1),
460
+ gap: gap === NO_STYLE ? 1 : gap === "auto" ? undefined : gap,
461
+ paddingTop: paddingTop === NO_STYLE ? 1 : paddingTop === "auto" ? undefined : paddingTop,
462
+ paddingBottom:
463
+ paddingBottom === NO_STYLE ? 1 : paddingBottom === "auto" ? undefined : paddingBottom,
396
464
  };
397
465
  }
398
466