nigig-org/.forgejo/workflows/makepad-table.yml
andodeki a2b05c56c9
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
makepad-table / model (push) Has been cancelled
makepad-table / widget (push) Has been cancelled
makepad-table / hygiene (push) Has been cancelled
feat(makepad-table): opt-in capabilities feature, and raise the matrix_client defect
The two caveats from the dependency investigation.

## The capabilities feature

Camera and location attachments are now available behind
`features = ["capabilities"]`, which pulls `nigig-uikit` and supplies
`UikitAttachmentProvider`.

Measured: 89 crates by default, 275 with the feature on. That cost is real
and it is inherent, not packaging waste. `camera_widget` imports
`send_geocode_request` and `request_map_tile` from `nigig-core`, both of
which call `spawn_async` — the shared Tokio runtime — and the first makes an
HTTPS call to Nominatim. A camera that geocodes needs an async runtime and an
HTTP client; there is no lighter honest version.

It is affordable because it is opt-in, and because any app enabling it
already depends on `nigig-core`, so that app's own tree grows by nothing.

Everything touching `nigig-uikit` is in one module, so the boundary is a file
rather than `#[cfg]` scattered through the widget. The provider holds no
widgets of its own: the host owns the `CameraWidget` already in its tree and
this asks it to open, because a provider that instantiated a second camera
would fight the first for the device.

A second request while one is outstanding is refused rather than overwriting.
The table turns that refusal into `AttachmentUnavailable`, so the user is
told the camera is busy instead of watching their first request vanish.

File picking is deliberately declined here — `robius-file-picker` already
ships unconditionally and costs nothing, and two paths for one job is one too
many.

Two CI gates, both verified to fail when they should: the opt-in build must
keep compiling, and the default build must pull none of `tokio`, `reqwest`,
`hyper`, `clap`, `csv`, `image`, `nigig-uikit` or `nigig-core`. The second
checks the resolved `cargo tree` rather than the manifest, because feature
unification can switch an optional dependency on from a sibling crate.

Tests 99 default, 105 with the feature. Both clippy-clean.

## The matrix_client defect

Raised in REVIEWS/MATRIX_CLIENT_FEATURE_GATE.md rather than fixed. It is not
my crate, nothing depends on the broken combination, and a blind fix could
change behaviour someone relies on.

`matrix_client` declares `native = ["dep:tokio", "dep:reqwest",
"dep:rusqlite"]` but its source gates on `#[cfg(not(target_arch =
"wasm32"))]`. Two switches for the same modules, so on a native target with
the feature off the modules compile and their dependencies do not — 19
errors, 26 ungated uses across 7 files. There is no CI job for the crate,
which is why it rotted unnoticed.

The note corrects an overstatement I made while arguing for the trait hook.
I said fixing this would unblock wasm. It would not: `matrix_client` already
builds clean for wasm32 with `--no-default-features`, and `nigig-core` has 8
wasm errors of its own (`crate::platform::spawn` missing) that have nothing
to do with it. The only broken combination is native-target-with-feature-off,
which nothing builds.

I also said earlier that `matrix_client` was heavy — it is a 7-dependency
local crate, not matrix-sdk. That was wrong and it inflated the case for the
trait hook; the note records the measured numbers instead.
2026-08-18 18:03:26 +00:00

220 lines
9.6 KiB
YAML

