New packages are created with the ordinary 0666 request; the process umask
decides the effective mode. Annotated with that reason so the analyzer's
world-writable warning does not change the intended sharing behaviour.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
* i18n: missing French strings don't fail tests
* i18n: drop the Czech tool-name translation check
* i18n tests: one catalog check for every language, no translated literals
One test checks every catalog (each language's table and context table, and
the shared tables' columns) for duplicate and stale keys, empty translations,
placeholders, padding, ellipses and label colons. One test selects, persists
and checks document data for every interface language. Context lookup,
fallback, built-in names and RTL are checked over every language against the
catalogs. Asserts on particular translated text, and checks that particular
strings are translated, are gone.
* i18n: drop a stale German entry
* i18n: complete the French interface (#644)
* i18n: French uses gabarit for parent pages; fixes
* i18n: French for the guide commands and the shortcut-set import
* #1180: import an InDesign keyboard shortcut set
Edit > Keyboard Shortcuts gets Import InDesign Set..., which reads the
.txt that InDesign's Show Set writes (window.importShortcuts {path|text}).
Matched commands take the set's Default shortcut, or lose theirs when
every occurrence says [none defined]; other commands give up keys the
set takes. The report lists changes, cleared conflicts, and the items
that could not be matched or read. Tool keys stay fixed.
* #1180: read the set file through the checked reader
* #1180: when two items want the same keys, the later one takes them
* Hidden characters: trailing spaces show their dots before the paragraph mark (#594)
* Hidden characters: skip glyphs whose offset isn't a character boundary
* Paste: other apps' text goes into a text caret even after copying objects (#212)
* Paste: text that matches a copied frame still pastes; groups with text frames aren't anchored (#212)
* IDML: keep column guides that were dragged off centre (#1096)
InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.
`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.
`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.
Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.
That commit left `lib.rs` still listing guides as ignored on import; corrected.
* IDML: test a ruler guide in a column gutter without facing pages (#1096)
* View: Lock Guides and Lock Column Guides are commands (#1096)
view.lockGuides {on?} (Cmd+Alt+;) sets the Lock Guides UI state the Properties panel and ruler-guide drags use. view.lockColumnGuides {on?} is a document setting, on by default, saved with the document, not an undo step, and read from and written to IDML ColumnGuideLocked. Both are in View > Grids & Guides with checkmarks.
* IDML: custom column positions are validated and round-trip (#1096)
Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.
ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.
* Layout: Margins and Columns keeps dragged columns unless the count or gutter changes (#1096)
* Layout: autoflow, inserted pages and new parents follow dragged columns (#1096)
* Integration: close#1121's round-trip test beside the ruler-guide test
* View: Lock Column Guides is translated wherever Lock Guides is (#1096)
* View: Lock Column Guides is translated wherever Lock Guides is (#1096)
* Layout: a new margin or page width resets dragged columns (#1096)
* IDML: a page without its own margins checks the default's column positions against its width (#1096)
* Layout: #1121's margin case follows the new-width rule (#1096)
* View: Lock Column Guides is undoable (#1096)
* Layout: dragged columns scale with a new margin or page width (#1096)
* Layout: dragged columns mirror when a page changes side (#1096)
* Layout: the page-width test drags a page whose columns are its own (#1096)
* Selection: drag a column guide to move its gutter (#1096)
* Layout: pages that follow a one-page parent take its dragged columns mirrored (#1096)
* Selection: column-guide drag review fixes (#1096)
* ROADMAP: the progress line lands in one commit after the guide PRs
---------
Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
* Guides: a dragged guide shows where it is (#1282)
While a guide is dragged out of a ruler or moved, a label beside the
pointer reads X: or Y: and the guide's place, in the ruler units and
measured the way the rulers measure (from the zero point, and from the
page origin when rulers restart per page).
* Guides: keep the drag readout inside the canvas
Near the canvas's right or bottom edge the readout flips to the other
side of the pointer, and it is kept inside the canvas in every case
(readout_at).
* Guides: the drag readout measures the spread under the pointer
The readout measured from the spread in the middle of the view (where the
rulers measure), but a guide is dropped on the spread under the pointer,
so with spreads above one another the number shown could differ from the
guide's place. It now measures that spread (ruler_pieces_of).
* Guides: no readout for an existing guide over another spread
An existing guide moves only on its own spread (guide_drag), so dropped
over another it stays where it was: the readout no longer shows a place
there it won't go.
* #1282: guide selection commands (guide.select, guide.selectAll, guide.deselect, guide.moveBy)
* #1282: select ruler guides on the canvas, X/Y in the Control bar and Properties
* #1282: guides take clicks over objects, a new guide is selected, group drag preview
* Guides: a dragged ruler guide snaps to guides, margins, columns and objects (#1221)
---------
Co-authored-by: SBaksa <sabaksa11@gmail.com>
* IDML: keep column guides that were dragged off centre (#1096)
InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.
`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.
`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.
Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.
That commit left `lib.rs` still listing guides as ignored on import; corrected.
* IDML: custom column positions are validated and round-trip (#1096)
Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.
ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.
* Layout: autoflow, inserted pages and new parents follow dragged columns (#1096)
* IDML: a page without its own margins checks the default's column positions against its width (#1096)
* Layout: dragged columns mirror when a page changes side (#1096)
* Layout: pages that follow a one-page parent take its dragged columns mirrored (#1096)
* ROADMAP: the progress line lands in one commit after the guide PRs
---------
Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
* IDML: keep column guides that were dragged off centre (#1096)
InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.
`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.
`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.
Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.
That commit left `lib.rs` still listing guides as ignored on import; corrected.
* IDML: custom column positions are validated and round-trip (#1096)
Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.
ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.
* Layout: Margins and Columns keeps dragged columns unless the count or gutter changes (#1096)
* Layout: a new margin or page width resets dragged columns (#1096)
* Layout: #1121's margin case follows the new-width rule (#1096)
* Layout: dragged columns scale with a new margin or page width (#1096)
* Layout: the page-width test drags a page whose columns are its own (#1096)
* ROADMAP: the progress line lands in one commit after the guide PRs
---------
Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
* View: Lock Guides and Lock Column Guides are commands (#1096)
view.lockGuides {on?} (Cmd+Alt+;) sets the Lock Guides UI state the Properties panel and ruler-guide drags use. view.lockColumnGuides {on?} is a document setting, on by default, saved with the document, not an undo step, and read from and written to IDML ColumnGuideLocked. Both are in View > Grids & Guides with checkmarks.
* View: Lock Column Guides is translated wherever Lock Guides is (#1096)
* View: Lock Column Guides is undoable (#1096)
* ROADMAP: the progress line lands in one commit after the guide PRs
* IDML: keep column guides that were dragged off centre (#1096)
InDesign records dragged column guides in `MarginPreference ColumnsPositions`,
as pairs of offsets from the margin box's left edge. The importer discarded them
and divided the columns evenly, so a page whose gutter the designer had moved
came back with it in the middle: on a 450 pt margin box, `0 150 170 450` was
read as columns at 215 and 235.
`Columns::positions` already existed, documented as "Custom column positions
(after dragging column guides)", but nothing set it and nothing read it. Now
`Page::column_rects` returns the custom rects when they are set — that one
method is what the canvas guides, snapping, threading and the first frame of a
placed story all go through, so they follow the dragged gutter — the importer
fills it, and the exporter writes the page's real positions instead of
recomputing an even split, so the round trip keeps them.
`layout.marginsAndColumns` clears them when a count or gutter is set, which is
what InDesign's Margins and Columns does. Without that the stale positions would
outrank the new value and the dialog would look broken.
Verified against InDesign-authored IDML rather than assumed, which settled two
things. The attribute is written whether or not anything was dragged — a real
page's values are exactly its even split — so an even set is dropped rather than
marking every imported page custom and freezing its columns; the tolerance for
that comparison only absorbs float noise, about 1e-14 pt on that page. And
`<Guide>` is a `<Page>` child, not a `<Spread>` one, which is where this
importer already looks: the reporter's missing ruler guide was 099fbd2, landed
the day the ticket was filed, not a second bug.
That commit left `lib.rs` still listing guides as ignored on import; corrected.
* IDML: custom column positions are validated and round-trip (#1096)
Columns::positions are used only when they are 2 x count finite edges,
ascending from 0 to the margin box width (within 0.01 pt); anything else
is the even split, in Page::column_rects, on IDML import (bounded parse,
checked against the page's margin box) and on IDML export.
Columns::with_gutter_moved moves one gutter's pair of guides between its
neighbours for the coming column-guide command.
ROADMAP: drop four progress-log lines the #1121 merge duplicated and move
its line to the top.
* IDML: a page without its own margins checks the default's column positions against its width (#1096)
* ROADMAP: the progress line lands in one commit after the guide PRs
---------
Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
* Guides: a dragged guide shows where it is (#1282)
While a guide is dragged out of a ruler or moved, a label beside the
pointer reads X: or Y: and the guide's place, in the ruler units and
measured the way the rulers measure (from the zero point, and from the
page origin when rulers restart per page).
* Guides: keep the drag readout inside the canvas
Near the canvas's right or bottom edge the readout flips to the other
side of the pointer, and it is kept inside the canvas in every case
(readout_at).
* Guides: the drag readout measures the spread under the pointer
The readout measured from the spread in the middle of the view (where the
rulers measure), but a guide is dropped on the spread under the pointer,
so with spreads above one another the number shown could differ from the
guide's place. It now measures that spread (ruler_pieces_of).
* Guides: no readout for an existing guide over another spread
An existing guide moves only on its own spread (guide_drag), so dropped
over another it stays where it was: the readout no longer shows a place
there it won't go.
* #1282: guide selection commands (guide.select, guide.selectAll, guide.deselect, guide.moveBy)
* #1282: select ruler guides on the canvas, X/Y in the Control bar and Properties
* #1282: guides take clicks over objects, a new guide is selected, group drag preview
* ROADMAP: the progress line lands in one commit after the guide PRs
---------
Co-authored-by: SBaksa <sabaksa11@gmail.com>
* macOS menu bar: keep working after a rebuild; key equivalents follow the shortcuts in effect
* macOS menu bar: the shortcut test sets its key with window.setShortcut
* Dedupe the 400-year-cycle UTC conversion and the EPUB package writer
SonarCloud flagged new-code duplication on main (17%): five copies of the
"move the instant into chrono's range by whole 400-year cycles" conversion
and two copies of the EPUB OPF/container/zip writer.
- designcraft-doc gains a `time` module: `date_parts(days)` and
`utc_parts(secs, nanos)`. App logging, EPUB `iso8601`, PDF `civil`, PPTX
`civil` and text-variable `civil` are now thin callers; each keeps its own
format string and year clamp. The app reaches it through
`designcraft_engine::doc`, so its direct chrono dependency is dropped.
- EPUB: `package_opf` + `write_package` shared by fixed-layout and
reflowable export (same entry order and compression).
- Boundary tests on the helper: pre-1970, year 9999 -> 10000, i64 extremes,
nanos past a second.
Output is unchanged: existing date tests pass, and fixed/reflowable EPUBs
(with/without cover, author, pre-1970 and i64::MAX dates) are byte-identical
to origin/main.
* time: drop the unreachable i128 clamp and match arms in utc_parts
Whole 400-year cycles times 400 stay under 2.6e16 for any i64 day count,
so the year can't overflow i64 and the i128 + saturate was dead code
(carried over from the copies this replaced). The date and time lookups
can't fail either; each keeps one unwrap_or_default instead of a match
with a fake 1970 fallback. Output and tests unchanged.
* Use chrono for UTC/calendar conversion instead of hand-rolled copies
Replaces the hand-rolled civil / civil_from_days / days_from_civil copies (the
146_097 / 719_468 magic numbers) across the logging timestamp, text-variable
dates, EPUB dcterms:modified, PDF metadata dates and PPTX core.xml dates with
chrono, added as a workspace dependency (default-features = false, std only, so
wasm is unaffected).
- apps/designcraft logging `timestamp` (private civil_from_days deleted).
- doc text variables: `civil` and `days_from_civil` use chrono::NaiveDate;
`format_date` and `parse_iso_date` keep their own format/parse logic and
still call these helpers.
- epub `iso8601`, pdf `civil`, pptx `civil`: UTC datetime via
chrono::DateTime::from_timestamp.
Output is byte-identical and pinned by the existing date, ISO-date, EPUB,
image, EPS and logging tests. The OOXML/EPUB dcterms timestamps keep their
exact strings. Each converted helper keeps a proleptic-Gregorian fallback for
dates outside chrono's NaiveDate range (and for the serial/overflow dates
parse_iso_date can hand days_from_civil), so no input changes or panics.
AGENTS.md now says time goes through chrono.
Follow-up to storytold/cadcraft#1785
* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed
logging.rs timestamp(): chrono's From<SystemTime> unwraps, so a clock past
year 262143 panicked inside the logger. Use the fallible DateTime::from_timestamp
and read such a clock, or a pre-epoch clock, as the epoch like the old saturating
code did. Years past 9999 are written with {:04} like before, because %Y adds a
'+'. Tests cover both cases.
epub iso8601(): write the year with {:04} instead of %Y, matching the old
{year:04} output, so years past 9999 are not prefixed with '+'. Test added.
AGENTS.md: the "Time goes through chrono" rule now notes the narrow fallback
for dates outside chrono::NaiveDate's range, which crates/doc/src/vars.rs
retains (Howard Hinnant's algorithm).
* Don't build an unrepresentable SystemTime in the far-future test
UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.
* Route out-of-range dates through chrono's 400-year cycle, not hand-rolled copies
doc/vars.rs kept hand-rolled Hinnant `civil_fallback`/`days_from_civil_fallback`
copies for dates outside chrono's ±262143-year range, and AGENTS.md carved a
"narrow fallback" exception for them, which left calendar arithmetic outside
chrono after all.
Replace both copies with the Gregorian 400-year cycle (146_097 days): chrono
converts the day/year within one cycle and whole cycles are added back via i128
(so i64::MIN/MAX can't overflow). `civil` is reached by the system clock through
`format_date(now(), ..)`, so it must stay exact; `days_from_civil` only ever
sees a 4-digit parsed year via `parse_iso_date`, but it uses the same cycle form
for consistency. Both reproduce the pre-chrono proleptic Gregorian result, so
outputs are unchanged (verified against the old formula over wide ranges and at
the i64 extremes).
New test pins dates past chrono's range on both sides of the epoch, through the
public `format_date` and round-tripping `days_from_civil`/`civil`, with values
from the old formula. Removed the AGENTS.md "narrow fallback" exception.
epub/pdf/pptx `civil`/`iso8601` are left as-is: the PR already dropped their
hand-rolled copies for pure chrono, and their remaining epoch (`unwrap_or_default`)
fallback only triggers for a metadata/clock timestamp past year 262143 -- the
live-clock/file-mtime case FredApps accepted, with outputs that clamp the year
anyway (pptx 0..9999, PDF DateTime). No hand-rolled copy remains there to replace.
Tested: cargo test -p designcraft-doc; fmt --check; clippy --all-targets -D warnings.
* Carry an overflowing day into the next month again in parse_iso_date
parse_iso_date accepts any day 1..=31, and the old days_from_civil
carried a day past the end of its month into the next one (2019-02-30 is
2019-03-02). from_ymd_opt rejects such a day, and unwrap_or(0) turned it
into 1970-01-01. chrono now places the 1st of the month and the day is
added on, which restores the old normalization. Tested through days_from_civil
and parse_iso_date.
* time: preserve old out-of-range date conversion (no epoch fallback)
`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any
instant past chrono's year range to 1970-01-01, changing output the old
hand-rolled Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s
was 318857-05-20 17:46:40, not the epoch). Restore the old conversion domain
with a cycle-based helper that routes the calendar through chrono (the
calendar repeats every 400 years = 146_097 days, so month/day/time come from
chrono and only the year is carried, keeping every i64 exact), then apply each
format's existing year clamp afterwards.
- apps/designcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/epub/src/lib.rs: `iso8601` via `utc_parts`.
- crates/pdf/src/export.rs: `civil` cycle-based; `date_time` keeps its
0..=9999 year clamp.
- crates/pptx/src/package.rs: `civil` cycle-based, keeps its 0..=9999 year
clamp on the year only (month/day/time keep the real instant).
Pinned old outputs in tests (through the public helpers):
logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch)
epub 10_000_000_000_000 -> 318857-05-20T17:46:40Z
-10_000_000_000_000 -> -314918-08-13T06:13:20Z
i64::MAX -> 292277026596-12-04T15:30:07Z
i64::MIN -> -292277022657-01-27T08:29:52Z
pdf civil(10_000_000_000_000) -> (318857,5,20,17,46,40)
pptx civil(10_000_000_000_000) -> (9999,5,20,17,46,40) (year clamped)
* Don't build a year-318857 SystemTime unchecked in the logging test
Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.
* Name the 400-year range extension as the one exception to the time rule
The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.
* Say the library computes month and day; time-of-day is plain arithmetic
Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
Adds .coderabbit.yaml so CodeRabbit no longer reviews or comments on PRs opened by dependabot[bot].
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Only path-filtered workflows run on pull requests today, so most PRs land
without a test run. Port of the wordcraft#800 workflow: Linux, standard
GitHub-hosted runner, read-only token, no secrets, craft-fonts pinned to
the release.yml commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>