12 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| a2b05c56c9 |
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. |
|||
| 7737096858 |
refactor(makepad-table): adopt Robrix's image decode path
Replaces the hand-rolled try-PNG-then-JPEG with `pageflipnav/src/utils.rs::load_png_or_jpg`, the pattern the Robrix-derived app in this repo already uses. Two things it does better: - It sniffs the header with `imghdr` and calls the matching loader directly, so a JPEG does not decode-and-fail as a PNG first on every cold cache. - It still falls back to trying both when the sniff names something unexpected or nothing at all. `imghdr` is not perfect, and a mislabelled file is more useful decoded than refused. `imghdr` has no transitive dependencies — it reads a header and names a format. It is already a dependency of `pageflipnav` at the same version. The upstream version logs the failure and dumps the bad bytes to disk. That is right for a chat client receiving untrusted media and wrong here: this runs from the draw path for every attached cell, so a broken file would log once per frame. The caller already caches the failure and draws a labelled chip naming the file, which tells the user more than a log line would. Tests 94 -> 99. Verified by removing the sniff and by removing the fallback; each fails the ordering test. `TextOrImage`, the other candidate for reuse, is referenced in `room_screen.rs` but not defined anywhere in this checkout — it is upstream Robrix only, so there was no baseline here to adopt. |
|||
| 01ebeeb7fe |
feat(makepad-table): real image rendering with a resize anchor, and per-row heights
Two things: attached images are decoded and drawn rather than shown as a placeholder chip, and row heights become genuinely per-row. **Image rendering.** Decoding follows `pageflipnav/src/utils.rs`, the Robrix-derived app in this repo: try PNG, then JPEG, because a header sniff is not reliable enough to choose on its own. The decoded texture is cached per cell, and a cache entry of `None` records a file that could not be read so a broken path is attempted once rather than every frame — `draw_walk` runs at 60Hz and re-decoding a photo there would be the slowest thing in the widget by a wide margin. One `Image` widget repositioned per cell, matching `cell_editor` and `math_cell`, with the texture swapped from the cache. A pool would let several textures live at once but needs runtime template instantiation and a reuse policy; this is the same number of GPU uploads with far less machinery. On first successful decode the real pixel size is written back to the attachment, so the row is sized from the true aspect ratio instead of the placeholder guess. **The resize anchor.** A grab square at the image's bottom-right corner. Dragging it writes `ImageSizing::Fixed`, which pins the height so a later relayout cannot overrule what the user chose, and the row follows because `row_height_for` reads the same value. The floor is asserted at compile time against the anchor size: an image dragged smaller than its own grab handle could not be grabbed again, and the user would have to delete the attachment to recover it. The anchor is hit-tested before the cell, or dragging it would open the editor instead. Sizing lives on the attachment rather than in widget state, so it survives a column reorder along with the image. **Per-row heights.** `TableRow::height` holds a dragged override and `row_height_for` honours it. Item 5's row resize previously assigned `self.row_height`, which is table-wide — dragging one row's handle resized every row at once. `RowGeometry`, added with the attachments, now carries the consequence: rows below a resized one shift down. Tests 86 -> 94 (140 across the tree), all five crates clippy-clean. Verified by reintroducing four defects. One of those guards did not work first time and the gap was mine. Deleting the per-row override branch from `row_height_for` left the whole suite green: the tests checked that `TableRow::height` could be *stored*, and nothing checked it was ever *read*. Storing a value no one consults is exactly the shape of "the handle does nothing". `the_row_override_is_actually_consulted` now asserts the connection at both ends — that `row_height_for` reads `row.height`, and that the resize writes it rather than the table-wide field. |
|||
| 3c751f18bd |
feat(makepad-table): cell attachments and per-row heights (item 6)
The last of the nine. Items 1-5, 7, 8 and 9 shipped in |
|||
| bbdfe823f8 |
feat(makepad-table): row gutter, header select/resize, long-press menus, one input model
Items 1, 2, 3, 4, 5, 7 and 9 of the nine reported. Item 8 shipped separately
in
|
|||
| b83e7122c4 |
feat(makepad-table): file picker, search, recents, New/Delete (Invoicer UI Phase 3)
Some checks failed
email.yml / feat(makepad-table): file picker, search, recents, New/Delete (Invoicer UI Phase 3) (push) Failing after 0s
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
The last open phase. The sidebar gains a search box, a filtered document list and a recents list; the toolbar gains New Invoice/Quote/Receipt, Open, Save As and Delete. **The file picker is robius, not makepad's.** Makepad has an `open_system_openfile_dialog`, and it is implemented on macOS only — the Linux and Android backends never handle `CxOsOp::SelectFileDialog`, so the op is queued and dropped. It compiles, it runs, the dialog never appears. That is the worst kind of broken, so this uses `robius-file-picker`, the same crate `nigig-build`, `nigig-pay-ui` and `nigig-sms` already depend on at the same pinned revision, which goes through `rfd` on desktop and the platform picker on Android. CI gates against the macOS-only call returning. The picker's callback runs off the UI thread with no `Cx`, so it parks its outcome in a mutex and signals; `drain_file_picker` applies it on the next `Event::Signal`. Same shape as the SMS bulk CSV import. Model additions, in `makepad-doc-model` so they are testable without a window: `DocKind` with `blank()` constructors, `DocumentLibrary::create`, `remove`, and `selection_after_remove`. Decisions worth naming, because each has a wrong answer that looks fine: - **A new document is empty**, not seeded from the samples. A blank invoice arriving with "Acme Studio LLC" on it invites someone to export it without noticing whose name is there. `issue_date` is blank too — there is no clock in that crate and a guessed date is worse than none. - **Generated numbers cannot collide**, including with documents loaded from disk, and they reuse gaps left by deletions. The number becomes the filename: two documents called INV-1 save over each other and one is lost silently. - **Delete removes the row, not the file.** Removing an entry from a list is not consent to delete a document off disk, and there is no undo here. The status line says the file is untouched. - **Save reports "Choose where to save…", not "Saved."** The dialog being open is not the file being written. - **Search filters on every keystroke**, unlike the header fields, which commit on Return. Every prefix of a query is a valid narrower search; there is no such thing as a half-typed one. - **Searching does not move the selection.** Filtering is a view change, and switching the open document because a letter was typed loses the user's place. - **`selection_after_remove` is separate and exhaustively tested.** Deleting before the selection shifts it, deleting the selection keeps the index unless it was last, deleting after it changes nothing, and emptying the library selects nothing. Every wrong answer silently shows a different document; one of them indexes out of range. The document list is a fixed pool of 12 button slots rather than a `PortalList`, because this app opens documents one at a time. The pool is honest about its limit: anything past it renders as "+n more — narrow the search to reach them" rather than being dropped. Tests 79 -> 90. Six of them are the invoicer's first: `App` derives `Script` and cannot be built outside a live `Cx`, so the sidebar's presentation logic was extracted into four pure functions and tested there. Verified by reintroducing six defects across the two crates — silent overflow, a selection marker that shifts the indent, whitespace counting as a search, colliding numbers, a selection that ignores the shift, and a `blank()` that pre-fills. Also fixed, all pre-existing and all now blocking the `-D warnings` gate that has been running on these crates since the workflow was added: `std::io::Error::new(ErrorKind::Other, _)` in two crates, a manual `RangeInclusive::contains`, a manual `is_multiple_of`, a single-arm `match`, and a duplicated `#[test]` attribute that was annotating one function twice — which is why the count reads 36 rather than 37 here; no test was lost. The sample data keeps its `12_000_00` money literals, where the last group is the minor units and the number reads as "12,000.00" at a glance. `inconsistent_digit_grouping` is allowed at the crate root with that reasoning, rather than regrouping every amount into thousands and making each one need arithmetic to check against its comment. |
|||
| 624d6b846f |
feat(makepad-table): document library for Phase 3, and correct every stale note
Some checks failed
email.yml / feat(makepad-table): document library for Phase 3, and correct every stale note (push) Failing after 0s
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
Two things: the model layer Invoicer UI Phase 3 needs, and a sweep of the documentation, which was still describing the crate as it was six phases ago. `DocumentLibrary` in `makepad-doc-model` — open, save, recents and search, in the model rather than the app so it is testable without a window. The UI over it is not built; the README says so rather than claiming the phase. Three things it gets right that are easy to get wrong: - A document number becomes a filename and is user-controlled text. `safe_file_stem` replaces anything outside `[A-Za-z0-9._-]`, so `../../etc/passwd` cannot steer a write out of its directory. Leading dots go too, and a fully-stripped name falls back to `untitled`. - An empty query matches everything, so clearing the search box restores the list instead of emptying it. All terms must match, so each word narrows. - Recents de-duplicate and move to the front. Without that, re-opening one file fills the list with it and evicts everything else. doc-model tests 22 -> 31. Verified by reintroducing four defects: dropping the filename sanitising fails 3, removing the recents de-duplication fails 1, and switching the search from all-terms to any-term fails 2. The empty-query guard is honestly untested and marked as such below. Two test expectations I wrote were wrong and the code was right, which is worth recording because both look like search bugs and are not. Searching "globex" returns the invoice *and* the receipt — both are addressed to Globex, and finding every document for a client is the point. Searching "inv-2024-001" also returns both, because the receipt's line item reads "Invoice INV-2024-001 — Brand identity + website": it is the payment for that invoice, and surfacing it is the useful answer. Documentation, all of which had drifted: - `src/table.rs` called itself a "Phase 1 + 2 scaffold" with "Phase 3+ (SCAFFOLD ONLY — emits actions, no UI yet)". All six phases are implemented; the header now summarises what each one does. - `src/lib.rs` said Phase 3+ was "scaffolded via TableAction emissions but not yet implemented", and did not export the Phase 6 types at all. `parse_solid_spec`, `wireframe_edges`, `project_isometric`, `SolidSpec`, `SolidSpecError` and `Point3` were public but unreachable from the crate root. - `TableAction::RowMenuRequested` / `ColMenuRequested` were documented as "Phase 3 will open a PopupMenu". The widget opens the menu itself; these are notifications, not requests. - Both demos logged "Phase 3 will open PopupMenu" and neither handled `ColumnMoved`, so a Phase 4 drag produced no output in either. - The invoicer's header described a toolbar of six buttons that does not exist and a context menu as pending. - The README's caveats section listed three "if the compiler complains" predictions from before the crate had ever been built. All three are settled — `KeyCode::Tab` is right, `TextInput` needs no `ComponentRef`, pdf-writer 0.15 compiles as written — so it now lists the five real remaining limitations instead. - The README's workspace tree omitted `examples/table_demo` entirely and described `table.rs` as 1145 lines; it is 3260. The "drop into makepad" instructions were quietly wrong after Phase 5 and are now corrected with verified line numbers. They say to register `Table` next to `chart`, which at the pinned revision is line 611 — but `MathView` registers at 617, and `Table`'s DSL body names `mod.widgets.MathView`. Following the old advice literally would register the widget six lines before the type it depends on. |
|||
| c4b646c1fa |
feat(makepad-table): editable header, currency and tax, doc switcher (Invoicer UI Phase 2)
Some checks failed
email.yml / feat(makepad-table): editable header, currency and tax, doc switcher (Invoicer UI Phase 2) (push) Failing after 0s
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
The invoicer displayed the document number, issue date and due date in three `TextInput`s that were all `is_read_only: true`, because there was nowhere to write an edit back to. This is the write half, plus the currency and tax entry and the sidebar switcher the phase called for. Model (`makepad-doc-model`): - Setters for number, both dates, currency, default tax, issuer and recipient, each trimming its input. A trailing space in a document number becomes a trailing space in the exported filename, and a leading one makes two identical-looking documents sort apart. - `secondary_date`, `set_secondary_date` and `secondary_date_label`, because the second date is a due date on an invoice and a valid-until on a quote, and a receipt has neither. - `Currency::presets()` and `from_code()`. An unknown code is refused rather than turned into an `Other` with a guessed symbol and decimal count, which would format amounts confidently and wrongly. - `TaxRate::parse_percent`. - `switcher_label()`. **A defect this uncovered.** `Document::default_tax()` returned `None` for a receipt, even though `Receipt` carries a `default_tax` field like the other two and its `tax_total_minor()` bills from it. The accessor was the only thing claiming a receipt has no default rate. `document_to_table_data` trusted it, substituted `TaxRate::zero()`, and printed 0% in the Tax column for every un-overridden line while the totals underneath were computed from the real rate — the table and the total disagreeing on the same screen. It never showed because the shipped sample receipt is 0%-rated, so the wrong answer and the right one coincided. It separates as soon as a rate is set, which is exactly what the tax field added here now lets a user do. Fixed at the accessor, so the table builder is corrected without touching it. **A second one.** `TaxRate::percent` casts `f64 -> u32`, and that cast saturates: `percent(-5.0, ..)` is 0%, and so is `percent(f64::NAN, ..)`. Neither refuses, so a user typing nonsense into the new field would have got a plausible-looking rate they did not ask for. `parse_percent` validates first — finite, 0 to 100 — and returns `None` otherwise. Rejected input is reported in the status line and the field is reset to the stored value, so the box never keeps text the document did not accept. UI: - The three header inputs are editable, with a white background and a focus border rather than the read-only grey. - The due-date field used to render "2024-05-01 (valid until)" for a quote — the label baked into the value, so it could not be edited without deleting the annotation. The label is now on the label. - Currency and default-tax fields. - Three sidebar buttons switch document, labelled from the documents themselves, with the current one named below. - Header edits commit on Return or focus loss, not per keystroke: re-reading the model on each character fights the caret, and a half-typed date is not a date. Also fixed, all pre-existing: - `examples/table_demo` did not compile. It used `action.cast::<T>()`, which makepad's Action API no longer has. That package was in no workspace and had no CI until the previous commits, so it never failed loudly — it was simply never built. This is the second defect found purely by putting it somewhere a compiler would look. - Both demos imported `makepad_widgets` alongside `makepad_table`, which re-exports it wholesale, making every widget name ambiguous. - A dead `refresh_totals_display` no-op stub. - Stale "Phase 3 will open PopupMenu" status strings; the menus exist. doc-model tests 12 -> 22, and all five crates now pass `clippy --all-targets -D warnings`. Verified by reintroducing three defects: restoring `None` for a receipt's default tax fails the tax-reporting test, letting `parse_percent` fall through to `percent` fails the validation test, and dropping the trim fails the whitespace test. Invoicer UI Phase 3 (file browser, recent documents, search) remains open. |
|||
| c5eefaaea4 |
feat(makepad-table): 3D cells (Phase 6)
Some checks failed
email.yml / feat(makepad-table): 3D cells (Phase 6) (push) Failing after 0s
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
The last of the README's table phases. `CellKind::Solid3d` reads a short
textual description of a solid from the cell and draws an isometric wireframe
of it:
cube 10 20 30 / cube 10 / sphere 5 / cylinder 3 12
Separators may be spaces or commas, names are case-insensitive, `box` and
`cyl` are aliases. Text in, geometry out — the cell stays a `String`, so a 3D
column saves, loads and round-trips exactly like every other column, and the
kind travels with the column through a drag-reorder.
A wireframe rather than a shaded render, deliberately. Shading needs a 3D
pass with its own camera, depth buffer and lighting shader; the CAD viewport
elsewhere in this repo spends about 2,500 lines on precisely that. A cell
forty pixels tall gains nothing from it. Edges projected isometrically and
stroked with `DrawVector` need no pass of their own, and isometric has no
camera to configure and cannot degenerate. Curved solids are drawn as rings,
not their full triangulation: a 40px cell cannot resolve hundreds of
triangles and stroking them would cost more than the rest of the table.
The projection scales to fit and centres, so a 1-unit and a 1000-unit cube
are drawn identically — without that a cell shows either a dot or nothing.
Degenerate inputs (no edges, an inset larger than the cell, a zero-size cell,
geometry that collapses to a point) return nothing rather than dividing by a
zero span, because `DrawVector` silently drops a path containing NaN and the
cell would just look empty.
Dimensions must be finite and positive, and a rejected spec draws its reason
in the cell — `[unknown shape: torus]`, `[cube wants 3 args, got 2]`. Same
principle as the LaTeX path: an empty cell and a broken one must not look
identical, or a typo reads as a rendering fault.
Tests 26 -> 44. Parsing: each shape, uniform and three-dimension boxes,
aliases, case, comma separators, and every rejection path including NaN and
infinity. Wireframes: a cube has twelve edges and eight corners, extents are
centred, sphere vertices lie on the radius. Projection: fits inside the cell,
is scale-invariant, is centred, stays finite under extreme aspect ratios, and
returns nothing when degenerate.
Verified by reintroducing three defects: dropping dimension validation fails
1 test, a fixed scale instead of scale-to-fit fails 2, and removing the
re-centring fails 1.
That last one is the interesting case, because on the first attempt it failed
*nothing*. Every primitive is built centred on the origin, so the midpoint of
its projection is already zero and subtracting it is a no-op — the centring
tests could not distinguish "centres the drawing" from "happens to be
centred". `an_off_centre_solid_is_still_centred_in_its_cell` translates a
cube well away from the origin first, and that one does fail. A guard that
cannot fail is decoration, and this one could not until it was checked.
|
|||
| 15ef8a0447 |
feat(makepad-table): LaTeX cells (Phase 5)
Some checks failed
email.yml / feat(makepad-table): LaTeX cells (Phase 5) (push) Failing after 0s
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
A column can now be declared as maths rather than text:
TableColumn { kind: CellKind::Latex, ..Default::default() }
Rendering goes through makepad's own `MathView`, which is already registered
with the script VM and already owns a glyph cache and the `makepad-latex-math`
parse/layout path. Reimplementing that inside the table would have been a
second copy of the same logic with none of the same testing.
The kind lives on the column, not the cell. Cells stay `String`, so a
`TableData` built before this existed keeps working and a table still
round-trips through plain text. A column of formulae is also the realistic
case — a spreadsheet does not mix prose and LaTeX down one column — and it
means the decision is made once per column rather than re-derived per cell
per frame. `CellKind` defaults to `Text`, so nothing changes for existing
callers except that the struct gained a field.
One `MathView` is repositioned over each maths cell in turn, the same pattern
`cell_editor` already uses. A widget per cell would allocate a glyph cache per
cell. The walk is `Size::Fit` rather than fixed to the cell, because
stretching a glyph run to fill a cell distorts the maths.
An expression that does not parse is not blanked. `MathView` draws its own
`[reason]` marker, so a mistyped formula is visible in the cell rather than
silently erasing the content — the failure mode that makes a formula column
untrustworthy.
Also extracted `align_text_x`, which was inline in `draw_cells`. It counts
characters rather than bytes; a multi-byte string measured with `len()` is
pushed off the cell entirely. The 7px-per-character approximation is
unchanged and still crude — correcting it needs a real text measurer, not a
different guess — but it is now in one place and under test, so replacing it
will be a visible diff rather than a silent shift.
Tests 21 -> 26. New: kinds default to Text, a Latex column keeps its kind
through a drag-reorder, left alignment ignores content, centre and right
alignment against the stated approximation, and character-vs-byte counting.
Verified by reintroducing two defects: measuring bytes fails the multi-byte
test, and resetting kind during a reorder fails the kind-preservation test.
The invoicer's six columns gained an explicit `kind`. That break was caught
by the `Invoicer and demo compile` step added with the workflow in the
previous commit, which is what it is there for.
|
|||
| ab17c72c55 |
feat(makepad-table): drag-reorder columns, and the first tests this crate has
Some checks failed
repo hygiene / hygiene (push) Has been cancelled
email.yml / feat(makepad-table): drag-reorder columns, and the first tests this crate has (push) Failing after 0s
makepad-table / model (push) Has been cancelled
makepad-table / widget (push) Has been cancelled
makepad-table / hygiene (push) Has been cancelled
Phase 4 of the README's table, plus the test infrastructure Phases 1-3 never
had. The crate had zero tests before this; it now has 21.
Drag-reorder:
- A press on a column header no longer commits to an action. It arms a drag
and resolves on release: travel more than 8px and it reorders, release
without travelling and it opens the column menu as before. Without that
ambiguity resolved, every menu open would jitter into a one-pixel drag.
The threshold matches `TouchTracker::MOVE_THRESHOLD` so a mouse and a
finger agree on what a drag is.
- While dragging, the carried column is tinted full-height and a 2px bar
marks the boundary it would land on. The bar is suppressed when the drop
is a no-op, so no bar means nothing will happen rather than a bar sitting
misleadingly at the source edge.
- `TableAction::ColumnMoved { from, to }` fires only when the index actually
changed, so a host persisting column order is not asked to write on every
wobble. An open cell editor is cancelled, because it addresses a cell by
index and the indices just moved underneath it.
`draw_drag: DrawVector` — declared, never used anywhere — is replaced by two
`DrawColor` layers. `DrawVector` is a full tessellator with path, vertex,
index and paint state; a translucent rectangle and a vertical bar do not
need any of it.
Testability, which needed a structural change rather than a test file:
`Table` derives `Script` and `Widget`, so it has no `Default` and cannot be
constructed without a live `Cx`. Nothing about it was unit-testable. The
logic worth testing does not need a widget, so it moved off it —
`ColumnGeometry` owns boundary and drop-position arithmetic, and a free
`reorder_columns` owns the move. `Table` forwards to both, and
`compute_layout` now goes through `ColumnGeometry` too, so there is one
implementation rather than two that can drift.
The 21 tests cover column geometry at even and uneven widths and at a
non-zero origin, drop-position resolution including the exact-midpoint case
and clamping outside the table, the index shift in both directions, no-op
drops, out-of-range refusal, cells travelling with their header, ragged
rows, a permutation property over repeated drags, and the Phase 3 menu's
geometry and hit-testing.
Verified by reintroducing three defects separately: removing the shift for
the removed source column fails 7 tests, dropping the no-op guard fails 1,
and moving headers without their cells fails 3.
Phase 3 was marked "scaffolds only" in the README and was in fact
substantially complete — menu state, open, hit-test, apply, and drawing all
present, with 15 row and column actions wired. Corrected to done, with its
geometry now under test.
Also adds `.forgejo/workflows/makepad-table.yml`, the first CI this tree has
had. Every step passes `--manifest-path` explicitly: the crate is excluded
from the root workspace, so `-p` from the repo root cannot reach it and
`--workspace` skips it — omitting the flag does not fail loudly, it silently
tests nothing. The workflow gates tests, clippy at `-D warnings` and fmt,
and asserts three invariants that would otherwise regress quietly: that the
exclusion still holds from both sides, that no manifest tracks a git branch
instead of pinning a revision, and that monetary fields stay integer.
Each gate was checked by breaking what it protects. The exclusion check
caught a defect in itself while being tested: a bare grep for the path also
matched the explanatory comment above the exclude list, so deleting the
entry and keeping the comment passed. It now anchors on the quoted entry.
Two pre-existing clippy warnings fixed so the new `-D warnings` gate starts
from zero.
|
|||
| 258fa3259e | Merge origin/main: resolve xref/document conflicts, add makepad_table |