This repository was archived by the owner on Oct 20, 2026. It is now read-only.
Repository navigation
Fix the films' paged.data bugs: record numbering, merge frame formatting, stepper, script merge (DATA-1..4) - #4
Merged
Merged
Conversation
An unindexed EAN-13 barcode bound to `ean` over the films' product list (`SELECT * FROM catalogue`) encoded record 20's EAN (2000000210209, "Bookmark set") instead of the first row's (2000000210018), every run. Cause: every result was numbered by content (`stable_order` with no keys sorts by every column), so "record 0" was the alphabetically first product. Data Merge, the Dataset preview and InDesign number records in the order the data arrives, so the stepper's record 1, an unindexed binding and the merge's first page disagreed. Fix: record N is the Nth record as delivered when the query DEFINES its order, and the Nth by content only when it does not (spec 6.1): - data-query: `result_order_is_defined(sql)`, a conservative lexical reading of DuckDB's order-preservation rules: a top-level ORDER BY, or a scan of one table through order-preserving clauses (WHERE, LIMIT, OFFSET, UNION ALL, a derived table or CTE over such a scan; DuckDB keeps insertion order, `preserve_insertion_order` is its default). Joins, grouping, DISTINCT, other set operations, windows with a partition or order, sampling, subqueries outside FROM and an empty statement leave the order undefined. `delivered_order` sorts by group keys only, ties kept. - data-bind: the engine keeps per query whether its order is defined; `record_row` and the cached orders (tables, flows, rules) follow it; `record_order` exposes the numbering. - data-automation / data-js: batch plans use the session's numbering (`plan_batch_ordered`), so a unit's records are the stepper's records. DP-4 still holds where it applies: the same rows delivered in another order are no change for a query whose order is undefined. Tests: data-conformance/tests/record_order.rs (failed on origin/main: 2000000210209 vs 2000000210018; table and stepper in content order; ORDER BY ignored) and data-query order.rs unit tests. automation.rs keeps its content-order assertions over a join and adds the scan case; the pipeline Part C record 0 of a scan is its first delivered row; the W1/W3/W6 sort pins drop 1 -> 0 (nothing is sorted for a scan). ADR 553 amended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
…emove)
Found while proving DATA-1 end to end: stepping the preview of a lowered
barcode never changed its bars. The update-in-place batch removed the old
modules first and then drew the new ones; real core (protocol 71) refuses
that batch ("batch failed at index 30: position 30 out of range for parent
Spread", insertPath after deleteFrames of the same spread's items), the
refusal was only a log line, and the old symbol stayed.
Fix: the new modules go first, the removals follow, still one batch and one
undo step; the label still lands on the last new module ($created). The
warning now carries core's error.
Test: relower-real-core "a barcode lowered twice leaves one symbol" checked
only the module count, which a refused batch also keeps; it now compares
the bars and requires record 1's symbol to replace record 0's (failed with
the old order).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
Data Merge dropped the template frame's fill and inset: merged frames came out with `frameFillColor` null and `frameInsetSpacing` 0 against `Color/u4` and [14, 16, 14, 16] on the template. Cause: merged frames are minted fresh (core cannot copy a frame whose story holds a placeholder hyperlink) and only the STORY-level formatting was copied onto them; nothing read or wrote the frame's own properties. Fix: the template read (`readMergeTemplate`) also reads each template frame's properties and keeps every one the frame sets — object style first, then fill, tint, stroke, inset, text frame options, corners, effects, wrap — except where the frame sits, what it is threaded to, its layer, lock and visibility (`isCopiedFramePath`). Values a fresh frame already has (InDesign's `[Normal Text Frame]`, one column, no wrap, zero gradient angles, …) are skipped, so a plain template costs no op per record. The writer sets them on each merged text frame and image rectangle in the same batch (still one undo step). The extra read is paid for: the session now reads the pages and the scene tree once for the template and the re-merge plan (they were read twice). W8: host calls 18 -> 17, reads 12 -> 11, mutation ops unchanged (403). Tests: merge.test.ts (pure: the props per merged frame; what counts as set); the real-core proof is test/films-real-core.spec.ts (next commits). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
Data Merge could not be started from a script: the menu ran it through the
plain command `media.paged.data.command.mergeRecords`, but scripts reach
typed commands (`paged.invoke`, the object model), and the data plugin's
typed commands had refresh, apply and the data-set verbs, not the merge.
Fix: `media.paged.data.merge`, a typed command (manifest objectModel +
object-model.ts) with the merge options flat and optional (`query`, `mode`
single|multiple, `arrange`, spacings, `removeBlankLines`, `template`
keep|consume, `destination` current|newDocument, `pageId`,
`documentName`). It and the menu command both call `runMergeCommand`
(src/merge-command.ts): the first query by default, the Bindings panel
opened when there is none. A script gets `{ ok, records, pages, overset,
message, diagnostics }`.
Test: test/films-real-core.spec.ts DATA-4 (next commit) lists the command
on host.objects and merges through `h.objects.invoke`, with options and
with none (the menu's defaults).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
…t (DATA-3)
The record-preview stepper started disabled ("— / —") until a number was
typed into "jump to", and switching to the Dataset preview tab and back
disabled it again; the Data query panel's Preview stayed disabled after
Read columns.
Cause: the stepper's record total and position were panel state starting
at 0, set only by stepping, so every mount (a tab switch remounts the
panel) began at "— / —". Preview and Save were enabled only by typed SQL,
while the field showed the source's rows as a placeholder.
Fix:
- the session keeps the records each query delivered (`getState().records`,
set from the delivered batch, no engine call) and the stepped-to record
(`getState().preview`, `setPreviewPosition`); refreshData emits, so the
panels see new counts. The stepper renders from the session: "1 / N" as
soon as the query has run, and its place survives a remount.
- before the query ran, "next ›" runs it (as Data Merge does) and shows
record 1.
- Data query: with no SQL typed, Preview and Save run what the field
shows, the source's rows (`buildSql({ source })`, also the placeholder).
Tests: panels.test.ts (opens at "1 / 24"; keeps "2 / 24" across a
remount; next runs the data), query-panel.test.ts (Preview after Read
columns runs SELECT * FROM "catalogue"), preview.test.ts (the session
keeps counts and position); all failed before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
…DATA-4) test/films-real-core.spec.ts replays what the paged.media films recorded, on real core + the data-js engine + DuckDB: - DATA-1: a rectangle bound as EAN-13 to `ean` over "All rows of catalogue", lowered: the bars decode to the first row's EAN (2000000210018; was 2000000210209), and the stepper's record 4 to Bookmark set's. - DATA-2: a merge of card frames filled with a swatch, inset [14, 16, 14, 16], stroked and centred: every merged frame keeps all four (was: no fill, no inset). - DATA-4: `media.paged.data.merge` is listed on host.objects and merges through `invoke`, with options and with none. All three failed before their fixes (logged on the branch's base). docs/status.md: record numbering, the stepper, frame formatting in a merge, the script command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
cargo-deny's advisories check fails on `unsafe-libyaml` (unmaintained, RUSTSEC-2024-0449), which reached the workspace only through `serde_yaml` 0.9.34+deprecated: the registry rows read by the data-core and data-expr build scripts and by the coverage gate. `serde_norway` 0.9.42 is the maintained fork of serde_yaml 0.9 (same parser, same `Value`, same `from_str`), on `unsafe-libyaml-norway`; MIT OR Apache-2.0 / MIT, inside deny.toml's allow-list. `serde_yaml_ng` was not taken: it still depends on `unsafe-libyaml`. A rename of the crate path in three files; no other change. Identical behaviour, checked: - the generated code is byte-identical: data-core `funcs.rs` (sha256 f6f377e9…) and data-expr `dispatch.rs` (30cd6df8…) before and after; - all 23 registry YAML files parse to the same `Value` under both crates (JSON and Debug renderings compared, a scratch harness); - the coverage gate prints the same report (GREEN, 130 rows). cargo deny: advisories ok, bans ok, licenses ok, sources ok. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the paged.data defects the paged.media films recorded (DATA-1 to DATA-4; DATA-5 is reported below, most of it lives in core and the editor). Each fix landed with a test that failed first. Version: 0.1.0-canary.16 (the publish workflow ships it after a merge).
DATA-1: an unindexed barcode encoded the wrong record (A)
Cause. Every result was numbered by content:
stable_orderwith no keys sorts by every column. So "record 0" ofSELECT * FROM cataloguewas the alphabetically first product, Bookmark set (2000000210209), not the first row, Linen notebook (2000000210018). Data Merge, the Dataset preview and InDesign number records in the order the data arrives, so an unindexed binding, the stepper's record 1 and the merge's first page all disagreed.Fix. Record N is the Nth record as delivered when the query defines its order, and the Nth by content only when it does not (spec §6.1; ADR 553 amended).
data-query::result_order_is_definedreads the SQL against DuckDB's order-preservation rules, and errs on the safe side:ORDER BY, or a scan of one table through order-preserving clauses (WHERE,LIMIT,OFFSET,UNION ALL, a derived table or CTE over such a scan). DuckDB keeps insertion order there, becausepreserve_insertion_orderis on by default.DISTINCT, other set operations, windows with a partition or an order, sampling, subqueries outsideFROM, and an empty statement.Tables, record flows, rules, batch plans (
plan_batch_ordered), the stepper and every per-record binding share that numbering (record_row/record_order). DP-4 still holds: for a query whose order is undefined, the same rows delivered in another order are no change.Tests.
data-conformance/tests/record_order.rsfailed on origin/main: 3 of 4 tests (2000000210209vs2000000210018; table and stepper in content order;ORDER BYignored).data-queryorder.rsunit tests.automation.rskeeps its content-order assertions over a join and adds the scan case.stabilizeCalls1 → 0, as the harness asked: a scan sorts nothing.A related fix found on the way: a barcode re-lower or preview step never replaced the symbol
Cause. The update-in-place batch removed the old modules (
deleteFrame) and then drew the new ones (insertPath). Real core (protocol 71) refuses that batch with "position 30 out of range for parent Spread … (len 0)". The refusal was only a log line, so the old bars stayed. The existing relower test checked only the module count, and a refused batch keeps the count too.Fix. Draw first, then remove, still in one batch and one undo step. The label still lands on the last new module (
$created). The warning now includes core's error.Test.
relower-real-corenow compares the bars and requires record 1's symbol to replace record 0's. It failed with the old order.DATA-2: Data Merge dropped the template frame's fill and inset
Cause. Merged frames are minted fresh, because core cannot copy a frame whose story holds a placeholder hyperlink. Only the story-level formatting was copied onto them.
Fix.
readMergeTemplatealso reads each template frame's own properties. It keeps every property the frame sets except its place, threading, layer, lock and visibility (isCopiedFramePath), with the object style first. Values a fresh frame already has, such as InDesign's[Normal Text Frame]or one column, are skipped (isSetValue). The writer sets the kept properties on each merged frame in the same batch.The new read is paid for: the session now reads the pages and the scene tree once for both the template and the re-merge plan. W8: host calls 18 → 17, reads 12 → 11, mutation ops unchanged (403). The 8 InDesign recordings still match.
Tests.
test/films-real-core.spec.tsDATA-2 merges a card template that is filled, inset, stroked and centred, and checks every merged frame. It failed before:fill of ud: expected null to be 'Color/u1'.merge.test.tscovers the pure part.DATA-3: the stepper started disabled; Data query Preview stayed disabled
Cause. The stepper's record total and position were panel state that started at 0 and was set only by stepping. Every mount, including a tab switch, began at "— / —". In the Data query panel, Preview and Save were enabled only by typed SQL, while the field showed the source's rows as a placeholder.
Fix.
getState().records, taken from the delivered batch with no engine call) and the stepped-to record (getState().preview,setPreviewPosition).refreshDataemits a change. The stepper shows "1 / N" once the query has run and keeps its place across a remount.SELECT * FROM "<source>".Tests. All of these failed before the fix:
panels.test.ts: the stepper opens at "1 / 24", keeps "2 / 24" across a remount, and "next ›" runs the data.query-panel.test.ts: Preview after Read columns.preview.test.ts: the session keeps counts and position.DATA-4: Data Merge could not be started from a script
Cause. Scripts reach typed commands (
paged.invoke, the object model). The data plugin had typed commands for refresh, apply and data sets, but not for the merge. Only the plain menu command ran it.Fix. A new typed command,
media.paged.data.merge, with flat optional arguments:query,mode,arrange, spacings,removeBlankLines,template,destination,pageId,documentName. It and the menu command both callrunMergeCommand(src/merge-command.ts). A script gets{ ok, records, pages, overset, message, diagnostics }.Test.
films-real-coreDATA-4 checks that the command is listed and merges throughh.objects.invoke, both with options and with none (the menu's defaults). It failed before because the command was not listed.DATA-5 (C): mostly not in this repo
insertPagemints a page with noName, andduplicatePagecopies the template'sNameverbatim (every page "1"); seen in an exported package. The editor's filmstrip shows the spread label, and its id test (isIdLikeLabel,packages/shell/src/cockpit/spread-grouping.ts:39) needs at least 3 hex digits, sou5is shown as a label. To fix in core (page names for minted pages) and/or the editor.snapshotsmap, which has no entry for pages minted in a batch (ThumbnailRail.tsx). Editor.pageStructureChangedthe editor rebuilds the document handle (packages/shell/src/PagedShell.tsx:1133). The view then fits all 24 pages. Editor.Checks (local)
cargo nextest run --workspace --profile ci: 317/317. Clippy-D warningsis clean, as arecargo fmt, the coverage gate (GREEN, 130 rows) and both dependency guards.pnpm typecheckpasses.pnpm testwithREQUIRE_REAL_ENGINE=1 REQUIRE_REAL_DUCKDB=1 REQUIRE_REAL_CORE=1: 398 passed, 2 skipped (+53 host-model). The manifest is valid and the package-hash check passes (canary.16 is new).cargo deny check: advisories ok, bans ok, licenses ok, sources ok (after the dependency swap below).Dependency:
serde_yamlreplaced byserde_norway(RUSTSEC-2024-0449)Cause. The
Checkjob's cargo-deny step failed onunsafe-libyaml, which is unmaintained (RUSTSEC-2024-0449). It reached the workspace only throughserde_yaml0.9.34+deprecated, which reads the registry rows in the data-core and data-expr build scripts and in the coverage gate.Fix.
serde_norway0.9.42, the maintained fork of serde_yaml 0.9: same parser, sameValue, samefrom_str. It sits onunsafe-libyaml-norway. Licences are MIT OR Apache-2.0 and MIT, both inside deny.toml's allow-list.serde_yaml_ngwas rejected because it still depends onunsafe-libyaml. The change is a crate rename in three files.Identical behaviour.
funcs.rssha256 f6f377e9… anddispatch.rs30cd6df8…, before and after.Values under both crates. A scratch harness compared their JSON and Debug renderings.__CARGO_DEFAULT_LIB_METADATAchanged moves the code by 665 bytes and 10 functions.🤖 Generated with Claude Code