name: makepad-table
# CI for the nested `makepad_table` workspace: the Table widget, the shared
# document model, the PDF exporter and the invoicer app.
#
# This crate tree had no CI at all, and it showed. Its README stated it was
# "written without a local cargo/rust toolchain, so the first compile on your
# machine is the verification step" — and that first compile found three
# defects in the money code, two of which made every currency string in the
# application either panic or print the wrong number. None of them survive a
# `cargo test`. This workflow is that `cargo test`, run on every push.
#
# Note the `--manifest-path`. `crates/apps/makepad_table` is named in the root
# manifest's `workspace.exclude`, so it is invisible to every other workflow
# here: `cargo test -p ...` from the repo root cannot reach it and
# `--workspace` does not include it. Each step therefore points at the nested
# manifest explicitly. Dropping the `--manifest-path` does not fail loudly, it
# silently tests nothing, so the last job asserts the exclusion still holds.
on:
push:
paths:
- 'crates/apps/makepad_table/**'
- 'rust-toolchain.toml'
- '.forgejo/workflows/makepad-table.yml'
pull_request:
paths:
- 'crates/apps/makepad_table/**'
- 'rust-toolchain.toml'
- '.forgejo/workflows/makepad-table.yml'
env:
MANIFEST: crates/apps/makepad_table/Cargo.toml
jobs:
# The model and the exporter are UI-free — serde, pdf-writer, ttf-parser —
# so they need no GUI packages and give fast feedback on the arithmetic
# that actually handles money.
model:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Document model tests
run: cargo test --manifest-path "$MANIFEST" -p makepad-doc-model
- name: PDF exporter tests
run: cargo test --manifest-path "$MANIFEST" -p makepad-pdf-export
- name: Clippy on the pure crates
run: |
cargo clippy --manifest-path "$MANIFEST" \
-p makepad-doc-model -p makepad-pdf-export \
--all-targets -- -D warnings
# Money is stored in integer minor units precisely so that no rounding
# error can enter a total. A reviewer adding `f64` to the model is
# unlikely to be told why that is wrong, so the gate says it.
- name: Amounts stay in integer minor units
run: |
set -euo pipefail
MODEL=crates/apps/makepad_table/crates/doc-model/src/lib.rs
if grep -nE '(_minor|amount|price|total|subtotal|discount)[a-z_]*:[[:space:]]*f(32|64)' "$MODEL"; then
echo "FAIL: a monetary field is declared as a float" >&2
echo " minor units are i64 on purpose (see 'Data model highlights')" >&2
exit 1
fi
# The widget needs Makepad's Linux backend to link, which is the slow half.
widget:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install native dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq \
pkg-config libwayland-dev libxkbcommon-dev libx11-dev \
libxi-dev libxcursor-dev libxrandr-dev libgl1-mesa-dev \
libasound2-dev libpulse-dev libssl-dev
- name: Widget tests
run: cargo test --manifest-path "$MANIFEST" -p makepad-table
- name: Widget clippy
run: |
cargo clippy --manifest-path "$MANIFEST" -p makepad-table \
--all-targets -- -D warnings
# The invoicer's sidebar presentation logic is unit-tested; the rest
# of it opens a window, so there is nothing to assert without a
# display. The standalone demo is compile-only for the same reason.
# Compiling both still catches the common break, which is a widget API
# change the consumer was not updated for.
- name: Invoicer tests
run: cargo test --manifest-path "$MANIFEST" -p makepad-invoicer
- name: Invoicer clippy
run: |
cargo clippy --manifest-path "$MANIFEST" -p makepad-invoicer \
--all-targets -- -D warnings
- name: Demo compiles
run: |
cargo check --manifest-path "$MANIFEST" \
-p makepad-example-table --all-targets
# The `capabilities` feature pulls nigig-uikit, which takes the
# dependency tree from 89 crates to 275. That cost is only acceptable
# because it is opt-in, so both halves have to keep working: the
# default build must stay light, and the opt-in build must stay
# buildable. A feature nothing compiles is a feature that rots — which
# is exactly what happened to matrix_client's `native` (see
# REVIEWS/MATRIX_CLIENT_FEATURE_GATE.md).
- name: The capabilities feature still builds
run: |
cargo test --manifest-path "$MANIFEST" -p makepad-table \
--features capabilities
- name: The default build pulls none of the heavy tree
run: |
set -euo pipefail
# `cargo tree` on the default features must not mention any of
# these. Checking the resolved tree rather than the manifest,
# because an optional dependency can still be switched on by a
# sibling crate's feature unification.
leaked=$(cargo tree --manifest-path "$MANIFEST" -p makepad-table \
--prefix none 2>/dev/null \
| awk '{print $1}' | sort -u \
| grep -xE 'tokio|reqwest|hyper|clap|csv|image|nigig-uikit|nigig-core' || true)
if [ -n "$leaked" ]; then
echo "FAIL: the default build pulled the capabilities tree:" >&2
echo "$leaked" >&2
exit 1
fi
echo "default build is clean of the capabilities dependencies"
# makepad's own FileDialog is implemented on macOS only: the Linux and
# Android backends never handle CxOsOp::SelectFileDialog, so a button
# wired to it does nothing at all on the platform this repo targets.
# That failure is invisible — it compiles, it runs, the dialog just
# never appears — so the gate names it rather than trusting a reviewer
# to remember.
- name: The file picker is robius, not makepad's macOS-only dialog
run: |
set -euo pipefail
APP=crates/apps/makepad_table/apps/invoicer/src/main.rs
if grep -nE 'open_system_(openfile|savefile)_dialog' "$APP"; then
echo "FAIL: uses makepad's FileDialog, which is macOS-only" >&2
echo " use robius_file_picker::FileDialog instead" >&2
exit 1
fi
grep -q 'robius_file_picker' "$APP" \
|| { echo "FAIL: the file picker import is gone" >&2; exit 1; }
echo "file picking goes through robius-file-picker"
hygiene:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Formatting
run: |
cargo fmt --manifest-path "$MANIFEST" -- --check \
|| { echo "run: cargo fmt --manifest-path $MANIFEST"; exit 1; }
# Without the exclusion this tree joins every workspace-wide build in
# the repo and drags a second makepad checkout in with it. Cargo also
# declines to absorb it because of its own `[workspace]` table, so this
# asserts both halves: the root says exclude, and the nested manifest
# still declares itself a workspace.
- name: The nested workspace stays excluded from the root
run: |
set -euo pipefail
# Match the quoted exclude entry, not the prose. The first version
# of this check was a bare grep for the path, which the explanatory
# comment above the exclude list also matches — deleting the entry
# and keeping the comment passed the gate. Anchoring on the quotes
# is what makes it able to fail.
if ! grep -qE '^[[:space:]]*"crates/apps/makepad_table",?[[:space:]]*$' Cargo.toml; then
echo "FAIL: root Cargo.toml no longer excludes crates/apps/makepad_table" >&2
exit 1
fi
if ! grep -q '^\[workspace\]' "$MANIFEST"; then
echo "FAIL: $MANIFEST no longer declares its own [workspace]" >&2
exit 1
fi
members=$(cargo metadata --no-deps --format-version 1 --offline 2>/dev/null \
| tr ',' '\n' | grep -c 'makepad_table' || true)
if [ "$members" -ne 0 ]; then
echo "FAIL: makepad_table crates leaked into the root workspace" >&2
exit 1
fi
echo "nested workspace is excluded from the root, and self-declared"
# A floating git branch means the same commit builds against different
# dependencies on different days. Every other crate here pins a rev.
- name: Makepad dependency is pinned, not tracking a branch
run: |
set -euo pipefail
if grep -rn 'branch[[:space:]]*=' crates/apps/makepad_table --include=Cargo.toml; then
echo "FAIL: a manifest tracks a git branch instead of pinning a rev" >&2
exit 1
fi
missing=0
while IFS= read -r manifest; do
if grep -q 'makepad-widgets' "$manifest" && ! grep -q 'rev[[:space:]]*=' "$manifest"; then
echo "FAIL: $manifest depends on makepad-widgets without a rev" >&2
missing=1
fi
done < <(find crates/apps/makepad_table -name Cargo.toml)
[ "$missing" -eq 0 ]
echo "all makepad-widgets dependencies are pinned to a revision"
- name: Reject whitespace errors
run: git diff --check