* M6: calculations read their notation only from a comment, and a regex after a condition as a regex
Two findings from CodeRabbit's review of #3550, both reproduced first:
- parse_calculate and calculation_sites found BVCALC and EVCALC anywhere
in the script, so `var s = "BVCALC a EVCALC";` read as the notation `a`:
renaming `a` rewrote the string, and the form calculated `a` instead of
running the script. The lexer now keeps the text of each comment, and
the notation is read from the first comment that holds BVCALC and the
EVCALC after it (notation_range), by both the Calculate tab and a
rename. A line comment counts, and a comment that never ends runs to
the end of the script, as before.
- A `/` after any `)` divided, so after the condition of `if`, `while`,
`for` or `with` a regular expression was read as code: renaming `a`
rewrote `if (flag) /getField("a")/.test(x)`, and in
`if (flag) /"/.test(x)` the quote started a string that hid the
script's real getField calls. The lexer now notes which `(` opened
such a condition (not a method of that name, `o.if(x)`), and a `/`
after its `)` starts a regular expression.
af::tests::notation_is_read_only_from_a_comment,
af::tests::a_regex_after_a_condition_is_not_read_as_code and
tests::a_script_that_spells_the_markers_in_a_string_runs_as_a_script
fail without the fix. Parity, ROADMAP.md and docs/roadmap.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: form.props-calculate notes that a regex right after a brace is still read as code
From CodeRabbit's review of this PR. The lexer still divides after `}`,
which ends a block as often as an object literal, so in
`if (x) { } /"/.test(s); event.value = getField("a").value;` the quote
starts a string and the getField is hidden from a rename and from the
calculation order. Checked with a scratch test before writing it down.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: a line comment ends at any line terminator, so code after `\r` is still read
From CodeRabbit's review of this PR. A `//` comment ran to the next `\n`,
so with `\r` line endings it hid the rest of the script: a rename missed
the getField calls after it (since #3550), and a string after it that
spells BVCALC ... EVCALC read as the notation. A `/` taken for the start
of a regular expression likewise looked past `\r` for its end, and could
swallow the next line's code.
Both now stop at any of JavaScript's line terminators (`\n`, `\r`,
U+2028, U+2029; line_ends_at), and U+2028 and U+2029 between tokens are
skipped rather than read as the start of a word.
af::tests::a_line_comment_ends_at_any_line_terminator fails without each
of the three changes: with `\r`, the string read as notation `a`; with
the comment fixed but not the regex scan, `i++ / 2;\r` swallowed the
getField after it; without skipping U+2028, the getField after it was
not a call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: a word, number or regex's flags end at U+2028 and U+2029
From CodeRabbit's review of this PR. The scans for an identifier, a
number and a regular expression's flags took every byte past ASCII as
part of a word, U+2028 and U+2029 included, so in `x\u{2028}getField("a")`
the getField was part of one word and not a call: a rename missed it, and
the calculation order didn't read it.
`word` now takes a position and is false where line_ends_at is true, so
the three scans share one rule. The arm that skips U+2028 and U+2029
between tokens stays: without it, the punctuation arm would step one byte
into the character.
af::tests::a_token_ends_at_a_line_separator fails without the fix, for a
word, a number and a regex's flags alike; it and
a_line_comment_ends_at_any_line_terminator fail without the skipping arm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: the `while` that ends a `do` loop counts as a condition, said and tested
A `/` after the `)` of a do-while starts the next statement, so it starts a
regular expression there too. The comment on BEFORE_CONDITION says so, and
a_regex_after_a_condition_is_not_read_as_code covers it (from review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The track merges used a union driver for these files, which kept two
versions of a milestone row or status line wherever two tracks changed the
same one. They are recomputed as base plus each track's changes, with two
edits of one line combined into one line. The status lines that #2805, #3808
and #3532 put above the Stage line join the status list below it, newest
first.
* M14: add Norwegian Bokmål interface and date language
A 2,592-entry `nb.tsv` covering every key the other bundled catalogs have,
written from the meaning of the English labels (clean-room); terminology
reviewed by a native-speaking contributor. Registers `nb` with the English
one/other plural rule, adds Norwegian month and weekday names for Fill &
Sign dates, resolves `no` and `nn` locales to Bokmål until a Nynorsk catalog
exists, attributes the catalog and records it in parity.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the strings main added since the Norwegian catalog was written
69 keys (signature appearance labels, word count, full-screen prompt, print
as image, image crop, redaction mark appearance, …) and the attribution hash.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the picture-menu and label-paper strings in the Norwegian catalog
Seven keys main added (Cut/Redact Image, Create Link…, Recognize Text…,
Turn upside down, the 4 x 6 in label paper) and the attribution hash.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the page-group folding commands in the Norwegian catalog
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: drop the superseded localization summaries left beside the current ones
Concurrent merges on main left older copies of several localization
summaries next to their replacements. Review of #1836 flagged them:
- localization-parity.md: remove the 2026-10-13 status line and the
14-language summary that still listed Norwegian as missing; the
current summary keeps the Italian (and now Norwegian Bokmål)
measurement dates.
- localization.md: keep one catalog-coverage paragraph (now naming
Norwegian Bokmål, up to about 2,700 entries) and one font paragraph.
The font paragraph folds in what only the older copies said: the
installed Chinese face fallback (#826) and Slovenian's Latin faces and
four plural forms.
- roadmap.md: end the M14 Missing list before the Fontconfig note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 27.
crates/ui-egui/tests/fonts.rs: both catalog lists; Catalan added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the ca row; 27 interface languages; both revision rows.
docs/localization.md: main's text with Català, the ca locales, code and catalog paragraph added.
Conflict resolutions:
- ATTRIBUTION.toml: main's entries kept; #3101's page-gap provenance note appended to the 22 catalogs it translates (hashes recomputed separately).
- ATTRIBUTION.md: main's side kept; regenerated from ATTRIBUTION.toml separately.
- ROADMAP.md: #3101's two 2026-10-18 progress-log lines placed after the 2026-10-19 entries.
- docs/roadmap.md: both 2026-10-18 status lines kept; M3 row carries both sides' insertions.
ROADMAP.md, docs/roadmap.md: all status lines and progress-log lines kept; M12 row takes both 'Done since' insertions.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
ROADMAP.md: progress-log line added at the top
docs/roadmap.md: the M11 note applied onto main's row (which #3622 extended)
crates/optimize/tests/optimize.rs: new tests appended after main's
ROADMAP.md, docs/roadmap.md: both status lines and progress-log lines kept; M12 row takes both 'Done since' insertions.
crates/automation/tests/automation.rs, crates/xfa/src/fixtures.rs: union of #2624's and #2619's tests and fixtures.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: both progress-log lines, newest first.
docs/localization-parity.md: the PR's status line; both revision rows, newest first.
docs/roadmap.md: the PR's rows (main's rows plus the PR's wording).
ROADMAP.md: kept main's stage line and status lines, added this PR's status and log lines.
docs/roadmap.md: kept main's status lines and M8/M9/M10 rows (main had merged the duplicate M8
row), added this PR's status line and its M10 sentence.
crates/ui-egui/src/lib.rs: persist keeps main's `v` and its separately set keys; print_paper
set the same way.
crates/ui-egui/tests/print.rs: kept both sides' tests.
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: both progress-log lines, newest first.
crates/ui-egui/src/i18n/cs.tsv: both sides' entries, no duplicate keys.
docs/localization.md, docs/roadmap.md: main's single paragraph and row, with the PR's Czech wording applied.
parity/acrobat-features.toml: form.highlight-fields evidence and notes from both sides (the renamed remembered test, the read-only test)
docs/roadmap.md: M6 row keeps both added sentences
ROADMAP.md: union of the progress-log lines
crates/xfa/src/script.rs: template_field and named_in moved to crate::data in #3397; field_grows_with_text calls crate::data::template_field.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
ROADMAP.md: progress-log line added at the top
docs/roadmap.md: status line, M10 row note and revision row applied onto main's
parity/acrobat-features.toml: export-docx evidence and notes insertions applied onto main's
crates/edit/src/tests.rs, crates/engine/src/tests.rs: new tests appended after main's
ROADMAP.md: union of progress-log lines.
docs/roadmap.md: union of status lines and revision-history rows.
crates/ui-egui/src/comments_panel.rs: card() takes main's `info` argument and the PR's borrowed reply slice.
Conflicts:
- crates/ui-egui/src/i18n/*.tsv: union of both sides' new keys
- ATTRIBUTION.toml: ours; hashes recomputed at the end of the track
- ROADMAP.md, docs/roadmap.md: both progress-log entries, newest first
- parity/acrobat-features.toml (organize page grid): main's evidence and remembered-size note, with the PR's Pages inspector sentence
* M6: Prepare a form ▸ Text field table: rows and columns of text fields as one edit
Drag an area with the Text field table tool and set it up in a movable
window: rows, columns, row and column spacing, the first row number,
column widths (draggable dividers, arrow keys once focused) and a name
pattern per column, with the table previewed on the page. Add fields adds
every cell as one undoable edit ("Add text field table").
- forms: `table` lays the table out in view space and names its cells. It
refuses the whole table for a cell under MIN_FIELD_SIDE (now one
constant) or a name with a period, used twice or already taken, and
free_base picks the first free TableK for default names.
- engine: `field_table::edit` maps the cells onto the page, /Rotate and
/UserUnit included, and checks their size again there.
- automation: `form_add_field_table`, with an end-to-end test.
- ui-egui: the window belongs to one page of one document and closes when
that stops being the one prepared; Enter commits a number instead of
adding the table; dividers between many columns no longer panic in
f64::clamp. Strings in all 22 catalogs, and the Spanish engine-wording
tests cover the table's mistakes.
- parity: form.multiple-copies is partial; copying an existing field into
a grid is still missing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: text field table: refuse corners past the page's number range, run it by its command
Review follow-up on #3479.
- engine: a corner finite as an f64 but past f32's range came back from
the page's f32 mapping infinite. With one such corner a side was
infinite rather than NaN, so the cell passed and add_field refused it
only once the edit was applied ("the field is too small"). The edit now
refuses any corner that isn't finite on the page with the table's own
reason, before anything is added.
- automation: form.add.text_table runs form_add_field_table through
command_run, as comment.stamp runs stamp_custom.
- ui-egui: doc comments for the window's three remaining helpers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: text field table: close on document edits, lay out once per change, keep Escape for fields
Review follow-up on #3479 (benandez).
- The window belongs to the document as it was when it opened: it closes
when the edit generation moves (a page rotated, moved or deleted, an edit
undone), so a table set up for one page never lands on another or off it.
- The table is laid out and checked once per change, not several times a
frame: the window and the page preview share one `Laid` (cells and the
edit), and the preview draws only the cells in view, naming only those
with room for a name.
- Escape in a field of the window leaves the field and keeps the setup; a
second Escape closes the window.
- A table placed with a click is moved in from the page's edges, so all of
it is on the page.
- `free_base` takes one pass over the names; `{seq}` is documented as
counting from the first row number; `form_add_field_table` takes its
names from the batch instead of laying the table out again; the
unreachable `Dialog::FieldTable` arm says why it is there.
Each new test fails with its fix reverted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: text field table: drawn with its panel closed, the window opens Prepare a form instead of closing
With the tool picked and the Prepare a form panel closed, the form is
still prepared and a table can be drawn. Drawing sends the tool back to
Select, though, so nothing was prepared on the next frame, and the table
window, which lasts only while the form is prepared, closed itself at
once: the table couldn't be set up. Opening the window now opens the
Prepare a form panel it belongs to. Closing the panel while the window
is open still closes the window.
Found by CodeRabbit, which saw the preview stop drawing; the window
closing first was the larger effect. prepare.rs
a_table_drawn_with_the_panel_closed_opens_it_again fails without the fix
("the window stays open": (None, false)).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* Drop the second generation binding the main merge left in document_area
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>