Commit Graph
1961 Commits
Author SHA1 Message Date
kinower 7dce06dca4 UI: keep rapid mouse-wheel slide navigation steps 2026-10-22 00:43:03 +02:00
Dylan M. Taylor e149f32208 security: enforce HTTPS on curl downloads (SonarCloud S6506)
Add --proto '=https' to curl invocations so redirects cannot downgrade to
plaintext HTTP. Resolves SonarCloud S6506 (shell/docker/githubactions).
2026-10-21 17:06:39 -04:00
Dylan M. Taylor 354087ecd5 ci: add sonar-project.properties for CI-based analysis
Marks the project ineligible for SonarCloud Automatic Analysis, resolving the
CI-vs-automatic-analysis conflict so the SonarCloud workflow succeeds.
2026-10-21 16:00:30 -04:00
Dylan M. Taylor 4113361660 ci: add SonarCloud scan via shared reusable workflow
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).
2026-10-21 15:46:52 -04:00
Dominik StrebelandClaude Opus 5.5 30d00468e2 PPTX: a theme's spDef anchor is for new shapes, not the file's own
A shape whose text has no anchor of its own (`<a:bodyPr/>`) was given the
anchor of the theme's shape defaults (`a:objectDefaults/a:spDef/a:bodyPr`),
which Office's themes set to `ctr`, so its text was centred in the box.
PowerPoint for Mac draws such text at the top: a synthetic deck with an
spDef anchor="ctr" and three rectangles (`<a:bodyPr/>`, anchor="t",
anchor="ctr"), exported as PDF, puts the first two at the top. The spDef
defaults are for shapes inserted later.

