* #147: wire Picture Format UI for inserted pictures
* #147: fix Picture Format state bugs from review (alt/crop buffers, undo coalescing, pending target, tab fallback)
* #147: review round 2 (cancel pending change on next action, alt media check, drag-only undo join)
---------
Co-authored-by: Tony <gittonyp@gmail.com>
* Find/Replace: skip text deleted under Track Changes
With Track Changes on, Replace marks the match deleted and leaves it in
the paragraph text, then inserts the replacement in front of it. The
search helper matched the raw paragraph text, so the next step found that
same deleted "cat" right after the caret and replaced it again: four
Replaces on "one cat two cat" gave "one dogdogdogdog two cat" and
`remaining` never dropped.
`search` (used by Find, Find Next/Previous, Replace, its `remaining`
count and Replace All) now skips any match that overlaps a run marked
deleted, so deleted text is never found, as in Word. A match that would
run across deleted text is skipped too: that string is not in the
document as it now reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Undo: a failed command keeps the redo stack and the oldest undo step
`run` checkpoints a mutating command before running it, and the
checkpoint clears the redo stack (and, once the history is at its limit,
drops the oldest step). When the command then failed, the error path put
back the document, selection and typing group and truncated the history,
but the cleared redo stack stayed cleared. Insert, Undo, then any
command that fails on its parameters (`text.insert {}`,
`para.align {value:"bogus"}`) made the undone work unrecoverable. At the
limit, truncating to the old length also kept the failed command's empty
step and lost the oldest real one.
The checkpoint now hands back what it removed, and the error path
restores both. join_next is deliberately left consumed: the drag that
set it re-arms it every frame, and restoring it would merge the next
unrelated command into the drag's undo step.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Find/Replace: find live text next to a tracked deletion
The previous fix rejected any regex match that overlapped tracked-deleted
text, but `find_iter` had already consumed those positions, so a valid
match overlapping the rejected one was never tried. "aaa" with the first
"a" deleted reads "aa", yet Find "aa" found nothing (raw 0..2 was
rejected and the scan resumed at 2) and Replace All replaced nothing.
Searching the raw text also let deleted characters decide word
boundaries: deleting the space in "cat fish" still matched whole-word
"fish".
`search` now builds each paragraph as it reads, without deleted runs,
and searches each stretch of live text separately with
`regex_automata::Input::range`. A match stays within one stretch, as
before, and `\b`, `^` and `$` see the neighbouring live text, so a
deletion neither creates nor hides a word boundary. `regex::Regex` cannot
bound a search's end without slicing the haystack, which turns the slice
end into a word boundary; regex-automata is the engine `regex` already
uses (0.4.18, already in the lockfile), so no new code is built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Undo: a failed command restores history changed by nested commands
The previous fix had the checkpoint report what it evicted and put that
back on failure, but it only saw the outer command's own checkpoint.
Commands run nested commands, and a nested one that succeeds checkpoints
too. `file.inspect {"remove":["comments","revisions"]}` in a
comments-only document runs `review.deleteComment` (allowed) and then
`review.acceptAll` (refused). With 499 undo steps, the nested checkpoint
evicted the oldest step and the rollback, truncating to 499, kept the
outer command's empty step instead. At 500, only one of the two
evictions came back. Nested undo, redo and version restore change the
stacks in ways a length cannot describe either.
Undo steps are now `Arc<Undo>`, and `run` snapshots both stacks whole
before a mutating command and puts them back on failure, so the
history is exact whatever ran inside. The snapshot copies one pointer
per step, under 1,000 refcount bumps and an 8 KB allocation at
MAX_UNDO, beside the two document clones every mutating command
already makes. Undo and Redo are not mutating commands, so a top-level
Undo still moves its document out of the step without cloning it. Only
an undo nested inside a mutating command (a macro) clones, because the
snapshot still holds the step. This replaces the eviction-return
plumbing, so `checkpoint` is back to its original form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: compare undo timelines without comment timestamps
failed_command_restores_history_changed_by_nested_commands builds the
same session twice and compares their documents. Comments are stamped
with the time to the second, so when the clock crossed a second between
the two builds the documents differed and the test failed (about 1 run
in 12). Clear the stamp in the compared copies; the test still checks
every other part of each document in the timeline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Undo: wrap the typing-parity undo step in Arc after merging main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
#128's coverage test requires every English string; #72 and #194 added three in the same batch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The canvas ignored egui's zoom delta, so Ctrl/Cmd+wheel and trackpad pinch
did nothing over the document. Apply it through view.zoom, starting from the
shown scale so fit modes (Page Width etc.) zoom from where they are.
Co-authored-by: Simon Geljic <simon.geljic2@det.nsw.edu.au>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Every viewport command schedules a repaint, so sending the title at the
end of each frame kept the app redrawing at the monitor's refresh rate
while idle. Remember the last title sent and skip identical ones.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
771-entry pt-br.tsv catalog written clean-room from the English labels,
one LANGUAGES row, locale/persistence/coverage/font regression tests,
and README, ROADMAP, control-protocol and ATTRIBUTION updates.
A w:num can redefine any of its abstract list's levels with a whole
w:lvl inside w:lvlOverride. We kept only the start value from it and
numbered with the abstract list's levels, so documents that rely on it
showed the wrong labels: a construction specification (SpecLink) read
"A. PART 1 GENERAL" and "I. SECTION INCLUDES" where Word shows "PART 1
GENERAL" and "1.01 SECTION INCLUDES". Saving such a document also
dropped the definitions, so Word showed the wrong labels afterwards.
Num::level_overrides keeps them; Numbering::level and the list counters
prefer them over the abstract list's levels; the .docx reader reads them
with the abstract levels' parser and the writer writes them back (one
w:lvlOverride per level with its startOverride and/or lvl).
In docxide-pdf's Word reference corpus 6 of 382 documents carry such
definitions; the specification's SSIM goes 13% -> 41%, the others
restate their abstract lists and are unchanged.
Tests: counters use a list's own levels ("" and "%2.%3" in decimalZero
give "1.01", "1.02", "2.01") and fall back to the abstract list for the
rest; level overrides round-trip through .docx beside start overrides.
Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
krilla subsets fonts only when the document is finished, so one font its
subsetter rejects ("failed to subset font: malformed font") failed the
whole PDF export. Two of the 365 documents in docxide-pdf's Word
reference corpus hit this on Linux: Caladea Italic and Bold Italic
(ttf-caladea, our metric-compatible stand-in for Cambria) and Noto Sans
New Tai Lue Bold.
When finishing fails on a font, the exporter finds the face krilla
names, logs a warning and writes the document again with that face's
glyphs drawn as filled outlines (FontDb::outline), including in the
watermark; every other font is still embedded as selectable text. At
most 32 faces fall back before the export gives up with the error.
kurbo moves from dev-dependencies to dependencies of wordcraft-pdf (it
was already in the workspace).
Test: a copy of the bundled Source Sans 3 with one glyph turned into a
truncated composite fails the old export exactly as the corpus documents
did; it now exports, with no text glyphs from that font and its glyphs
drawn as paths.
Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The web build always starts eframe's wgpu runner; only `?webgl` changes
the backend. `?cpu` selected the same renderer as no flag, so drop it
from the renderer table (and the HOSTING.md shipped in the web archive).
Fixes#126.
Co-authored-by: lloyd <lloyd@stackworx.io>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
View › Switch Modes (and File › Options › Dark page) shows the page as dark
grey (80% black, #333333) with every document colour's lightness inverted
and its hue kept, so black text turns white and dark-blue headings light
blue. Pictures are untouched and the caret turns white. Screen only: saving,
printing and PDFs are unchanged. The setting is remembered between runs.
Refs #185.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: close the TOC field in its last entry, not in a text box inside it
`para_inner` took `toc_end_here` only after writing the paragraph's content,
so a nested story in the last TOC entry (a text box) reached `para_inner`
first, took the flag, and wrote the field's end inside `w:txbxContent`.
Take it before the content.
Review follow-up on #52.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: Update Table reads a heading above the TOC at its own index
Pass 1 shifted every heading by `entries.len()` to account for the entries
now in the body, but entries go in below the TOC heading, so a heading above
it never moves. Its lookup landed on a TOC entry and took that page (a
Preface on page 1 above a TOC on page 2 showed 2). Shift only headings below
the TOC.
Review follow-up on #52.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: start the TOC field in the first entry, as Word does
The writer opened the TOC field at the end of the "Contents" heading.
Word reads that fine, but LibreOffice splits the heading: "Content" as
body text, then "s" as the TOC title (on the next page when the TOC
follows a page break). Word itself writes the heading before the field
and begins the field in the first entry; written that way, LibreOffice
shows the heading whole.
Hold the heading's field back and write its begin/instruction/separate
at the start of the first entry, taken before the paragraph content so
a text box in the heading or an entry can't claim it (as with the end).
The reader moves a TOC field that starts a paragraph right after a TOC
Heading back to the end of the heading, so WordCraft's own files read
back as before and a TOC saved by Word now updates in WordCraft too
(before, Update Table left Word's first entry behind as a duplicate).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* HTML import: keep body text when </head> is omitted
Close an open head implicitly on body-content start tags, before skipped raw-text handling. Preserve head metadata and title capture while allowing body paragraphs and inline text to import.
Co-Authored-By: Codex <noreply@openai.com>
* RTF import: read pictures inside shppict wrappers
Recognize shppict as a transparent destination so nested pict groups are decoded instead of inheriting Skip. Continue skipping nonshppict legacy duplicates and pictures under other skipped destinations.
Co-Authored-By: Codex <noreply@openai.com>
---------
Co-authored-by: Codex <noreply@openai.com>
`hostile_params_never_panic` runs every command four times, Read Aloud
included, and on macOS that spawns `say` with the whole sample document:
four overlapping voices for minutes after `cargo test` returns. Skip it
with the other commands that reach outside the session; it ignores its
params, so junk payloads had nothing to exercise there.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The control server answered a line that wasn't JSON with an error and kept
reading, so an HTTP request to the port (for example a web page's fetch to
127.0.0.1) could carry a command such as file.saveAs in its body. It now
replies once and closes the connection on the first bad line; HTTP request
lines never parse, so nothing after them runs.
Ctrl/Cmd+click on a hyperlink passed any URL to the system. It now follows
only http, https and mailto links, and shows a status message otherwise, so
a document can't launch file: paths or custom-scheme handlers.
Tests cover JSON requests, closing on bad JSON, an HTTP POST carrying a
command, and the link allow-list. The control protocol docs note the
closing behaviour.
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Typing parity with Word: lists, AutoFormat and AutoCorrect as you type
Typed side by side with Word (same keystrokes, results read back) and matched it:
- Enter after a list no longer gets stuck: the paragraph that leaves a list goes back to
Normal, and later Enters make blank paragraphs (the `num: 0` marker counted as a list item,
so every Enter re-ran "leave the list" and did nothing; the list indent also stuck to every
following paragraph).
- Enter on an empty nested item moves it up a level before leaving the list.
- Second Backspace at the start of an un-numbered list paragraph returns it to Normal
instead of joining it to the paragraph above.
- "1. " always starts numbering at 1; "a. " and "I. " start lettered/roman lists; "- " makes
a dash bullet.
- Undo right after an AutoFormat/AutoCorrect change undoes only that change.
- AutoCorrect: sentence capitals (with abbreviation/initial exceptions), runs on Enter too,
"word -- word" en dash, "wait..." ellipsis, 1/2 1/4 3/4, ordinal superscripts, web
addresses become links.
- Border lines: Enter after ---, ===, ***, ___, ~~~, ### draws a bottom border.
- Layout: no line break right after a slash ("and/or", URLs), as in Word.
- ui_shot: UI_SHOT_FULL=1 prints whole replies for scripted checks.
Regression tests in crates/engine/src/tests_typing.rs and the layout tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Typing parity: record Word's observed typing rules, two more tests
docs/typing-parity.md lists what Word does for each key sequence (lists, Backspace, Tab,
AutoCorrect, AutoFormat, slash line breaks) with the test that pins it, the known gaps, and how
to compare without taking over the shared machine. CLAUDE.md/AGENTS.md point at it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
WordCraft's interface can now be shown in Simplified Chinese, Traditional
Chinese or Japanese as well as English. The new `language` setting is
`auto` by default: the UI follows the operating system's preferred
languages (Windows display language, macOS preferred languages,
LANG/LC_* on Unix, via sys-locale; WORDCRAFT_LANGUAGE overrides) when
there is a catalog for one of them, and English otherwise. File > Options
> Interface language picks one by hand, and the `ui.language` command does
the same for scripts and agents. The setting persists with the other UI
preferences.
The i18n module is PdfCraft's (catalog format, parser, locale matching),
trimmed to plain entries. English strings in the code are the keys; the
ribbon helpers (group, big, small, split, menu_button, menu items,
tooltips and ribbon locations), tabs, Backstage, Options, dialogs, panes,
the status bar, the context menu and command search translate at draw
time. Command ids, documents, the CLI, MCP and the control channel stay
English. Catalogs hold ~780 entries per language, written clean-room from
the English meaning.
Chinese interfaces put the craft-fonts Chinese face before the Japanese
one so a line never mixes glyph styles; fonts are reinstalled when the
language changes.
Fixes#8.
Claude-Session: https://claude.ai/code/session_019FczntrDNYd6DupyBdpbL5
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
* Desktop: write log records to standard error and a rotating log file
The desktop app installed no logger, so every log::warn!/log::error! in
the workspace was discarded: an unreadable part of a .docx, a picture or
font a PDF export had to leave out, a PNG that would not encode. A launch
from a desktop menu has no terminal either, and a panic's message went to
that missing terminal, so there was nothing to attach to a bug report.
apps/wordcraft/src/logging.rs (no new dependency):
- standard error plus logs/wordcraft.log next to the preferences
(~/.config/wordcraft/logs, or under $XDG_CONFIG_HOME); each launch
rotates the previous log to wordcraft.1.log and .2.log, so the log of
a run that crashed survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
(bounded) and written once the file is attached
- default levels: info for wordcraft*, warn for everything else;
RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
error as the only sink
- runs with WORDCRAFT_NO_PREFS (agents' test runs) log to standard error
only, so they don't rotate away the user's own logs
- a panic hook reports every panic, also those Session::run's guard
recovers from, through the logger; text the message quotes between
backticks (std quotes the string it failed to slice, which can be the
user's writing) is left out
The logger is installed first in main, before the panic hook, and the
file is opened after argument parsing (--version leaves no file). The
control server's and file-open messages go through the logger and drop
their "wordcraft: " prefix, which the record's target now shows.
Documented in README.md (Logs).
* Desktop: no eprintln! on the log and panic paths
eprintln! panics when standard error is a broken pipe (a GUI launch on
Windows), and a panic inside the panic hook aborts. The "no log file"
message goes through log::warn! (standard error only by then); the panic
hook's fallback writes to standard error with write_fmt and ignores the
error. Same fix as storytold/vectorcraft#465.
---------
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
The DMG opened as a plain Finder window. It now shows the app icon's figure on
the WordCraft colour, with WordCraft.app and the Applications link side by side.
Same approach as PhotoCraft's and VectorCraft's (storytold/photocraft#1201,
storytold/vectorcraft#493).
- packaging/macos/dmg/: background.svg (source; text outlined, no paper grain,
the figure an <image> of assets/app-icon), background.tiff (1x + 2x HiDPI,
16-colour palette, Deflate, sRGB; ~23 KB)
and dmg-layout.DS_Store (Finder view settings: window, icon size 128,
positions, background).
- generate.py writes the TIFF and the layout on any OS, byte-for-byte
reproducible, with nothing from the machine that ran it (no paths, users,
disk names or volume UUIDs).
- package.sh copies them into the staging folder; the image is still built with
hdiutil makehybrid. Nothing extra runs in the signing job.
- The volume name drops the version: the layout finds the background through
an alias that includes the volume name. The DMG file name keeps the version.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M6: Update Table puts each heading on its real page
The second pass removed the entries and laid the document out again before
looking headings up at `path + entries.len()`, so every lookup landed past
its heading: later entries showed the next heading's page and the last fell
back to page 1. Lay out with the entries still in place, then replace them.
A heading typed after a manual page break also starts with the break, so its
page is now read after any leading breaks rather than before them.
The existing test only had a heading on page 1; the new one puts headings on
pages 2, 3 and 4 (before: 4, 1, 1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M6: save the table of contents as a TOC field Word can update
The writer saved the TOC heading's field empty (begin, instruction, separate
and end all at the end of "Contents"), with the entries as plain paragraphs
after it. Word showed them as ordinary text: no TOC frame, and no Update
Table or Update Field on the entries.
Write the field so its result spans the entries (closed at the end of the
last entry) and wrap heading and entries in the Table of Contents content
control (w:sdt with docPartGallery "Table of Contents", ECMA-376 17.5.2).
The reader already keeps a multi-paragraph field's start as an empty field
in its first paragraph and the rest as text, so WordCraft reads the TOC back
exactly as before; the new round-trip test checks both the file and that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
eframe's default wgpu instance enables PRIMARY | GL on Windows. Creating
the OpenGL backend can crash inside AMD's atio6axx.dll before the window
appears, so the app exits silently at launch. When WGPU_BACKEND is not
set, the Windows instance now enables only DirectX 12; an explicit
WGPU_BACKEND is still honoured. macOS, Linux and web are unchanged.
Same fix as storytold/designcraft#168 and storytold/filmcraft#179.
Claude-Session: https://claude.ai/code/session_01LyVbT6Vv4BwWZ4hY8973qD
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
references.footnote starts the note's text with a NoteRef to the note itself,
so the editor shows its number. The writer already puts the w:footnoteRef
mark at the start of every note, then also wrote that NoteRef as a
w:footnoteReference, so each note referenced itself. LibreOffice refuses to
open such a file ("source file could not be loaded").
Skip a note's reference to itself while writing that note; the mark stands
in for it. Notes read from Word files never contain one, so they are
unaffected.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
wordcraft.wxs shipped with DesignCraft's UpgradeCode and app component
GUID. Windows Installer treats packages that share an UpgradeCode as one
product, so with DesignCraft 0.2.1 installed the WordCraft 0.1.0 MSI was a
"downgrade" and refused to install ("A newer version of WordCraft is
already installed"), and installing DesignCraft after WordCraft would have
removed WordCraft.
Give WordCraft fresh GUIDs, drop the header comment copied from PhotoCraft,
and make packaging-lint fail if the .wxs reuses a sibling Craft app's
UpgradeCode/component GUID or repeats a GUID.
Fixes#4.
Claude-Session: https://claude.ai/code/session_019FczntrDNYd6DupyBdpbL5
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
egui's built-in keyboard zoom fired on the same keys as Word's shortcuts (optional hyphen, subscript, space before), so Ctrl+- looked like zoom out while it also typed an optional hyphen over the selection. Turn it off once at startup; document zoom stays under View > Zoom and the status bar.
tall_first_page_header_pushes_body_down compared page 1's first line with
the last paragraph on the last page. Where Aptos falls back to Liberation
Sans (Linux without Carlito), the last page is nearly full and that
paragraph sits below page 1's first line, so the test failed although
layout was correct. Compare with the first paragraph on page 2 instead,
which is what the test means: later pages don't reserve the tall
first-page header's space.
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
apply_style's guard ended with `&& id == "Normal"`, making the
`id.starts_with("Heading") || id == "Title"` terms dead: only Normal
cleared a numbered paragraph's direct numbering, so applying Heading 1/2/3
or Title to a list item left the number behind — contradicting the
comment right above it. Drop the redundant term and cover it with a
regression test.
Also clears the clippy overly_complex_bool_expr error this expression
raised, which was failing `cargo xtask ci`.
Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Also fix the .deb/.rpm description in packaging/linux/nfpm.yaml, which still described PhotoCraft.
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- packaging/linux/package.sh: embed gh-releases-zsync update information in each AppImage
(appimagetool -u) and write the .zsync beside it; the Linux job installs zsync and checks both.
- New flatpak job (x86_64 on ubuntu-24.04, aarch64 on ubuntu-24.04-arm) repackages the Linux
tarball with packaging/linux/flatpak-bundle.sh and ai.storyteller.wordcraft.bundle.yml; no Rust
build, no release environment. The release job needs it.
- version, Linux and web jobs drop `environment: release` (they sign nothing), so a branch
workflow_dispatch dry-runs every unsigned job; the release job still needs the signed jobs.
- FreeBSD release build embeds craft-fonts like the other desktop builds and ships its licences.
- Flathub manifest: same finish-args as the bundle (adds xdg-documents), builds the CLI too;
packaging-lint checks both manifests agree.
- README: Downloads section; docs/releasing.md updated.
Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Custom macOS larger runners in a runner group do not pick up jobs; the
built-in M2 Pro label does. Only release.yml uses it, and the
actions_macos_xl budget caps the monthly spend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The standard macOS runners are shared with all CI and queue for an hour or
more. The macos-release runner (GitHub-hosted M2 Pro) is in a runner group
usable only by release.yml on the release branch, with a monthly spend cap
on the actions_macos_xl SKU.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)
Adds About ▸ Contributors and About ▸ Models tabs per the craftrules standard
(standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as
static tables. Each contributor line: @username, opt-in display/real name, PRs, commits,
lines +/−/Δ, binary assets +/−, first/last commit. Sortable by every column; alphabetical
ignores case and the leading @. `ui.dialog` also accepts `contributors` / `models` to open
the About window on that tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section
build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Contributors: regenerate (verified people.toml entries)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- body on a titlePg first page now starts below the first-page header
- decode tiff pictures
- fade images through an opacity layer; vello_cpu panics on image sampler alpha