# Vendored Perspective pf_app runs a **patched build of Perspective**. Upstream's C++ engine has always implemented column-axis expand/collapse — `t_ctx2::set_depth(HEADER_COLUMN, …)` and `open`/`close(HEADER_COLUMN, idx)` are fully written — but nothing above C++ could reach it: `set_column_pivot_depth()` was never called, and `View::expand/collapse` hardcoded `HEADER_ROW`. The patch is wiring, not new engine logic. It buys two things the released packages cannot do at all: - `split_by_depth` in `ViewConfig`, the `split_by` counterpart to `group_by_depth` - `expand_column()` / `collapse_column()`, so one column branch can fold to its subtotal while its siblings stay expanded — the Excel behaviour **Source:** https://github.com/fleetside72/perspective, branch `column-axis-expand-collapse`. See `PROVENANCE.txt` for the exact commit these tarballs were built from. ## Why tarballs and not npm The feature is not released upstream. Until it is, the four packages are built from the fork and committed here as npm tarballs. `npm install` expands them exactly as it expands anything from the registry — no special tooling, and `pf.sh deploy` works unchanged. A deploy machine needs node and nothing else: no emscripten, no cmake, no protoc, no Rust. All four move together, never a subset. Perspective couples loader, package versions, data format and `apache-arrow`; vendoring a partial set reintroduces exactly the drift that causes trouble. ## Changing the engine ./rebuild-perspective.sh # builds the fork, repacks, rewrites PROVENANCE.txt cd .. && npm install git add vendor && git commit Push the fork first — the script warns if the source tree is dirty, because a tarball built from uncommitted code has no recoverable source. The build itself needs cmake >= 3.29.5, protoc >= 22 (its version silently selects which protobuf source tree gets cloned), pnpm, and the Rust nightly the repo pins. Roughly 40 minutes cold. Only ever on a machine changing the engine. ## Getting rid of this This is a fork, with the maintenance that implies. The exit is upstream taking the change — the patch is small and additive, and the engine work is already theirs. When a release ships it, delete this directory and put normal version ranges back in `ui/package.json`. ## Pending patch: depth fields on a config update `0001-apply-depth-fields-on-config-update.patch` is not in the vendored tarballs yet. It fixes an upstream bug in `rust/perspective-client/src/rust/config/view_config.rs`: `ViewConfig::apply_update` applies ten fields and **neither `group_by_depth` nor `split_by_depth` is among them**. So a depth can be set when a view is created (`table.view({ group_by_depth: 1 })` — which is what the fork's own `depth_test.mjs` exercises) but never through `restore()`, which is how a viewer changes its own configuration. The field arrives, deserializes, and is discarded before the engine sees it. `group_by_depth` and the omission are both upstream; the fork mirrored `split_by_depth` alongside it faithfully, including the omission. Note the semantics, from `server.cpp`: ```cpp ctx1->set_depth(row_pivot_depth - 1); // one-sided ctx2->set_depth(t_header::HEADER_ROW, row_pivot_depth - 1); // two-sided ``` The config field counts **levels to show**; `view.set_depth()` counts the boundary below them. So `group_by_depth: n` equals `set_depth(n - 1)`, and `Forecast.jsx` sends `d + 1`. To apply: ```bash cd $PSP_DIR # default ~/perspective git apply /path/to/pf_app/ui/vendor/0001-apply-depth-fields-on-config-update.patch ./ui/vendor/rebuild-perspective.sh cd ui && npm install ``` `cargo check` on this patch was clean — the 125 errors it reports without `protoc` installed are unresolved generated protobuf modules, none of them in `view_config.rs`. Until the rebuild lands, `applyDepth()` in `Forecast.jsx` reads the config back after restoring it and falls back to the imperative `view.set_depth()` when the value did not stick. That fallback loses the depth whenever the viewer rebuilds its view; the declarative path does not, which is the point of the patch. The check is cheap and self-clearing — once the engine honours the field, the fallback stops running on its own.