Opening a file now anchors every shape without an anchor at the top, as
the standard has it; the model's middle default is still written as ctr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:05:25 +02:00
Dominik StrebelandClaude Opus 5.5 7919429652 Render: measure 3-D point sizes without the camera's turn
shape_at folds a 3-D camera's turn into the shape's transform, so
per_point() measured its foreshortening as if it were a group's
child-coordinate scale: in a 1:1 group, a shape under a turned camera
got a factor other than 1 and drew its extrusion depth, contour and
bevel at the wrong size. The factor is now measured on the transform
before the turn (m with the camera's affine taken back out), and the
turned transform still draws the geometry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:02:17 +02:00
Dominik StrebelandClaude Opus 5.5 48dbdffe07 Render: 3-D sizes in a scaled group stay in points
A group's child coordinates scale its shapes' boxes, but extrusion
depths, contour widths and bevel sizes are points, as PowerPoint draws
them. The renderer applied them in the child space, so the group's scale
multiplied them: a 1 pt contour on a small extruded cube in a legacy
group (1360 child units across a 61 pt group) was drawn hundreds of
points wide, a band across the whole slide, and the cube's extrusion ran
off towards the vanishing point.

per_point() gives the shape-space units per point (the inverse of the
child-coordinate scale that line_space already separates for line
widths); the extrusion depth, contour width and bevel width and height
are converted with it before drawing. Outside a group it is 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:47:33 +02:00
Dylan M. Taylor 5a76518ddd ci: add SonarCloud scan workflow and config 2026-10-21 13:55:12 -04:00
skylar taylor-barrick 9ad2cf3be8 Merge pull request #432 from Lexx143/fix/macos-finder-open
macOS: open presentations from Finder, Open With and the Dock (#138, #347)
2026-10-21 10:39:36 -06:00
fchinuandClaude Sonnet 5.5 f71ce3fa9d Format Painter: paint paragraph formatting per covered paragraph
Paragraph properties now go only onto paragraphs the selection covers
whole, so partly covered end paragraphs and the paragraph after a
triple-click keep their bullets. Double and triple clicks apply the
painter only when they changed the text selection.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 13:46:05 +02:00
FaabyyandClaude 3cd6a815ca Roadmap: record reuse-picker-1103
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:14:34 +00:00
FaabyyandClaude fdfdb9e058 Reuse Slides: choose which slides of the other file to insert (#1103)
Reuse Slides… used to insert every slide of the picked presentation. It now opens a list
of that presentation's slides (number and title, all ticked), with Select All / Select None
and a Keep source formatting option, and inserts only the ticked ones after the current
slide. New query slide.reuseList {path | data} gives the list; slide.reuse already took
`slides`.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 08:04:19 +00:00
Dylan M. Taylor 63831cdfa9 Merge pull request #865 from dylanmtaylor/ci/shared-nix-workflow
ci: switch to shared nix workflow from craft-common-actions
2026-10-21 03:40:14 -04:00
FaabyyandClaude e03debfd4b Media: engine requests resolve on the editor's slide (#994)
The media.* commands look the shape id up on the editor's current slide, so
the request has to be carried out against that slide too. Passing the show's
slide instead could play another slide's clip under a repeated id.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 06:07:58 +00:00
FaabyyandClaude 906898f662 Canvas: a flipped box snaps the edge under the dragged handle (#961)
The resize handles sit in the box's unflipped frame, so in a flipped box
(also one that a drag past the opposite edge just flipped) the dragged
handle is on the other side. Snapping looked at, and moved, the fixed
edge instead.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 06:07:58 +00:00
Faabyy 2741d3236b Thumbnails: only render the slides in view (#969)
The slide thumbnail pane and the Slide Sorter asked the texture cache for a
thumbnail on every row, in view or not, three or four new ones a frame. The
cache keeps at most 600 thumbnails and drops the 200 oldest beyond that, so
on a deck of more than 600 slides the pane evicted its own thumbnails before
it reached the end: it kept rendering, the rows in view went white again and
again, and it never finished.

Now only rows within the scroll area's clip (plus two rows of margin) ask for
a thumbnail and keep the pane repainting; the others are still laid out, so
scroll height, section headers, clicks and drag-to-reorder are unchanged.
Small decks behave as before.

Co-Authored-By: Claude <noreply@anthropic.com>


# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto a31b990
# Last command done (1 command done):
#    pick 03fce2a Thumbnails: only render the slides in view (#969)
# No commands remaining.
# You are currently rebasing.
#
# Changes to be committed:
#	modified:   ROADMAP.md
#	modified:   crates/ui-egui/src/huge_deck_tests.rs
#	modified:   crates/ui-egui/src/sorter.rs
#	modified:   crates/ui-egui/src/thumbs.rs
#
2026-10-21 06:07:58 +00:00
FaabyyandClaude 96697f7612 Media: the playing clip decides whether it carries over (#994)
The carry-over check looked the clip up again on the previous slide. If
that slide held the same media under the same id without Play Across
Slides, the voice stopped one slide later. A slide without the id fell
back to the first match in the deck. Use the clip the voice was started
with; the next slide is only checked for another clip under that id.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 06:07:58 +00:00
Faabyy 7b0252f291 Canvas: resizing snaps the edge being dragged (#961)
snap_resize ran snap_rect on the whole box, which looks at all three
edges on each axis and takes the nearest line. A fixed edge already on a
line (distance 0) always won, so the dragged edge never snapped; a fixed
edge near a line moved the dragged edge by its own distance. Resizing now
looks only at the dragged edges (snap_edges), also for the grid fallback.

Co-Authored-By: Claude <noreply@anthropic.com>


# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto a31b990
# Last command done (1 command done):
#    pick 92df608 Canvas: resizing snaps the edge being dragged (#961)
# Next command to do (1 remaining command):
#    pick abb0e09 Canvas: a flipped box snaps the edge under the dragged handle (#961)
# You are currently rebasing.
#
# Changes to be committed:
#	modified:   ROADMAP.md
#	modified:   crates/engine/src/tests.rs
#	modified:   crates/engine/src/tools.rs
#
2026-10-21 06:07:58 +00:00
FaabyyandClaude 2303efe7d5 Media: stop a clip across slides when the next slide reuses its id (#994)
A clip set to play across slides kept its voice even when the next slide
holds a different clip under the same shape id, so that slide's own clip
never started. Keep the voice only when the next slide has no clip under
that id or the same media.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-21 06:07:58 +00:00
Faabyy 2d6582163f Media: look a clip up on the slide it belongs to (#994)
The PPTX import numbers shapes per slide, so two slides can hold different
videos under one shape id. find_clip searched the whole deck and took the
first match, so the show's media cues, the keep-across-slides filter and
the media.* requests all got the first slide's clip: from slide 3 on, video 2
played. find_clip now takes the slide the request comes from and searches it
first, then the rest of the deck.

Co-Authored-By: Claude <noreply@anthropic.com>


# Please enter the commit message for your changes. Lines starting
# with '#' will be kept; you may remove them yourself if you want to.
# An empty message aborts the commit.
#
# interactive rebase in progress; onto a31b990
# Last command done (1 command done):
#    pick 1ab070b Media: look a clip up on the slide it belongs to (#994)
# Next commands to do (3 remaining commands):
#    pick ad4e3f0 Media: stop a clip across slides when the next slide reuses its id (#994)
#    pick 0957b58 Media: the playing clip decides whether it carries over (#994)
# You are currently rebasing.
#
# Changes to be committed:
#	modified:   ROADMAP.md
#	modified:   crates/ui-egui/src/lib.rs
#	modified:   crates/ui-egui/src/media.rs
#	modified:   crates/ui-egui/src/show.rs
#
2026-10-21 06:07:58 +00:00
NetterSilverandClaude Haiku 5.5 e89a893fe1 Add angle adjust handles to partial-circle presets
Fixes #1094

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:04:04 +00:00
NetterSilverandClaude Haiku 5.5 72a9e4456e Fix scientific number format for subnormal values
Found while testing crates/model/src/numfmt.rs:204-211 (sci)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 03:34:30 +00:00
NetterSilverandClaude Haiku 5.5 309d9f5b2c Treat CRLF as a single paragraph break in text.set
Found while testing crates/engine/src/cmd/text.rs:1219 (set), via crates/model/src/text.rs:605 (TextBody::from_text)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 00:13:45 +00:00
Mex 6b91132ce7 M8: document verified empty-format reliability scope
Record the synthetic formatter regression and preserved General fallback; broader chart gaps and readiness scores remain unchanged.
2026-10-20 23:10:09 +02:00
Mex 5e0a60b15e M8: record empty chart number-format section fix
Record narrow formatter behavior and synthetic regression evidence without changing chart scores or readiness estimates.
2026-10-20 23:09:29 +02:00
Mex 02b3940efd M8: honor explicit empty chart number-format sections
Keep blank whole-format General fallback while hiding values selected by explicit empty sections. Pin section selection, signed zeros, semicolon boundaries and General controls with regressions. Contribution on behalf of @MMexicann.
2026-10-20 23:08:54 +02:00
NetterSilverandClaude Haiku 5.5 98b6d04413 Keep rounded corners round in non-uniformly scaled groups
Fixes #1083

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:50:30 +02:00
Sazid Al Bayazid dc69e57600 Tests: Cancel restores every Options setting, not only the first (#878)
A report on 0.7.0 said the Options dialog's Cancel did nothing, and a
reproduction on 0.8.0 found it working for the appearance toggle and the
name field, without trying the other controls. This covers the rest: the
Editing checkboxes (snap to grid, smart quotes, autocorrect) and the name
field go back with Cancel, like the first checkbox does.

Verified red with the restore of the settings taken out: the name keeps
what was typed.
2026-10-21 00:35:43 +06:00
Bryson Meiling e5a407d6e2 text: add Symbol euro and Apple alternatives; keep covered bullets
Adobe symbol.txt also maps 0xA0 to the euro sign (U+20AC), which
symbol_unicode lacked. Apple's Symbol maps a few codes to different
Unicode than Adobe (0xA4 to U+2044, 0xE1/0xF1 to U+3008/U+3009), so
symbol_font_draw now tries those alternatives after the Adobe character
when the installed face doesn't cover it. The Adobe aliases for
0x44/0x57/0x6D (U+2206/U+2126/U+00B5) are not Apple alternatives and
stay out.

symbol_font_char masked every bullet character to its low byte before
checking direct coverage, so a 'bullet' (U+2022) bullet drew an
unrelated Symbol code. It now maps only symbol-font codes (low byte or
U+F0xx) and falls back to the original character when the face covers
it.
2026-10-20 12:23:50 -06:00
NetterSilverandClaude Haiku 5.5 502fb2ba73 Indent table rows in outline export so they stay on their slide
Found while testing crates/format/src/lib.rs:660-665 (slides_to_outline) and :573-574 (outline_to_slides)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 18:03:20 +00:00
fchinuandClaude Sonnet 5.5 6f83ed464d Format Painter: apply on text selection, keep bullets on partial lines
The painter only applied when a shape was clicked. It now also applies
when a text range is selected by drag, double-click or triple-click.
Paragraph formatting (bullets, alignment) is copied only onto
paragraphs the selection covers whole.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 19:46:15 +02:00
NetterSilverandClaude Haiku 5.5 e37267f548 Strip URI fragment before percent-decoding OPC targets
Found while testing crates/pptx/src/opc.rs:283-285 (resolve)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 18:36:13 +02:00
Brandon ThomasandClaude Opus 5.5 a31b990d32 Skip CodeRabbit reviews on Dependabot PRs (#1068)
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>
2026-10-20 11:40:09 -04:00
Dylan M. Taylor 7571e01210 Merge pull request #984 from dylanmtaylor/docs/add-sonar-coderabbit-badges
docs: add SonarCloud and CodeRabbit badges to README
2026-10-20 11:33:18 -04:00
NetterSilverandClaude Haiku 5.5 18f5e17b36 Escape carriage return as &#13; in XML text content
Found while testing crates/pptx/src/xml.rs:363 (esc)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 14:54:55 +00:00
NetterSilverandClaude Haiku 5.5 65564cfc1b Reject invalid slide index params instead of using the selection
Found while testing crates/engine/src/cmd/slide.rs:234 (delete); same pattern in index_of :182, move_slide :269, duplicate :193 and section_add :534

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 16:29:18 +02:00
oracola91 2071ff599d Control server: wait after a failed accept instead of retrying at once
The accepting thread dropped failed accepts with incoming().flatten()
and asked again at once. When accepting keeps failing, for example while
the process has no file descriptors left, that loop keeps a core busy
until it recovers.

Wait 100 ms after a failed accept. The loop goes through a small helper
so the wait can be tested without running out of file descriptors.
2026-10-20 14:49:51 +03:00
oracola91 1df1d2e724 The AppImage and DeckCraft.app ship the licence files
The AppImage step moved usr/share/doc out of the AppDir, so the AppImage
shipped without the licence files the .deb, .rpm and .tar.gz carry from
the same staged tree. DeckCraft.app only got the craft-fonts licences, and
no licence at all when built without CRAFT_FONTS_DIR.

Keep usr/share/doc in the AppDir, and copy the docs into the app bundle
with copy_docs, as the CLI zip does, before signing.
2026-10-20 14:21:42 +03:00
NetterSilverandClaude Haiku 5.5 e843309280 Fix extra empty histogram bin with a fixed bin count
Found while testing crates/model/src/chart.rs:1129-1135 (histogram_bins)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 08:28:08 +00:00
Sazid Al Bayazid 0405fb73bf Charts: a series' own data-label setting decides whether its labels show (#868)
The reader stored a series-level `c:dLbls/c:showVal` in
`Series.data_labels`, but every drawing path tested only the chart-wide
`Chart.data_labels`: a series that asked for labels got none, and a
series that asked for none got them. The series' own setting now wins,
and a bare `<c:showVal/>` (no `val` attribute) means true, as the
schema's default.

The plot group's own setting is respected too: a group that asks for no
labels keeps them off, even when another group of a combo chart asks for
them. Before, any group that asked turned the chart-wide setting on, and
the group that said no inherited it.

Tests: `crates/pptx/tests/chart_data_labels.rs` - a bare series
`showVal` shows that series' labels while its siblings stay quiet; an
explicit `val="0"` on the first series keeps it quiet under a chart-wide
on; a combo chart keeps each group's own setting; all three settings
round-trip. Red-proof: with the series-level setting ignored, the first
two fail; with the group's setting ignored, the combo chart's bar group
shows labels it asked to keep quiet.

The model gains `Chart::series_data_labels`, the one place that decides
whether a series' labels show; the render paths (bar, column, line,
area, pie, doughnut, radar, scatter) call it.
2026-10-20 09:31:04 +06:00
Sazid Al Bayazid 0b476e823d Tests: the Options dialog's Cancel, Esc and the close button restore the settings (#878)
The Options dialog applies every change as it is made, and closing it
with Cancel, Esc or the window's close button puts the settings back the
way they were. Nothing covered that, so a change to the dialog could have
dropped it unnoticed.

The kittest now covers all three ways out: a checkbox put back by Cancel,
the appearance and the interface size put back by Esc, the same checkbox
put back by the window's close button, and the dialog closing in each
case. Red-proof: with the Cancel restore removed the checkbox test fails
(the setting stays changed), with the Esc restore removed the appearance
test fails (it stays dark), and with the Esc/close restore removed the
close-button test fails (the setting stays changed).

ROADMAP progress-log row added.
2026-10-20 09:21:26 +06:00
Sazid Al Bayazid 52a9a45f6a Render: a quick style on an empty placeholder is covered by a test (#832)
The fix changed the empty-placeholder fill gate, but nothing locked the
behaviour in: a quick style's fill has to appear on an empty placeholder,
and a placeholder with no style at all still has to stay empty.

The new render test draws an empty body placeholder in the editor with a
quick style and checks that the accent fill is painted - every channel of
it - and that the same placeholder without a style stays white. With the
gate reverted to `s.fill.is_none()` alone the first assertion fails - the
placeholder stays white - so the test is red on the unfixed code.
2026-10-20 09:09:28 +06:00
Sazid Al Bayazid bc12031d70 Canvas: the resize cursor follows the handle's diagonal, whatever the shape (#745)
Two gaps in the resize cursor, both found by a review of the fix:

- The handle's direction was taken from the box centre to the handle, so a
  wide rectangle's corner pointed almost along an edge: a 400x80 shape's NW
  corner showed a horizontal cursor. A corner drags along the diagonal
  whatever the shape's proportions, so the direction is now normalised -
  the diagonal at 45 degrees - and only the shape's rotation and flips
  turn it.
- The cursor read the shape's own box, but the handles are drawn from the
  effective one, which a placeholder inherits from its layout. Such a
  placeholder showed the default arrow instead of a resize cursor.

The tests cover both: a wide shape keeps its corner cursors diagonal, and
a placeholder that inherits its box has a resize cursor. Red-proof: with
the aspect ratio back in the direction, the wide shape's NW corner shows
a horizontal cursor; with the shape's own box, the placeholder shows the
default arrow.
2026-10-20 08:33:22 +06:00
Brandon ThomasandClaude Opus 5.5 fe510bdac6 Release 0.9.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.9.0
2026-10-19 22:18:44 -04:00
Eduardo GarciaandClaude Sonnet 5.5 a9031d63e1 Test: resolve attribute prefixes in the namespace check (#974)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 17:12:53 -03:00
Zetenypapp 0dc39b9efc Merge remote-tracking branch 'origin/main' into fix/smart-duplicate-966
# Conflicts:
#	ROADMAP.md
2026-10-19 21:33:38 +02:00
Zetenypapp 57d65faf4f Merge remote-tracking branch 'origin/main' into fix/find-focus-951
# Conflicts:
#	ROADMAP.md
2026-10-19 21:33:38 +02:00
Hamza 7040bb0619 Control server: cap the polite drain at the grace deadline
set_read_timeout applies to each read, so a peer that sent a byte just
before every timeout expired could hold the connection thread, and its
slot, for roughly twice the grace. Set the remaining time before each
read instead.
2026-10-20 00:28:41 +05:00
Hamza 900f62f5a3 Control server: a rejected line's reason survives a pipelined peer
A client that sent another line while its previous one was being rejected never
saw the reason. `serve` writes the error and drops the socket; the client's
second write lands on a socket the server has already closed, the kernel answers
it with RST, and an RST discards what was queued for the client -- including the
reply. The client reads a connection reset instead of being told why.

The comment there, "a peer that can't read the reason loses nothing", holds for
a peer that stopped reading. A peer that is reading loses the reason, and it is
the one that gets reset.

`close_politely` shuts the write side down so the FIN follows the reply, then
reads and discards for up to the write timeout and at most 64 KiB, so the peer's
pipelined line is absorbed instead of answered with RST. Both paths that reject
and close use it: a line that is not a request, and a line over the length
limit. A peer that keeps talking is still dropped at the deadline.

This is also why `a_line_that_is_not_a_request_closes_the_connection` was red
about four times in five: it writes a second line after the junk one. Run alone
twelve times, it failed 10 on `main`, 0 with the second line removed, and 0 with
this change and the second line kept.
2026-10-20 00:28:41 +05:00
Hamza 4b474d77a9 Replace Fonts test: a window wide enough to hold Home's ribbon
Home's ribbon has grown since the test was written. At the harness's
1400x900 the Editing group starts at x=1398, so the Replace Fonts
button is two pixels wide on screen and a click at its centre lands
outside the window: the button is found and enabled, but never clicked,
and the dialog assertion fails. The ribbon does not scroll sideways, so
the window has to hold it; 1800x1100 is what the other full-ribbon
tests here use.

cargo xtask ci fails at `test` on main without this.
2026-10-20 00:28:19 +05:00